Skip to content

Commit 00d0b09

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 3540d89 commit 00d0b09

2 files changed

Lines changed: 178 additions & 1 deletion

File tree

tests/api.c

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32856,6 +32856,178 @@ static int test_wolfSSL_inject(void)
3285632856
int testAll = 1;
3285732857
int stopOnFail = 0;
3285832858

32859+
/*----------------------------------------------------------------------------*/
32860+
/* LMS tests */
32861+
/*----------------------------------------------------------------------------*/
32862+
int test_wc_LmsKey_sign_verify(void);
32863+
int test_wc_LmsKey_reload_cache(void);
32864+
32865+
#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \
32866+
!defined(WOLFSSL_LMS_VERIFY_ONLY)
32867+
32868+
#include <wolfssl/wolfcrypt/wc_lms.h>
32869+
#include <wolfssl/wolfcrypt/lms.h>
32870+
32871+
#define LMS_TEST_PRIV_KEY_FILE "/tmp/wolfssl_test_lms.key"
32872+
32873+
static int test_lms_write_key(const byte* priv, word32 privSz, void* context)
32874+
{
32875+
FILE* f = fopen((const char*)context, "wb");
32876+
if (f == NULL)
32877+
return -1;
32878+
fwrite(priv, 1, privSz, f);
32879+
fclose(f);
32880+
return WC_LMS_RC_SAVED_TO_NV_MEMORY;
32881+
}
32882+
32883+
static int test_lms_read_key(byte* priv, word32 privSz, void* context)
32884+
{
32885+
FILE* f = fopen((const char*)context, "rb");
32886+
if (f == NULL)
32887+
return -1;
32888+
if (fread(priv, 1, privSz, f) == 0) {
32889+
fclose(f);
32890+
return -1;
32891+
}
32892+
fclose(f);
32893+
return WC_LMS_RC_READ_TO_MEMORY;
32894+
}
32895+
32896+
/* Helper: init an LMS key with callbacks and L1-H10-W8 params */
32897+
static int test_lms_init_key(LmsKey* key, WC_RNG* rng)
32898+
{
32899+
int ret;
32900+
32901+
ret = wc_LmsKey_Init(key, NULL, INVALID_DEVID);
32902+
if (ret != 0) return ret;
32903+
32904+
ret = wc_LmsKey_SetParameters(key, 1, 10, 8);
32905+
if (ret != 0) return ret;
32906+
32907+
ret = wc_LmsKey_SetWriteCb(key, test_lms_write_key);
32908+
if (ret != 0) return ret;
32909+
32910+
ret = wc_LmsKey_SetReadCb(key, test_lms_read_key);
32911+
if (ret != 0) return ret;
32912+
32913+
ret = wc_LmsKey_SetContext(key, (void*)LMS_TEST_PRIV_KEY_FILE);
32914+
if (ret != 0) return ret;
32915+
32916+
(void)rng;
32917+
return 0;
32918+
}
32919+
32920+
#endif /* WOLFSSL_HAVE_LMS && WOLFSSL_WC_LMS && !WOLFSSL_LMS_VERIFY_ONLY */
32921+
32922+
/*
32923+
* Test basic LMS sign/verify with multiple signings.
32924+
* Uses L1-H10-W8 (1024 total signatures, 32-entry leaf cache).
32925+
*/
32926+
int test_wc_LmsKey_sign_verify(void)
32927+
{
32928+
EXPECT_DECLS;
32929+
#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \
32930+
!defined(WOLFSSL_LMS_VERIFY_ONLY)
32931+
LmsKey key;
32932+
WC_RNG rng;
32933+
byte msg[] = "test message for LMS signing";
32934+
byte sig[2048];
32935+
word32 sigSz;
32936+
int i;
32937+
int numSigs = 5;
32938+
32939+
ExpectIntEQ(wc_InitRng(&rng), 0);
32940+
32941+
remove(LMS_TEST_PRIV_KEY_FILE);
32942+
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
32943+
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
32944+
32945+
for (i = 0; i < numSigs; i++) {
32946+
sigSz = sizeof(sig);
32947+
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
32948+
ExpectIntEQ(wc_LmsKey_Verify(&key, sig, sigSz, msg, sizeof(msg)), 0);
32949+
}
32950+
32951+
wc_LmsKey_Free(&key);
32952+
wc_FreeRng(&rng);
32953+
remove(LMS_TEST_PRIV_KEY_FILE);
32954+
#endif
32955+
return EXPECT_RESULT();
32956+
}
32957+
32958+
/*
32959+
* Test LMS key reload after advancing past the leaf cache window.
32960+
*
32961+
* Reproduces a heap-buffer-overflow bug in wc_lms_treehash_init() where the
32962+
* leaf cache write uses (i * hash_len) instead of ((i - leaf->idx) * hash_len).
32963+
* When q > max_cb (default 32), wc_LmsKey_Reload calls wc_hss_init_auth_path
32964+
* which calls wc_lms_treehash_init with q > 0, causing writes past the end of
32965+
* the leaf cache buffer.
32966+
*
32967+
* Reproduction steps:
32968+
* 1. Generate L1-H10-W8 key (cacheBits=5, max_cb=32)
32969+
* 2. Sign 33 times to advance q past the cache window
32970+
* 3. Free the key and reload from persisted state
32971+
* 4. Sign and verify after reload
32972+
*
32973+
* Without the fix: heap-buffer-overflow at wc_lms_impl.c:1965
32974+
* With the fix: all operations succeed, signatures verify
32975+
*/
32976+
int test_wc_LmsKey_reload_cache(void)
32977+
{
32978+
EXPECT_DECLS;
32979+
#if defined(WOLFSSL_HAVE_LMS) && defined(WOLFSSL_WC_LMS) && \
32980+
!defined(WOLFSSL_LMS_VERIFY_ONLY)
32981+
LmsKey key;
32982+
LmsKey vkey;
32983+
WC_RNG rng;
32984+
byte msg[] = "test message for LMS signing";
32985+
byte sig[2048];
32986+
word32 sigSz;
32987+
byte pub[64];
32988+
word32 pubSz = sizeof(pub);
32989+
int i;
32990+
/* Sign 33 times to advance q past the 32-entry cache window. */
32991+
int preSigs = 33;
32992+
32993+
ExpectIntEQ(wc_InitRng(&rng), 0);
32994+
32995+
/* Phase 1: Generate key and sign past cache window */
32996+
remove(LMS_TEST_PRIV_KEY_FILE);
32997+
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
32998+
ExpectIntEQ(wc_LmsKey_MakeKey(&key, &rng), 0);
32999+
33000+
for (i = 0; i < preSigs; i++) {
33001+
sigSz = sizeof(sig);
33002+
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
33003+
}
33004+
33005+
/* Save public key for verification after reload */
33006+
ExpectIntEQ(wc_LmsKey_ExportPubRaw(&key, pub, &pubSz), 0);
33007+
33008+
wc_LmsKey_Free(&key);
33009+
33010+
/* Phase 2: Reload key. Triggers wc_lms_treehash_init with q=33 */
33011+
ExpectIntEQ(test_lms_init_key(&key, &rng), 0);
33012+
ExpectIntEQ(wc_LmsKey_Reload(&key), 0);
33013+
33014+
/* Phase 3: Sign after reload and verify with separate verify-only key */
33015+
sigSz = sizeof(sig);
33016+
ExpectIntEQ(wc_LmsKey_Sign(&key, sig, &sigSz, msg, sizeof(msg)), 0);
33017+
33018+
ExpectIntEQ(wc_LmsKey_Init(&vkey, NULL, INVALID_DEVID), 0);
33019+
ExpectIntEQ(wc_LmsKey_SetParameters(&vkey, 1, 10, 8), 0);
33020+
ExpectIntEQ(wc_LmsKey_ImportPubRaw(&vkey, pub, pubSz), 0);
33021+
ExpectIntEQ(wc_LmsKey_Verify(&vkey, sig, sigSz, msg, sizeof(msg)), 0);
33022+
33023+
wc_LmsKey_Free(&vkey);
33024+
wc_LmsKey_Free(&key);
33025+
wc_FreeRng(&rng);
33026+
remove(LMS_TEST_PRIV_KEY_FILE);
33027+
#endif
33028+
return EXPECT_RESULT();
33029+
}
33030+
3285933031
TEST_CASE testCases[] = {
3286033032
TEST_DECL(test_fileAccess),
3286133033

@@ -32977,6 +33149,10 @@ TEST_CASE testCases[] = {
3297733149
/* ASN */
3297833150
TEST_ASN_DECLS,
3297933151

33152+
/* LMS */
33153+
TEST_DECL_GROUP("lms", test_wc_LmsKey_sign_verify),
33154+
TEST_DECL_GROUP("lms", test_wc_LmsKey_reload_cache),
33155+
3298033156
/* PEM and DER APIs. */
3298133157
TEST_DECL(test_wc_PemToDer),
3298233158
TEST_DECL(test_wc_AllocDer),

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)