From adfa8e1265f6155d1a582baa9929af198bb5d4de Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 1 May 2026 17:45:39 +0200 Subject: Misc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- librecrypt_decode.c | 110 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 13 deletions(-) (limited to 'librecrypt_decode.c') diff --git a/librecrypt_decode.c b/librecrypt_decode.c index ee451a7..847bfea 100644 --- a/librecrypt_decode.c +++ b/librecrypt_decode.c @@ -12,6 +12,15 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, unsigned char a, b, c, d; size_t n = 0u; + /* For as many multiple of 4 input characters as available, + * it can be coded into multiples of 3 bytes (or less if + * padding is used), however we need to check that the output + * buffer is large enough. Even if `size` is 0, we need read + * all characters to ensure that they only use the valid + * characters in the encoding alphabet or that padding is + * used correct — we want to validate the string, and we do + * not support padding in the middle (it is technically + * doable, but there is no reason it would ever be used) */ for(; len >= 4u; str += 4u) { len -= 4u; @@ -44,16 +53,28 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, n++; } - if (len && strict_pad && pad) + /* If the number of input characters was not a multiple of + * four, the string is invalid if padding was required + * (`strict_pad` is ignored if `pad` is the NUL byte) */ + if (len && pad && strict_pad) goto einval; + /* Decode the remainder of the input, which shouldn't + * contain any padding since it is less than 4 bytes, + * and padding is done to multiples of 4 bytes */ switch (len) { case 0u: + /* 0 characters left over, there was nothing left, and we are done */ goto out; + case 1u: + /* 1 character left over, this is invalid as at least 2 base-64 + * characters is required to encode at least 1 byte */ goto einval; default: + /* 2 or 3 characters left over */ + a = lut[str[0u]]; b = lut[str[1u]]; if ((a | b) == 0xFFu) @@ -66,7 +87,9 @@ librecrypt_decode(void *out_buffer, size_t size, const char *ascii, size_t len, c = lut[str[2u]]; if (c == 0xFFu) { - if (!pad || str[2u] != pad) + if (!pad) + goto einval; + if (str[2u] != pad) goto einval; break; } @@ -112,29 +135,41 @@ static const unsigned char lut[256u] = { check((BINARY), sizeof(BINARY) - 1u, (ASCII PAD), sizeof(ASCII) - 1u, sizeof(ASCII PAD) - 1u) +static int check_good_padding = 1; + + static void check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_len, size_t padded_len) { char buf[16u]; size_t i, j; + /* Internal test check */ assert(binary_len <= sizeof(buf)); - assert(padded_len % 4u == 0u); - assert(padded_len / 4u == (unpadded_len + 3u) / 4u); + /* Internal test check */ + if (check_good_padding) { + assert(padded_len % 4u == 0u); + assert(padded_len / 4u == (unpadded_len + 3u) / 4u); + } assert(padded_len >= unpadded_len); + /* Test no output and without padding */ EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len); EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '\0', 1) == (ssize_t)binary_len); EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 0) == (ssize_t)binary_len); - if (padded_len == unpadded_len) { EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == (ssize_t)binary_len); } else { + /* illegal combination: padding missing but required */ errno = 0; EXPECT(librecrypt_decode(NULL, 0u, ascii, unpadded_len, lut, '=', 1) == -1); EXPECT(errno == EINVAL); + } + /* Test no output and with padding */ + if (padded_len != unpadded_len) { + /* illegal combinations: padding present no padding character defined (padded with illegal characters) */ errno = 0; EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 0) == -1); EXPECT(errno == EINVAL); @@ -143,10 +178,11 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_ EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '\0', 1) == -1); EXPECT(errno == EINVAL); } - EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 0) == (ssize_t)binary_len); - EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); + if (check_good_padding) + EXPECT(librecrypt_decode(NULL, 0u, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); + /* Test output, with and without truncation */ for (i = 0u; i < sizeof(buf); i++) { memset(buf, 99, sizeof(buf)); EXPECT(librecrypt_decode(buf, i, ascii, unpadded_len, lut, '\0', 0) == (ssize_t)binary_len); @@ -197,12 +233,14 @@ check(const char *binary, size_t binary_len, const char *ascii, size_t unpadded_ for (; j < sizeof(buf); j++) EXPECT(buf[j] == 99); - memset(buf, 99, sizeof(buf)); - EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); - j = i < binary_len ? i : binary_len; - EXPECT(!memcmp(buf, binary, j)); - for (; j < sizeof(buf); j++) - EXPECT(buf[j] == 99); + if (check_good_padding) { + memset(buf, 99, sizeof(buf)); + EXPECT(librecrypt_decode(buf, i, ascii, padded_len, lut, '=', 1) == (ssize_t)binary_len); + j = i < binary_len ? i : binary_len; + EXPECT(!memcmp(buf, binary, j)); + for (; j < sizeof(buf); j++) + EXPECT(buf[j] == 99); + } } } @@ -215,6 +253,8 @@ main(void) SET_UP_ALARM(); + /* Normal test cases */ + check_good_padding = 1; CHECK("", "", ""); CHECK("\x00", "AA", "=="); CHECK("\x00\x00", "AAA", "="); @@ -224,6 +264,14 @@ main(void) CHECK("zy[]y21 !", "enlbXXkyMSAh", ""); CHECK("{~|~}~~~\x7f\x7f", "e358fn1+fn5/fw", "=="); + /* Bad but acceptable case */ + check_good_padding = 0; + CHECK("\x00", "AA", "="); + + /* Test illegal characters, and 1 non-value character with + * 3 pad characeters (illegal since at least 2 base-64 + * characters are needed to encode at least 1 byte) */ + check_good_padding = 1; for (i = 0; i <= 1; i++) { errno = 0; EXPECT(librecrypt_decode(NULL, 0u, "AA=*", 4u, lut, '=', i) == -1); @@ -248,6 +296,42 @@ main(void) errno = 0; EXPECT(librecrypt_decode(NULL, 0u, "====", 4u, lut, '=', i) == -1); EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "A*==", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "*A==", 4u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "A*", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "*A", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "=", 1u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "==", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "=A", 2u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA*", 3u, lut, '=', i) == -1); + EXPECT(errno == EINVAL); + + errno = 0; + EXPECT(librecrypt_decode(NULL, 0u, "AA*", 3u, lut, '\0', i) == -1); + EXPECT(errno == EINVAL); } return 0; -- cgit v1.2.3-70-g09d2