From d4755b4b6261e92e044adb756533e246cb42fd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Wed, 5 Aug 2026 17:18:22 +0200 Subject: [PATCH] Fix EVP_DecodeUpdate overflow on NUL byte in base64 input The quad decoding loop broke out on a NUL byte without clearing the remaining input length, unlike the equivalent loops in Base64_Decode and Base64_Decode_nonCT. Control then fell into the block that buffers the leftover input in the context, which copied the full remaining byte count into the 48 byte ctx->data with an unbounded index, and read one byte past the end of the caller's buffer. An application decoding attacker supplied base64 with an explicit, binary safe length could write attacker controlled data past the end of a heap allocated encode context. Clear the length before breaking, and bound the leftover copy loop at one decode block, which is the most that loop can legitimately buffer. That also keeps ctx->remaining below the block size, so a reused context cannot underflow the copy size in the next update call or over-read ctx->data in EVP_DecodeFinal. Add a negative test feeding a NUL byte followed by more non-whitespace data than the context buffer can hold. Fixes F-7445. --- tests/api/test_evp.c | 27 +++++++++++++++++++++++++++ wolfcrypt/src/evp.c | 11 +++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/api/test_evp.c b/tests/api/test_evp.c index 354d056660..5a89b171f7 100644 --- a/tests/api/test_evp.c +++ b/tests/api/test_evp.c @@ -584,6 +584,33 @@ int test_wolfSSL_EVP_DecodeUpdate(void) 0); } + /* decode input holding a NUL byte at the start of a 4 byte group, followed + * by more data than the context buffer can hold */ + + { + unsigned char enc5[64]; + + XMEMSET(enc5, 'A', sizeof(enc5)); + enc5[0] = '\0'; + + EVP_DecodeInit(ctx); + + ExpectIntEQ( + EVP_DecodeUpdate( + ctx, + decOutBuff, + &outl, + enc5, + (int)sizeof(enc5)), + 1 /* expected result code 1: success */ + ); + ExpectIntEQ(outl, 0); + /* Before the fix all 64 input bytes were buffered into the 48 byte + * ctx->data. The NUL byte ends the input, so nothing may be buffered + * at all. */ + ExpectIntEQ(ctx->remaining, 0); + } + EVP_ENCODE_CTX_free(ctx); #endif /* OPENSSL && WOLFSSL_BASE_DECODE */ return EXPECT_RESULT(); diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index 37f6ba5155..2d178c2505 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -13925,6 +13925,8 @@ int wolfSSL_EVP_DecodeUpdate(WOLFSSL_EVP_ENCODE_CTX* ctx, } e[0] = in[j++]; if (e[0] == '\0') { + /* a NUL byte ends the input, nothing is left to buffer */ + inLen = 0; break; } inLen--; @@ -14005,6 +14007,15 @@ int wolfSSL_EVP_DecodeUpdate(WOLFSSL_EVP_ENCODE_CTX* ctx, if (c == '=') { pad = 1; } + if (i >= BASE64_DECODE_BLOCK_SIZE) { + /* More leftover data than one decode block can hold. The loops + * above leave at most a partial block behind, so this is a + * bound on the buffer and not a reachable input error. */ + XMEMSET(ctx->data, 0, sizeof(ctx->data)); + ctx->remaining = 0; + *outl = 0; + return -1; + } ctx->data[i++] = c; ctx->remaining++; inLen--;