Skip to content

fix: signed-shift UB in ML-DSA gamma1_19 encoder (32-bit path)#10918

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

fix: signed-shift UB in ML-DSA gamma1_19 encoder (32-bit path)#10918
MarkAtwood wants to merge 1 commit into
wolfSSL:masterfrom
MarkAtwood:fix/mldsa-gamma1-19-shift-ub

Conversation

@MarkAtwood

Copy link
Copy Markdown
Contributor

The 32-bit little-endian path of mldsa_encode_gamma1_19_bits_c() (compiled when WC_64BIT_CPU is not set and WOLFSSL_MLDSA_ALIGNMENT <= 2) shifts sword32 operands whose values reach 2^20-1: z1 << 20 and z3 << 28 both overflow INT32_MAX, which is undefined behavior (CWE-190, C11 6.5.7p4). The sibling encoders (gamma1_17, w1) already declare these locals as word32 with an explicit cast; this encoder was missed.

Fix: same idiom as the siblings, word32 zN = (word32)(MLDSA_GAMMA1_19 - z[j + N]);. Output bits are unchanged. ML-DSA KATs pass on both the 32-bit and 64-bit paths.

Verified with -fsanitize=shift -DWC_32BIT_CPU -DWOLFSSL_MLDSA_ALIGNMENT=0 (and -fwrapv removed, since it suppresses the check): pre-fix, left shift of 1034459 by 20 and left shift of 325275 by 28 fire at the two lines during the standard KAT test; post-fix the run is clean.

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 (same bug class in SLH-DSA base_2b).

The 32-bit little-endian path shifts sword32 operands (z1 << 20,
z3 << 28) whose values reach 2^20-1, overflowing INT32_MAX (C11
6.5.7p4). Declare the locals word32 with an explicit cast, the
same idiom the gamma1_17/w1 encoders already use. Output bits
unchanged; ML-DSA KATs pass on 32-bit and 64-bit paths.

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 pull request fixes undefined behavior in the 32-bit little-endian implementation of the ML‑DSA gamma1_19 encoder by ensuring shift operands are unsigned, matching the pattern already used by sibling encoders and keeping the encoded output unchanged.

Changes:

  • Change z0..z3 temporaries in mldsa_encode_gamma1_19_bits_c() from sword32 to word32 with an explicit cast of the subtraction result.
  • Eliminate signed-left-shift overflow UB in the 32-bit packing path (z1 << 20, z3 << 28) while preserving identical bit packing.

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

@Frauschi

Copy link
Copy Markdown
Contributor

Jenkins retest this please

@Frauschi Frauschi 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.

LGTM

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.

3 participants