Skip to content

Commit d6fa846

Browse files
authored
Merge pull request #10096 from padelsbach/dilithium-oob-shift
Fix out of bounds shift in ML-DSA
2 parents f02f47b + 73c6f2a commit d6fa846

5 files changed

Lines changed: 186 additions & 17 deletions

File tree

.github/workflows/pq-all.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ jobs:
1818
matrix:
1919
config: [
2020
# Add new configs here
21+
'--disable-shared --enable-dilithium --enable-mlkem CFLAGS="-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer" LDFLAGS="-fsanitize=undefined" CPPFLAGS="-DWOLFSSL_DILITHIUM_ALIGNMENT=4"',
2122
'--enable-intelasm --enable-sp-asm --enable-mlkem=yes,kyber,ml-kem CPPFLAGS="-DWOLFSSL_ML_KEM_USE_OLD_IDS"',
2223
'--enable-intelasm --enable-sp-asm --enable-all --enable-testcert --enable-acert --enable-dtls13 --enable-dtls-mtu --enable-dtls-frag-ch --enable-dtlscid --enable-quic --with-sys-crypto-policy --enable-experimental --enable-mlkem=yes,kyber,ml-kem --enable-lms --enable-xmss --enable-slhdsa --enable-dilithium --enable-dual-alg-certs --disable-qt CPPFLAGS="-pedantic -Wdeclaration-after-statement -DWOLFCRYPT_TEST_LINT -DNO_WOLFSSL_CIPHER_SUITE_TEST -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"',
2324
'--enable-smallstack --enable-smallstackcache --enable-intelasm --enable-sp-asm --enable-all --enable-testcert --enable-acert --enable-dtls13 --enable-dtls-mtu --enable-dtls-frag-ch --enable-dtlscid --enable-quic --with-sys-crypto-policy --enable-experimental --enable-mlkem=yes,kyber,ml-kem --enable-lms --enable-xmss --enable-slhdsa --enable-dilithium --enable-dual-alg-certs --disable-qt CPPFLAGS="-pedantic -Wdeclaration-after-statement -DWOLFCRYPT_TEST_LINT -DNO_WOLFSSL_CIPHER_SUITE_TEST -DTEST_LIBWOLFSSL_SOURCES_INCLUSION_SEQUENCE"',

tests/api/test_mldsa.c

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24781,6 +24781,135 @@ int test_mldsa_pkcs8_export_import_wolfSSL_form(void)
2478124781
return EXPECT_RESULT();
2478224782
}
2478324783

