Skip to content

fix: signed-shift UB in SLH-DSA base_2b digest accumulator#10917

Open
MarkAtwood wants to merge 1 commit into
wolfSSL:masterfrom
MarkAtwood:fix/slhdsa-base2b-shift-ub
Open

fix: signed-shift UB in SLH-DSA base_2b digest accumulator#10917
MarkAtwood wants to merge 1 commit into
wolfSSL:masterfrom
MarkAtwood:fix/slhdsa-base2b-shift-ub

Conversation

@MarkAtwood

@MarkAtwood MarkAtwood commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

slhdsakey_base_2b() accumulates the FORS message digest into a signed int that is never masked down; from the fourth consumed byte onward total << 8 exceeds INT32_MAX, which is undefined behavior (CWE-190, C11 6.5.7p4). It sits on the sign and verify path of every SLH-DSA parameter set.

Fix: declare total as word32. Output is identical: the overflowed high bits were always discarded by the mask. SLH-DSA KATs pass unchanged.

Verified with -fsanitize=shift (with -fwrapv removed from CFLAGS): pre-fix, wc_slhdsa.c:1717: left shift of 609481888 by 8 places cannot be represented in type 'int' fires during the standard KAT test; post-fix the run is clean.

Exposure note: autotools builds get -fwrapv from the hardening macros, which gives this defined wrapping semantics, but embedded users compiling wolfCrypt sources with their own flags have no such guarantee. -fwrapv also suppresses the sanitizer's shift-base check, which is why nightly sanitizer runs never caught it.

Credit: found by Dominik Blain of Qreative Lab (qreativelab.io) using their Z3-based formal analysis engine, reported with a machine-checked witness. Same bug class as the earlier fix in this file from PR #10104, which addressed a different site.

Related: #10918 (ML-DSA gamma1_19 encoder), #10919 (ML-DSA slow-multiply reductions) — same bug class, found in the same report.

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 fixes undefined behavior in the SLH-DSA base_2b() digest-to-indices conversion by ensuring the digest accumulator is unsigned, eliminating signed left-shift overflow on common inputs while preserving output correctness.

Changes:

  • Change the total accumulator in slhdsakey_base_2b() from int to word32 to avoid signed-shift UB during byte accumulation.

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

The FORS digest accumulator 'total' was a signed int and is never
masked, so 'total << 8' overflows INT32_MAX from the 4th consumed
byte onward (C11 6.5.7p4 undefined behavior). Declare it word32.
Output is unchanged: overflowed high bits were already discarded
by the mask. Reproduced with -fsanitize=shift during the standard
SLH-DSA KAT test; clean after this change.
@MarkAtwood
MarkAtwood force-pushed the fix/slhdsa-base2b-shift-ub branch from 91d1db9 to c428218 Compare July 15, 2026 19:42
@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