Security #6902
closedbase64: off-by-three overflow in DecodeBase64()
fd47e67dc65f9111895c88fb406c938b1f857325
Description
static inline void DecodeBase64Block(uint8_t ascii[ASCII_BLOCK], uint8_t b64[B64_BLOCK])
{
ascii[0] = (uint8_t) (b64[0] << 2) | (b64[1] >> 4);
ascii[1] = (uint8_t) (b64[1] << 4) | (b64[2] >> 2);
ascii[2] = (uint8_t) (b64[2] << 6) | (b64[3]);
}
Base64Ecode DecodeBase64(uint8_t *dest, uint32_t dest_size, const uint8_t *src, uint32_t len,
uint32_t *consumed_bytes, uint32_t *decoded_bytes, Base64Mode mode)
{
int val;
uint32_t padding = 0, bbidx = 0, sp = 0, leading_sp = 0;
uint8_t *dptr = dest;
uint8_t b64[B64_BLOCK] = { 0,0,0,0 };
bool valid = true;
Base64Ecode ecode = BASE64_ECODE_OK;
*decoded_bytes = 0;
for (uint32_t i = 0; i < len; i++) {
val = GetBase64Value(src[i]);
if (val < 0) {
if (mode == BASE64_MODE_RFC2045 && src[i] != '=') {
if (bbidx == 0) {
leading_sp++;
}
sp++;
continue;
}
if (src[i] != '=') {
valid = false;
ecode = BASE64_ECODE_ERR;
if (mode == BASE64_MODE_STRICT) {
*decoded_bytes = 0;
}
break;
}
padding++;
}
b64[bbidx++] = (val > 0 ? (uint8_t)val : 0);
if (bbidx == B64_BLOCK) {
[1] uint32_t numDecoded_blk = ASCII_BLOCK - (padding < B64_BLOCK ? padding : ASCII_BLOCK);
[2] if (dest_size < *decoded_bytes + numDecoded_blk) {
SCLogDebug("Destination buffer full");
ecode = BASE64_ECODE_BUF;
break;
}
[3] DecodeBase64Block(dptr, b64);
dptr += numDecoded_blk;
*decoded_bytes += numDecoded_blk;
bbidx = 0;
padding = 0;
*consumed_bytes += B64_BLOCK + sp;
sp = 0;
leading_sp = 0;
memset(&b64, 0, sizeof(b64));
}
}
...
if (valid && bbidx > 0 && (mode != BASE64_MODE_RFC2045)) {
*decoded_bytes += ASCII_BLOCK - (B64_BLOCK - bbidx);
[4] DecodeBase64Block(dptr, b64);
}
..
}
Consider the situation when destination buffer is full, ie 'dest_size' is equal to '*decoded_bytes'.
1. - in case 'padding' is equal to B64_BLOCK (4), 'numDecoded_blk' will be set to 0
2. - we will pass this check, as 'dest_size' is equal to '*decoded_bytes'
3. - 3 bytes will be written past the end of buffer 'dptr'
4. - similar issue
How to verify:
1) get source code
$ git clone https://github.com/OISF/suricata
2) Edit src/util-base64.c and change B64DecodeCompleteString() test.
Change string "SGVsbG8gV29ybGR6" to "SGVsbG8gV29ybGR6===="
3) build
$ export CFLAGS="-ggdb3 -Werror -Wchar-subscripts -fno-strict-aliasing -fstack-protector-all -fsanitize=address -fno-omit-frame-pointer -Wno-unused-parameter -Wno-unused-function"
$ ./autoconf.sh
$ ./configure --enable-unittests
$ make
4) run
$ ./src/suricata -U B64DecodeCompleteString -u
ASAN LOG:
==77257==ERROR: AddressSanitizer: dynamic-stack-buffer-overflow on address 0x7ffcc360882c at pc 0x55907c573699 bp 0x7ffcc36086c0 sp 0x7ffcc36086b0
WRITE of size 1 at 0x7ffcc360882c thread T0 (Suricata-Main)
#0 0x55907c573698 in DecodeBase64Block suricata/src/util-base64.c:91
#1 0x55907c573c75 in DecodeBase64 suricata/src/util-base64.c:161
#2 0x55907c574378 in B64DecodeCompleteString suricata/src/util-base64.c:241
#3 0x55907bc7a22c in UtRunTests suricata/src/util-unittest.c:212
#4 0x55907c43fffb in RunUnittests suricata/src/runmode-unittests.c:286
#5 0x55907bbd6460 in StartInternalRunMode suricata/src/suricata.c:2335
#6 0x55907bbd8fc6 in SuricataMain suricata/src/suricata.c:2901
#7 0x55907bbc98eb in main suricata/src/main.c:22
#8 0x7f3b31a29d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#9 0x7f3b31a29e3f in __libc_start_main_impl ../csu/libc-start.c:392
#10 0x55907bbc97f4 in _start (suricata/src/.libs/suricata+0x3107f4)