24784+
/* Exercise w1 encoding with adversarial inputs to catch undefined behavior.
24785+
*
24786+
* The word32-optimized paths in dilithium_encode_w1_88_c and
24787+
* dilithium_encode_w1_32_c left-shift sword32 values by up to 30 bits.
24788+
* Large or invalid w1 values trigger signed integer overflow (UB in C).
24789+
* Under -fsanitize=undefined this test will trap on the offending shifts.
24790+
*
24791+
* We also test that encoding the same input twice yields identical output
24792+
* (determinism check - UB can cause nondeterministic results).
24793+
*/
24794+
int test_wc_dilithium_encode_w1_large_values(void)
24795+
{
24796+
EXPECT_DECLS;
24797+
#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \
24798+
(!defined(WOLFSSL_DILITHIUM_NO_SIGN) || \
24799+
!defined(WOLFSSL_DILITHIUM_NO_VERIFY))
24800+
24801+
sword32 w1[DILITHIUM_N];
24802+
unsigned int j, k;
24803+
24804+
/* Adversarial w1 values:
24805+
* - valid boundary values (0, 1, max-for-bit-width)
24806+
* - oversize values that exceed the expected bit width
24807+
* - negative values (should never appear but might if prior step is buggy)
24808+
* - extreme values near INT32 limits
24809+
* - alternating patterns that stress cross-element packing
24810+
*/
24811+
const sword32 patterns[] = {
24812+
0, /* trivial zero */
24813+
1, /* minimal nonzero */
24814+
0x7F, /* 7 bits set - wider than both 4-bit and 6-bit */
24815+
0xFF, /* full byte */
24816+
0xFFFF, /* 16 bits */
24817+
0x7FFFFFFF, /* INT32_MAX */
24818+
-1, /* all bits set (negative) */
24819+
-128, /* negative */
24820+
(sword32)0x80000000 /* INT32_MIN */
24821+
};
24822+
const int n_patterns = (int)(sizeof(patterns) / sizeof(patterns[0]));
24823+
24824+
/* ---- 6-bit encoding (dilithium_encode_w1_88 path) ---- */
24825+
#ifndef WOLFSSL_NO_ML_DSA_44
24826+
{
24827+
ALIGN32 byte enc_a[DILITHIUM_N * DILITHIUM_Q_HI_88_ENC_BITS / 8];
24828+
ALIGN32 byte enc_b[DILITHIUM_N * DILITHIUM_Q_HI_88_ENC_BITS / 8];
24829+
24830+
/* Uniform fill with each pattern */
24831+
for (k = 0; k < (unsigned int)n_patterns; k++) {
24832+
for (j = 0; j < DILITHIUM_N; j++) {
24833+
w1[j] = patterns[k];
24834+
}
24835+
24836+
XMEMSET(enc_a, 0, sizeof(enc_a));
24837+
XMEMSET(enc_b, 0, sizeof(enc_b));
24838+
wc_dilithium_encode_w1_88(w1, enc_a);
24839+
wc_dilithium_encode_w1_88(w1, enc_b);
24840+
24841+
/* Determinism: same input must produce same output */
24842+
ExpectIntEQ(XMEMCMP(enc_a, enc_b, sizeof(enc_a)), 0);
24843+
}
24844+
24845+
/* Alternating pattern: adjacent elements get different values */
24846+
for (j = 0; j < DILITHIUM_N; j++) {
24847+
w1[j] = (j & 1) ? 43 : 0;
24848+
}
24849+
XMEMSET(enc_a, 0, sizeof(enc_a));
24850+
XMEMSET(enc_b, 0, sizeof(enc_b));
24851+
wc_dilithium_encode_w1_88(w1, enc_a);
24852+
wc_dilithium_encode_w1_88(w1, enc_b);
24853+
ExpectIntEQ(XMEMCMP(enc_a, enc_b, sizeof(enc_a)), 0);
24854+
24855+
/* Ascending pattern: each element differs */
24856+
for (j = 0; j < DILITHIUM_N; j++) {
24857+
w1[j] = (sword32)(j % 44); /* 0..43 cycling */
24858+
}
24859+
XMEMSET(enc_a, 0, sizeof(enc_a));
24860+
XMEMSET(enc_b, 0, sizeof(enc_b));
24861+
wc_dilithium_encode_w1_88(w1, enc_a);
24862+
wc_dilithium_encode_w1_88(w1, enc_b);
24863+
ExpectIntEQ(XMEMCMP(enc_a, enc_b, sizeof(enc_a)), 0);
24864+
}
24865+
#endif /* !WOLFSSL_NO_ML_DSA_44 */
24866+
24867+
/* ---- 4-bit encoding (dilithium_encode_w1_32 path) ---- */
24868+
#if !defined(WOLFSSL_NO_ML_DSA_65) || !defined(WOLFSSL_NO_ML_DSA_87)
24869+
{
24870+
ALIGN32 byte enc_a[DILITHIUM_N * DILITHIUM_Q_HI_32_ENC_BITS / 8];
24871+
ALIGN32 byte enc_b[DILITHIUM_N * DILITHIUM_Q_HI_32_ENC_BITS / 8];
24872+
24873+
/* Uniform fill with each pattern */
24874+
for (k = 0; k < (unsigned int)n_patterns; k++) {
24875+
for (j = 0; j < DILITHIUM_N; j++) {
24876+
w1[j] = patterns[k];
24877+
}
24878+
24879+
XMEMSET(enc_a, 0, sizeof(enc_a));
24880+
XMEMSET(enc_b, 0, sizeof(enc_b));
24881+
wc_dilithium_encode_w1_32(w1, enc_a);
24882+
wc_dilithium_encode_w1_32(w1, enc_b);
24883+
24884+
ExpectIntEQ(XMEMCMP(enc_a, enc_b, sizeof(enc_a)), 0);
24885+
}
24886+
24887+
/* Alternating pattern */
24888+
for (j = 0; j < DILITHIUM_N; j++) {
24889+
w1[j] = (j & 1) ? 15 : 0;
24890+
}
24891+
XMEMSET(enc_a, 0, sizeof(enc_a));
24892+
XMEMSET(enc_b, 0, sizeof(enc_b));
24893+
wc_dilithium_encode_w1_32(w1, enc_a);
24894+
wc_dilithium_encode_w1_32(w1, enc_b);
24895+
ExpectIntEQ(XMEMCMP(enc_a, enc_b, sizeof(enc_a)), 0);
24896+
24897+
/* Ascending pattern */
24898+
for (j = 0; j < DILITHIUM_N; j++) {
24899+
w1[j] = (sword32)(j % 16); /* 0..15 cycling */
24900+
}
24901+
XMEMSET(enc_a, 0, sizeof(enc_a));
24902+
XMEMSET(enc_b, 0, sizeof(enc_b));
24903+
wc_dilithium_encode_w1_32(w1, enc_a);
24904+
wc_dilithium_encode_w1_32(w1, enc_b);
24905+
ExpectIntEQ(XMEMCMP(enc_a, enc_b, sizeof(enc_a)), 0);
24906+
}
24907+
#endif /* !WOLFSSL_NO_ML_DSA_65 || !WOLFSSL_NO_ML_DSA_87 */
24908+
24909+
#endif /* HAVE_DILITHIUM && WOLFSSL_WC_DILITHIUM && sign/verify */
24910+
return EXPECT_RESULT();
24911+
}
24912+
2478424913
int test_mldsa_pkcs12(void)
2478524914
{
2478624915
EXPECT_DECLS;

tests/api/test_mldsa.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ int test_wc_dilithium_verify_kats(void);
4040
int test_wc_Dilithium_PrivateKeyDecode_OpenSSL_form(void);
4141
int test_mldsa_pkcs8_import_OpenSSL_form(void);
4242
int test_mldsa_pkcs8_export_import_wolfSSL_form(void);
43+
int test_wc_dilithium_encode_w1_large_values(void);
4344
int test_mldsa_pkcs12(void);
4445

4546
#define TEST_MLDSA_DECLS \
@@ -59,6 +60,7 @@ int test_mldsa_pkcs12(void);
5960
TEST_DECL_GROUP("mldsa", test_wc_Dilithium_PrivateKeyDecode_OpenSSL_form), \
6061
TEST_DECL_GROUP("mldsa", test_mldsa_pkcs8_import_OpenSSL_form), \
6162
TEST_DECL_GROUP("mldsa", test_mldsa_pkcs8_export_import_wolfSSL_form), \
63+
TEST_DECL_GROUP("mldsa", test_wc_dilithium_encode_w1_large_values), \
6264
TEST_DECL_GROUP("mldsa", test_mldsa_pkcs12)
6365

6466
#endif /* WOLFCRYPT_TEST_MLDSA_H */

wolfcrypt/src/dilithium.c

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,15 +2191,24 @@ static void dilithium_encode_w1_88_c(const sword32* w1, byte* w1e)
21912191
* 16 numbers in 12 bytes. (16 * 6 bits = 12 * 8 bits) */
21922192
#if defined(LITTLE_ENDIAN_ORDER) && (WOLFSSL_DILITHIUM_ALIGNMENT <= 4)
21932193
word32* w1e32 = (word32*)w1e;
2194-
w1e32[0] = (word32)( w1[j+ 0] | (w1[j+ 1] << 6) |
2195-
(w1[j+ 2] << 12) | (w1[j+ 3] << 18) |
2196-
(w1[j+ 4] << 24) | (w1[j+ 5] << 30));
2197-
w1e32[1] = (word32)((w1[j+ 5] >> 2) | (w1[j+ 6] << 4) |
2198-
(w1[j+ 7] << 10) | (w1[j+ 8] << 16) |
2199-
(w1[j+ 9] << 22) | (w1[j+10] << 28));
2200-
w1e32[2] = (word32)((w1[j+10] >> 4) | (w1[j+11] << 2) |
2201-
(w1[j+12] << 8) | (w1[j+13] << 14) |
2202-
(w1[j+14] << 20) | (w1[j+15] << 26));
2194+
w1e32[0] = (word32)( (word32)w1[j+ 0] |
2195+
((word32)w1[j+ 1] << 6) |
2196+
((word32)w1[j+ 2] << 12) |
2197+
((word32)w1[j+ 3] << 18) |
2198+
((word32)w1[j+ 4] << 24) |
2199+
((word32)w1[j+ 5] << 30));
2200+
w1e32[1] = (word32)(((word32)w1[j+ 5] >> 2) |
2201+
((word32)w1[j+ 6] << 4) |
2202+
((word32)w1[j+ 7] << 10) |
2203+
((word32)w1[j+ 8] << 16) |
2204+
((word32)w1[j+ 9] << 22) |
2205+
((word32)w1[j+10] << 28));
2206+
w1e32[2] = (word32)(((word32)w1[j+10] >> 4) |
2207+
((word32)w1[j+11] << 2) |
2208+
((word32)w1[j+12] << 8) |
2209+
((word32)w1[j+13] << 14) |
2210+
((word32)w1[j+14] << 20) |
2211+
((word32)w1[j+15] << 26));
22032212
#else
22042213
w1e[ 0] = (byte)( w1[j+ 0] | (w1[j+ 1] << 6));
22052214
w1e[ 1] = (byte)((w1[j+ 1] >> 2) | (w1[j+ 2] << 4));
@@ -2238,6 +2247,11 @@ static void dilithium_encode_w1_88(const sword32* w1, byte* w1e)
22382247
dilithium_encode_w1_88_c(w1, w1e);
22392248
}
22402249
}
2250+
2251+
WOLFSSL_TEST_VIS void wc_dilithium_encode_w1_88(const sword32* w1, byte* w1e)
2252+
{
2253+
dilithium_encode_w1_88(w1, w1e);
2254+
}
22412255
#endif /* !WOLFSSL_NO_ML_DSA_44 */
22422256

