Skip to content

Commit 390d30c

Browse files
committed
Fix buffer-overflow in LMS leaf cache indexing
wc_lms_treehash_init() writes leaf node hashes into the leaf cache using an absolute index (i * hash_len), but the cache is only max_cb entries starting from leaf->idx. When leaf->idx > 0 (which occurs when wc_LmsKey_Reload is called after signing more than max_cb times), the write goes past the end of the cache buffer. Fix by using the relative offset (i - leaf->idx) * hash_len instead. Added unit tests (test_lms.c): - test_wc_LmsKey_sign_verify: basic sign/verify sanity check - test_wc_LmsKey_reload_cache: (TDD) reproduces the overflow by signing 33 times then reloading the key
1 parent b3f08f3 commit 390d30c

6 files changed

Lines changed: 243 additions & 1 deletion

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2816,6 +2816,7 @@ if(WOLFSSL_EXAMPLES)
28162816
tests/api/test_ed448.c
28172817
tests/api/test_mlkem.c
28182818
tests/api/test_mldsa.c
2819+
tests/api/test_lms.c
28192820
tests/api/test_signature.c
28202821
tests/api/test_dtls.c
28212822
tests/api/test_ocsp.c

tests/api.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
#include <tests/api/test_sha512.h>
179179
#include <tests/api/test_sha3.h>
180180
#include <tests/api/test_blake2.h>
181+
#include <tests/api/test_lms.h>
181182
#include <tests/api/test_sm3.h>
182183
#include <tests/api/test_ripemd.h>
183184
#include <tests/api/test_hash.h>
@@ -32969,6 +32970,8 @@ TEST_CASE testCases[] = {
3296932970
TEST_MLKEM_DECLS,
3297032971
/* Dilithium */
3297132972
TEST_MLDSA_DECLS,
32973+
/* LMS */
32974+
TEST_LMS_DECLS,
3297232975
/* Signature API */
3297332976
TEST_SIGNATURE_DECLS,
3297432977

tests/api/include.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ tests_unit_test_SOURCES += tests/api/test_curve448.c
4646
tests_unit_test_SOURCES += tests/api/test_ed448.c
4747
tests_unit_test_SOURCES += tests/api/test_mlkem.c
4848
tests_unit_test_SOURCES += tests/api/test_mldsa.c
49+
tests_unit_test_SOURCES += tests/api/test_lms.c
4950
tests_unit_test_SOURCES += tests/api/test_signature.c
5051
# TLS Protocol
5152
tests_unit_test_SOURCES += tests/api/test_dtls.c
@@ -148,6 +149,7 @@ EXTRA_DIST += tests/api/test_curve448.h
148149
EXTRA_DIST += tests/api/test_ed448.h
149150
EXTRA_DIST += tests/api/test_mlkem.h
150151
EXTRA_DIST += tests/api/test_mldsa.h
152+
EXTRA_DIST += tests/api/test_lms.h
151153
EXTRA_DIST += tests/api/test_signature.h
152154
EXTRA_DIST += tests/api/test_dtls.h
153155
EXTRA_DIST += tests/api/test_ocsp.h

tests/api/test_lms.c

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
/* test_lms.c
2+
*
3+
* Copyright (C) 2006-2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfSSL.
6+
*
7+
* wolfSSL is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* wolfSSL is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20+
*/
21+
22+
#include <tests/unit.h>
23+
24+
#ifdef NO_INLINE
25+
#include <wolfssl/wolfcrypt/misc.h>
26+
#else
27+
#define WOLFSSL_MISC_INCLUDED
28+
#include <wolfcrypt/src/misc.c>
29+
#endif
30+
31+
#include <wolfssl/wolfcrypt/types.h>
32+
#include <tests/api/api.h>
33+
#include <tests/api/test_lms.h>
34+
35+
#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \
36+
!defined(WOLFSSL_LMS_VERIFY_ONLY)
37+
38+
#include <wolfssl/wolfcrypt/wc_lms.h>
39+
#include <wolfssl/wolfcrypt/lms.h>
40+
#include <wolfssl/wolfcrypt/error-crypt.h>
41+
#include <wolfssl/wolfcrypt/random.h>
42+
43+
#define LMS_TEST_PRIV_KEY_FILE "/tmp/wolfssl_test_lms.key"
44+
45+
static int test_lms_write_key(const byte* priv, word32 privSz, void* context)
46+
{
47+
FILE* f = fopen((const char*)context, "wb");
48+
if (f == NULL)
49+
return -1;
50+
fwrite(priv, 1, privSz, f);
51+
fclose(f);
52+
return WC_LMS_RC_SAVED_TO_NV_MEMORY;
53+
}
54+
55+
static int test_lms_read_key(byte* priv, word32 privSz, void* context)
56+
{
57+
FILE* f = fopen((const char*)context, "rb");
58+
if (f == NULL)
59+
return -1;
60+
if (fread(priv, 1, privSz, f) == 0) {
61+
fclose(f);
62+
return -1;
63+
}
64+
fclose(f);
65+
return WC_LMS_RC_READ_TO_MEMORY;
66+
}
67+
68+
/* Helper: init an LMS key with callbacks and L1-H10-W8 params */
69+
static int test_lms_init_key(LmsKey* key, WC_RNG* rng)
70+
{
71+
int ret;
72+
73+
ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID);
74+
if (ret != 0) return ret;
75+
76+
ret = wc_LmsKey_SetParameters(key, 1, 10, 8);
77+
if (ret != 0) return ret;
78+
79+
ret = wc_LmsKey_SetWriteCb(key, test_lms_write_key);
80+
if (ret != 0) return ret;
81+
82+
ret = wc_LmsKey_SetReadCb(key, test_lms_read_key);
83+
if (ret != 0) return ret;
84+
85+
ret = wc_LmsKey_SetContext(key, (void*)LMS_TEST_PRIV_KEY_FILE);
86+
if (ret != 0) return ret;
87+
88+
(void)rng;
89+
return 0;
90+
}
91+
92+
#endif /* WOLFSSL_HAVE_LMS && WOLFSSL_WC_LMS && !WOLFSSL_LMS_VERIFY_ONLY */
93+
94+
/*
95+
* Test basic LMS sign/verify with multiple signings.
96+
* Uses L1-H10-W8 (1024 total signatures, 32-entry leaf cache).
97+
*/
98+
int test_wc_LmsKey_sign_verify(void)
99+
{
100+
EXPECT_DECLS;
101+
#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \
102+
!defined(WOLFSSL_LMS_VERIFY_ONLY)
103+
LmsKey key;
104+
WC_RNG rng;
105+
byte msg[] = "test message for LMS signing";
106+
byte sig[2048];
107+
word32 sigSz;
108+
int i;
109+
int numSigs = 5;
110+
111+
ExpectIntEQ(wc_InitRng(&rng), 0);
112+
113+
remove(LMS_TEST_PRIV_KEY_FILE);
114+
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
115+
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
116+
117+
for (i = 0; i < numSigs; i++) {
118+
sigSz = sizeof(sig);
119+
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
120+
ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
121+
}
122+
123+
wc_LmsKey_Free(&key);
124+
wc_FreeRng(&rng);
125+
remove(LMS_TEST_PRIV_KEY_FILE);
126+
#endif
127+
return EXPECT_RESULT();
128+
}
129+
130+
/*
131+
* Test LMS key reload after advancing past the leaf cache window.
132+
*
133+
* Reproduces a heap-buffer-overflow bug in wc_lms_treehash_init() where the
134+
* leaf cache write uses (i * hash_len) instead of ((i - leaf->idx) * hash_len).
135+
* When q > max_cb (default 32), wc_LmsKey_Reload calls wc_hss_init_auth_path
136+
* which calls wc_lms_treehash_init with q > 0, causing writes past the end of
137+
* the leaf cache buffer.
138+
*
139+
* Reproduction steps:
140+
* 1. Generate L1-H10-W8 key (cacheBits=5, max_cb=32)
141+
* 2. Sign 33 times to advance q past the cache window
142+
* 3. Free the key and reload from persisted state
143+
* 4. Sign and verify after reload
144+
*
145+
* Without the fix: heap-buffer-overflow at wc_lms_impl.c:1965
146+
* With the fix: all operations succeed, signatures verify
147+
*/
148+
int test_wc_LmsKey_reload_cache(void)
149+
{
150+
EXPECT_DECLS;
151+
#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \
152+
!defined(WOLFSSL_LMS_VERIFY_ONLY)
153+
LmsKey key;
154+
LmsKey vkey;
155+
WC_RNG rng;
156+
byte msg[] = "test message for LMS signing";
157+
byte sig[2048];
158+
word32 sigSz;
159+
byte pub[64];
160+
word32 pubSz = sizeof(pub);
161+
int i;
162+
/* Sign 33 times to advance q past the 32-entry cache window. */
163+
int preSigs = 33;
164+
165+
ExpectIntEQ(wc_InitRng(&rng), 0);
166+
167+
/* Phase 1: Generate key and sign past cache window */
168+
remove(LMS_TEST_PRIV_KEY_FILE);
169+
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
170+
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
171+
172+
for (i = 0; i < preSigs; i++) {
173+
sigSz = sizeof(sig);
174+
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
175+
}
176+
177+
/* Save public key for verification after reload */
178+
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz), 0);
179+
180+
wc_LmsKey_Free(&key);
181+
182+
/* Phase 2: Reload key — triggers wc_lms_treehash_init with q=33 */
183+
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
184+
ExpectIntEQ(wc_LmsKey_Reload(&key), 0);
185+
186+
/* Phase 3: Sign after reload and verify with separate verify-only key */
187+
sigSz = sizeof(sig);
188+
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
189+
190+
ExpectIntEQ(wc_LmsKey_Init(&vkey, NULL, INVALID_DEVID), 0);
191+
ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 10, 8), 0);
192+
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&vkey, pub, pubSz), 0);
193+
ExpectIntEQ(wc_LmsKey_Verify(&vkey, sig, sigSz, msg, sizeof(msg)), 0);
194+
195+
wc_LmsKey_Free(&vkey);
196+
wc_LmsKey_Free(&key);
197+
wc_FreeRng(&rng);
198+
remove(LMS_TEST_PRIV_KEY_FILE);
199+
#endif
200+
return EXPECT_RESULT();
201+
}

