Skip to content

fix: signed-shift UB in ML-DSA slow-multiply reductions#10919

Open
MarkAtwood wants to merge 1 commit into
wolfSSL:masterfrom
MarkAtwood:fix/mldsa-slow-red-shift-ub
Open

fix: signed-shift UB in ML-DSA slow-multiply reductions#10919
MarkAtwood wants to merge 1 commit into
wolfSSL:masterfrom
MarkAtwood:fix/mldsa-slow-red-shift-ub

Conversation

@MarkAtwood

@MarkAtwood MarkAtwood commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Under the opt-in MLDSA_MUL_QINV_SLOW / MLDSA_MUL_Q_SLOW build options (both default OFF), mldsa_mont_red() and mldsa_red() left-shift signed operands that are routinely negative (the NTT zetas), which is undefined behavior (CWE-190, C11 6.5.7p4). The default multiply-based paths are unaffected.

Fix: perform the shifts on word32/word64 and convert back. Results are bit-identical: the QINV path only ever keeps the low 32 bits, and the Q_SLOW word64 shifts are exact (|t * 2^23| < 2^54). ML-DSA KATs pass.

Verified with -fsanitize=shift -DMLDSA_MUL_QINV_SLOW -DMLDSA_MUL_Q_SLOW --disable-intelasm (and -fwrapv removed): pre-fix, seven shift-UB reports across the four flagged lines fire during the standard KAT test, including left shift of negative value; post-fix the run is clean. Note the C reduction paths only execute with Intel ASM disabled, another reason sanitizer nightlies never saw this.

Credit: found by Dominik Blain of Qreative Lab (qreativelab.io) using their Z3-based formal analysis engine, reported with a machine-checked witness.

Related: #10917 (SLH-DSA base_2b) and #10918 (ML-DSA gamma1_19 encoder).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses C undefined behavior in the opt-in ML-DSA “slow multiply” reduction paths by eliminating left shifts of routinely-negative signed values in mldsa_mont_red() and mldsa_red(), while preserving bit-identical results.

Changes:

  • Updates the MLDSA_MUL_QINV_SLOW path in mldsa_mont_red() to perform shift-based arithmetic on word32 instead of signed types.
  • Updates the MLDSA_MUL_Q_SLOW paths in mldsa_mont_red() and mldsa_red() to perform shift-based arithmetic using unsigned operands and cast back.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread wolfcrypt/src/wc_mldsa.c Outdated
Comment on lines +5621 to +5622
return (sword32)((a - (sword64)((word64)t << 23) +
(sword64)((word64)t << 13) - t) >> 32);

@MarkAtwood MarkAtwood Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI to AI: Reworked in ff34b2d: the Q_SLOW expression is now computed entirely in word64 with a single conversion back to sword64 before the arithmetic >> 32. The remaining unsigned-to-signed conversion is value-preserving (|result| < 2^56, well inside sword64 range) on the two's-complement targets wolfSSL supports. Verified: ML-DSA KATs pass under -fsanitize=shift -fno-sanitize-recover=all with both slow-mul defines; pre-fix code aborts on the same harness.

@MarkAtwood
MarkAtwood force-pushed the fix/mldsa-slow-red-shift-ub branch from 9af907f to 08aaf00 Compare July 15, 2026 19:41
Under MLDSA_MUL_QINV_SLOW / MLDSA_MUL_Q_SLOW, mldsa_mont_red and
mldsa_red left-shift signed operands that are routinely negative
(NTT zetas), which is undefined behavior. Perform the shifts on
word32/word64 and convert back; results are bit-identical. Only
opt-in slow-multiply builds compile these paths. Reproduced with
-fsanitize=shift --disable-intelasm; clean after this change.
@MarkAtwood
MarkAtwood force-pushed the fix/mldsa-slow-red-shift-ub branch from 08aaf00 to ff34b2d Compare July 16, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants