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.
This commit is contained in:
Tobias Frauenschläger
2026-08-07 08:11:23 +02:00
parent fa2faecdf9
commit d4755b4b62
2 changed files with 38 additions and 0 deletions
+27
View File
@@ -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();
+11
View File
@@ -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--;