22432257
#if !defined(WOLFSSL_NO_ML_DSA_65) || !defined(WOLFSSL_NO_ML_DSA_87)
@@ -2263,14 +2277,22 @@ static void dilithium_encode_w1_32_c(const sword32* w1, byte* w1e)
22632277
* 16 numbers in 8 bytes. (16 * 4 bits = 8 * 8 bits) */
22642278
#if defined(LITTLE_ENDIAN_ORDER) && (WOLFSSL_DILITHIUM_ALIGNMENT <= 8)
22652279
word32* w1e32 = (word32*)w1e;
2266-
w1e32[0] = (word32)((w1[j + 0] << 0) | (w1[j + 1] << 4) |
2267-
(w1[j + 2] << 8) | (w1[j + 3] << 12) |
2268-
(w1[j + 4] << 16) | (w1[j + 5] << 20) |
2269-
(w1[j + 6] << 24) | (w1[j + 7] << 28));
2270-
w1e32[1] = (word32)((w1[j + 8] << 0) | (w1[j + 9] << 4) |
2271-
(w1[j + 10] << 8) | (w1[j + 11] << 12) |
2272-
(w1[j + 12] << 16) | (w1[j + 13] << 20) |
2273-
(w1[j + 14] << 24) | (w1[j + 15] << 28));
2280+
w1e32[0] = (word32)(((word32)w1[j + 0] << 0) |
2281+
((word32)w1[j + 1] << 4) |
2282+
((word32)w1[j + 2] << 8) |
2283+
((word32)w1[j + 3] << 12) |
2284+
((word32)w1[j + 4] << 16) |
2285+
((word32)w1[j + 5] << 20) |
2286+
((word32)w1[j + 6] << 24) |
2287+
((word32)w1[j + 7] << 28));
2288+
w1e32[1] = (word32)(((word32)w1[j + 8] << 0) |
2289+
((word32)w1[j + 9] << 4) |
2290+
((word32)w1[j + 10] << 8) |
2291+
((word32)w1[j + 11] << 12) |
2292+
((word32)w1[j + 12] << 16) |
2293+
((word32)w1[j + 13] << 20) |
2294+
((word32)w1[j + 14] << 24) |
2295+
((word32)w1[j + 15] << 28));
22742296
#else
22752297
w1e[0] = (byte)(w1[j + 0] | (w1[j + 1] << 4));
22762298
w1e[1] = (byte)(w1[j + 2] | (w1[j + 3] << 4));
@@ -2305,6 +2327,11 @@ static void dilithium_encode_w1_32(const sword32* w1, byte* w1e)
23052327
dilithium_encode_w1_32_c(w1, w1e);
23062328
}
23072329
}
2330+
2331+
WOLFSSL_TEST_VIS void wc_dilithium_encode_w1_32(const sword32* w1, byte* w1e)
2332+
{
2333+
dilithium_encode_w1_32(w1, w1e);
2334+
}
23082335
#endif
23092336
#endif
23102337

