From 2af2a2967f8a309f65e77b48cfd190ef6c233823 Mon Sep 17 00:00:00 2001 From: Daniel Pouzzner Date: Mon, 29 Jun 2026 23:48:36 -0500 Subject: [PATCH] fix F-3085 "Base64_Decode silently returns success with outLen=0 when input is a 1-3 byte truncated base64 fragment, violating decode(encode(x)) roundtrip for inputs producing 2-3 base64 chars without padding" wolfcrypt/src/coding.c: in Base64_Decode() and Base64_Decode_nonCT(), check for non-whitespace characters past the end and return ASN_INPUT_E if found; wolfcrypt/test/test.c: in base64_test(), remove ';' from goodChar[], and add trailing*[] test strings and N_BYTE_TRAILING_TEST(), for positive and negative testing of new checks. --- wolfcrypt/src/coding.c | 62 +++++++++++++++++++++++++++++++++++------- wolfcrypt/test/test.c | 35 +++++++++++++++++++++++- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/wolfcrypt/src/coding.c b/wolfcrypt/src/coding.c index b3f804fcf0..c89dab769c 100644 --- a/wolfcrypt/src/coding.c +++ b/wolfcrypt/src/coding.c @@ -192,6 +192,7 @@ int Base64_Decode_nonCT(const byte* in, word32 inLen, byte* out, word32* outLen) } e1 = in[j++]; if (e1 == '\0') { + inLen = 0; break; } inLen--; @@ -230,11 +231,6 @@ int Base64_Decode_nonCT(const byte* in, word32 inLen, byte* out, word32* outLen) return ASN_INPUT_E; } - if (i + 1 + !pad3 + !pad4 > *outLen) { - WOLFSSL_MSG("Bad Base64 Decode out buffer, too small"); - return BUFFER_E; - } - e1 = Base64_Char2Val_by_table(e1); e2 = Base64_Char2Val_by_table(e2); e3 = (byte)((e3 == PAD) ? 0 : Base64_Char2Val_by_table(e3)); @@ -245,6 +241,11 @@ int Base64_Decode_nonCT(const byte* in, word32 inLen, byte* out, word32* outLen) return ASN_INPUT_E; } + if (i + 1 + !pad3 + !pad4 > *outLen) { + WOLFSSL_MSG("Bad Base64 Decode out buffer, too small"); + return BUFFER_E; + } + b1 = (byte)((e1 << 2) | (e2 >> 4)); b2 = (byte)(((e2 & 0xF) << 4) | (e3 >> 2)); b3 = (byte)(((e3 & 0x3) << 6) | e4); @@ -258,6 +259,24 @@ int Base64_Decode_nonCT(const byte* in, word32 inLen, byte* out, word32* outLen) break; } + /* If there is still input available, and it's not whitespace or nulls, then + * the input is invalid. + */ + while (inLen > 0) { + word32 cur_j = j; + if (in[j] == 0) + break; + if ((ret = Base64_SkipNewline(in, &inLen, &j)) != 0) { + if (ret == WC_NO_ERR_TRACE(BUFFER_E)) { + /* Running out of buffer here is not an error */ + break; + } + return ret; + } + if (j == cur_j) + return ASN_INPUT_E; + } + /* If the output buffer has a room for an extra byte, add a null terminator */ if (out && *outLen > i) out[i]= '\0'; @@ -294,6 +313,7 @@ int Base64_Decode(const byte* in, word32 inLen, byte* out, word32* outLen) } e1 = in[j++]; if (e1 == '\0') { + inLen = 0; break; } inLen--; @@ -321,11 +341,6 @@ int Base64_Decode(const byte* in, word32 inLen, byte* out, word32* outLen) if (pad3 && !pad4) return ASN_INPUT_E; - if (i + 1 + !pad3 + !pad4 > *outLen) { - WOLFSSL_MSG("Bad Base64 Decode out buffer, too small"); - return BUFFER_E; - } - e1 = Base64_Char2Val_CT(e1); e2 = Base64_Char2Val_CT(e2); e3 = (byte)((e3 == PAD) ? 0 : Base64_Char2Val_CT(e3)); @@ -336,6 +351,15 @@ int Base64_Decode(const byte* in, word32 inLen, byte* out, word32* outLen) return ASN_INPUT_E; } + /* Output space check needs to follow input character validation to + * assure ASN_INPUT_E is returned on truncated input with the + * terminating null included in the input buffer. + */ + if (i + 1 + !pad3 + !pad4 > *outLen) { + WOLFSSL_MSG("Bad Base64 Decode out buffer, too small"); + return BUFFER_E; + } + b1 = (byte)((e1 << 2) | (e2 >> 4)); b2 = (byte)(((e2 & 0xF) << 4) | (e3 >> 2)); b3 = (byte)(((e3 & 0x3) << 6) | e4); @@ -349,6 +373,24 @@ int Base64_Decode(const byte* in, word32 inLen, byte* out, word32* outLen) break; } + /* If there is still input available, and it's not whitespace or nulls, then + * the input is invalid. + */ + while (inLen > 0) { + word32 cur_j = j; + if (in[j] == 0) + break; + if ((ret = Base64_SkipNewline(in, &inLen, &j)) != 0) { + if (ret == WC_NO_ERR_TRACE(BUFFER_E)) { + /* Running out of buffer here is not an error */ + break; + } + return ret; + } + if (j == cur_j) + return ASN_INPUT_E; + } + /* If the output buffer has a room for an extra byte, add a null terminator */ if (out && *outLen > i) out[i]= '\0'; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 49deeaf0ce..928f573858 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -4130,12 +4130,18 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void) static const byte goodChar[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" - "0123456789+/;"; + "0123456789+/"; static const byte charTest[] = "A+Gd\0\0\0"; static const byte oneByteTest[] = "YQ=="; static const byte twoByteTest[] = "YWE="; static const byte threeByteTest[] = "YWFh"; static const byte fourByteTest[] = "YWFhYQ=="; + static const byte trailingLFTest[] = "YWFhYQ==\n\n"; + static const byte trailingSpaceTest[] = "YWFhYQ== "; + static const byte trailingCodesTest1[] = "YWFhY"; + static const byte trailingCodesTest2[] = "YWFhYW"; + static const byte trailingCodesTest3[] = "YWFhYWF"; + static const byte trailingJunkTest[] = "YWFhYQ==X"; static const byte byteTestOutput[] = "aaaa"; int i; WOLFSSL_ENTER("base64_test"); @@ -4255,6 +4261,26 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void) N_BYTE_TEST(Base64_Decode, 3, threeByteTest); N_BYTE_TEST(Base64_Decode, 4, fourByteTest); +#define N_BYTE_TRAILING_TEST(f, n, t, e) do { \ + outLen = (n); \ + ret = (f)(t, sizeof(t), out, &outLen); \ + if (ret != (e)) \ + return WC_TEST_RET_ENC_EC(ret); \ + else if (ret == 0) { \ + if (outLen != (n)) \ + return WC_TEST_RET_ENC_I(outLen); \ + if (XMEMCMP(out, byteTestOutput, n) != 0) \ + return WC_TEST_RET_ENC_NC; \ + } \ + } while (0) + + N_BYTE_TRAILING_TEST(Base64_Decode, 4, trailingLFTest, 0); + N_BYTE_TRAILING_TEST(Base64_Decode, 4, trailingSpaceTest, 0); + N_BYTE_TRAILING_TEST(Base64_Decode, 4, trailingCodesTest1, WC_NO_ERR_TRACE(ASN_INPUT_E)); + N_BYTE_TRAILING_TEST(Base64_Decode, 4, trailingCodesTest2, WC_NO_ERR_TRACE(ASN_INPUT_E)); + N_BYTE_TRAILING_TEST(Base64_Decode, 4, trailingCodesTest3, WC_NO_ERR_TRACE(ASN_INPUT_E)); + N_BYTE_TRAILING_TEST(Base64_Decode, 4, trailingJunkTest, WC_NO_ERR_TRACE(ASN_INPUT_E)); + /* Same tests again, using Base64_Decode_nonCT() */ /* Good Base64 encodings. */ @@ -4335,6 +4361,13 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void) N_BYTE_TEST(Base64_Decode_nonCT, 3, threeByteTest); N_BYTE_TEST(Base64_Decode_nonCT, 4, fourByteTest); + N_BYTE_TRAILING_TEST(Base64_Decode_nonCT, 4, trailingLFTest, 0); + N_BYTE_TRAILING_TEST(Base64_Decode_nonCT, 4, trailingSpaceTest, 0); + N_BYTE_TRAILING_TEST(Base64_Decode_nonCT, 4, trailingCodesTest1, WC_NO_ERR_TRACE(ASN_INPUT_E)); + N_BYTE_TRAILING_TEST(Base64_Decode_nonCT, 4, trailingCodesTest2, WC_NO_ERR_TRACE(ASN_INPUT_E)); + N_BYTE_TRAILING_TEST(Base64_Decode_nonCT, 4, trailingCodesTest3, WC_NO_ERR_TRACE(ASN_INPUT_E)); + N_BYTE_TRAILING_TEST(Base64_Decode_nonCT, 4, trailingJunkTest, WC_NO_ERR_TRACE(ASN_INPUT_E)); + #ifdef WOLFSSL_BASE64_ENCODE /* Decode and encode all symbols - non-alphanumeric. */ dataLen = sizeof(data);