From 3360eeb74b660b9a5911d624b7d363151a7609b5 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Wed, 24 Jun 2026 13:56:02 -0500 Subject: [PATCH] Fix wolfSSL_BUF_MEM_grow_ex with WOLFSSL_NO_REALLOC --- .github/workflows/smoke-test.yml | 3 +++ src/ssl.c | 4 +++- tests/api.c | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index 5cb6f198b3b..e849389f36e 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -150,6 +150,9 @@ jobs: {"name": "sanitize-asan", "configure": ["--enable-all"], "cflags": "-fsanitize=address -fno-omit-frame-pointer -g -O1", "ldflags": "-fsanitize=address"}, + {"name": "opensslextra-norealloc-asan", "configure": ["--enable-opensslextra"], + "cflags": "-DWOLFSSL_NO_REALLOC -fsanitize=address -fno-omit-frame-pointer -g -O1", + "ldflags": "-fsanitize=address"}, {"name": "enable-all-smallstack", "configure": ["--enable-all", "--enable-smallstack"]}, {"name": "enable-all", "configure": ["--enable-all"]}, {"name": "integration", "configure": ["--enable-openssh", "--enable-lighty", "--enable-stunnel", "--enable-opensslextra"]}, diff --git a/src/ssl.c b/src/ssl.c index c2a5827c9dd..c546296cc75 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -15429,7 +15429,9 @@ int wolfSSL_BUF_MEM_grow_ex(WOLFSSL_BUF_MEM* buf, size_t len, #ifdef WOLFSSL_NO_REALLOC tmp = (char*)XMALLOC(mx, NULL, DYNAMIC_TYPE_OPENSSL); if (tmp != NULL && buf->data != NULL) { - XMEMCPY(tmp, buf->data, len_int); + /* only the existing content is valid in the old buffer; copying + * len_int (the new, larger size) would read past buf->max */ + XMEMCPY(tmp, buf->data, buf->length); XFREE(buf->data, NULL, DYNAMIC_TYPE_OPENSSL); buf->data = NULL; } diff --git a/tests/api.c b/tests/api.c index 32b90b9a079..59c7ee48980 100644 --- a/tests/api.c +++ b/tests/api.c @@ -17034,10 +17034,33 @@ static int test_wolfSSL_BUF(void) EXPECT_DECLS; #if defined(OPENSSL_EXTRA) BUF_MEM* buf = NULL; + unsigned char pattern[16]; + ExpectNotNull(buf = BUF_MEM_new()); ExpectIntEQ(BUF_MEM_grow(buf, 10), 10); ExpectIntEQ(BUF_MEM_grow(buf, -1), 0); BUF_MEM_free(buf); + buf = NULL; + + /* Regression test for a heap over-read in wolfSSL_BUF_MEM_grow_ex(): the + * WOLFSSL_NO_REALLOC path copied the new (larger) size from the old buffer, + * reading (len - buf->max) bytes past it. Those stray bytes are then zeroed + * by the grow, so the over-read is observable only under a memory sanitizer + * (ASAN/valgrind) built with WOLFSSL_NO_REALLOC. The content check below is + * a sanity check that passes on both the buggy and fixed paths. */ + XMEMSET(pattern, 0x55, sizeof(pattern)); + ExpectNotNull(buf = BUF_MEM_new()); + /* establish a small allocation, then seed it with a known pattern */ + ExpectIntEQ(BUF_MEM_grow(buf, sizeof(pattern)), (int)sizeof(pattern)); + if (buf != NULL && buf->data != NULL) + XMEMCPY(buf->data, pattern, sizeof(pattern)); + /* grow well past buf->max to exercise the malloc+copy+free path */ + ExpectIntEQ(BUF_MEM_grow(buf, 256), 256); + ExpectNotNull(buf == NULL ? NULL : buf->data); + /* sanity check; the over-read itself is flagged by ASAN, not this assert */ + if (buf != NULL && buf->data != NULL) + ExpectIntEQ(XMEMCMP(buf->data, pattern, sizeof(pattern)), 0); + BUF_MEM_free(buf); #endif return EXPECT_RESULT(); }