tests/api/test_lms.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/* test_lms.h
2+
*
3+
* Copyright (C) 2006-2026 wolfSSL Inc.
4+
*
5+
* This file is part of wolfSSL.
6+
*
7+
* wolfSSL is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation; either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* wolfSSL is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
20+
*/
21+
22+
#ifndef WOLFCRYPT_TEST_LMS_H
23+
#define WOLFCRYPT_TEST_LMS_H
24+
25+
#include <tests/api/api_decl.h>
26+
27+
int test_wc_LmsKey_sign_verify(void);
28+
int test_wc_LmsKey_reload_cache(void);
29+
30+
#define TEST_LMS_DECLS \
31+
TEST_DECL_GROUP("lms", test_wc_LmsKey_sign_verify), \
32+
TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_cache)
33+
34+
#endif /* WOLFCRYPT_TEST_LMS_H */

wolfcrypt/src/wc_lms_impl.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1962,7 +1962,8 @@ static int wc_lms_treehash_init(LmsState* state, LmsPrivState* privState,
19621962

19631963
/* Cache leaf node if in range. */
19641964
if ((ret == 0) && (i >= leaf->idx) && (i < leaf->idx + max_cb)) {
1965-
XMEMCPY(leaf->cache + i * params->hash_len, temp, params->hash_len);
1965+
XMEMCPY(leaf->cache + (i - leaf->idx) * params->hash_len, temp,
1966+
params->hash_len);
19661967
}
19671968

19681969
/* Store the node if on the authentication path. */

0 commit comments

Comments
 (0)