wolfssl/wolfcrypt/dilithium.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1145,6 +1145,16 @@ WOLFSSL_API int wc_MlDsaKey_GetPrivLen(MlDsaKey* key, int* len);
11451145
WOLFSSL_API int wc_MlDsaKey_GetPubLen(MlDsaKey* key, int* len);
11461146
WOLFSSL_API int wc_MlDsaKey_GetSigLen(MlDsaKey* key, int* len);
11471147

1148+
#if !defined(WOLFSSL_DILITHIUM_NO_SIGN) || \
1149+
!defined(WOLFSSL_DILITHIUM_NO_VERIFY)
1150+
#ifndef WOLFSSL_NO_ML_DSA_44
1151+
WOLFSSL_TEST_VIS void wc_dilithium_encode_w1_88(const sword32* w1, byte* w1e);
1152+
#endif
1153+
#if !defined(WOLFSSL_NO_ML_DSA_65) || !defined(WOLFSSL_NO_ML_DSA_87)
1154+
WOLFSSL_TEST_VIS void wc_dilithium_encode_w1_32(const sword32* w1, byte* w1e);
1155+
#endif
1156+
#endif
1157+
11481158
#ifdef __cplusplus
11491159
} /* extern "C" */
11501160
#endif

0 commit comments

Comments
 (0)