mime/base64: decode cleanups and simplification

Addresses edge case: > 4 bytes at the end of the input with 2 or more
spaces.

Changes length type for remainder processing to allow for much longer
lines, which can happen in practice.

Adds a series of debug validation checks with real error handling
as well, to assist the fuzzer to find more edge cases.
pull/7488/head
Victor Julien 3 years ago
parent 92cd95b416
commit 30e47b2171

@ -1218,58 +1218,57 @@ static int ProcessDecodedDataChunk(const uint8_t *chunk, uint32_t len,
* \param state The current parser state
* \param force Flag indicating whether decoding should always occur
*
* \return Number of bytes pulled from the current buffer
* \return Number of bytes consumed from `buf`
*/
static uint8_t ProcessBase64Remainder(
const uint8_t *buf, uint8_t len, MimeDecParseState *state, int force)
const uint8_t *buf, const uint32_t len, MimeDecParseState *state, int force)
{
uint32_t ret;
uint8_t remainder = 0;
uint32_t remdec = 0;
uint32_t consumed_bytes = 0;
uint8_t buf_consumed = 0; /* consumed bytes from 'buf' */
uint32_t cnt = 0;
uint8_t block[B64_BLOCK];
/* Fill in block with first few bytes of current line */
remainder = B64_BLOCK - state->bvr_len;
remainder = remainder < len ? remainder : len;
if (buf) {
uint8_t tmp[B64_BLOCK];
uint32_t rem = 0;
for (int i = 0; i < state->bvr_len; i++) {
/* Special case of SP in remainder */
if (state->bvremain[i] != ' ') {
tmp[cnt++] = state->bvremain[i];
} else {
rem++;
}
/* should be impossible, but lets be defensive */
DEBUG_VALIDATE_BUG_ON(state->bvr_len > B64_BLOCK);
if (state->bvr_len > B64_BLOCK) {
state->bvr_len = 0;
return 0;
}
/* Strip spaces in remainder */
for (uint8_t i = 0; i < state->bvr_len; i++) {
if (state->bvremain[i] != ' ') {
block[cnt++] = state->bvremain[i];
}
if (cnt != 4) {
/* Special case where the buf where we take extra bytes from contains SP */
for (int i = 0; i < len && cnt < 4; i++) {
if (buf[i] != ' ') {
tmp[cnt++] = buf[i];
} else {
rem++;
}
}
/* if we don't have 4 bytes see if we can fill it from `buf` */
if (buf && len > 0 && cnt != B64_BLOCK) {
for (uint32_t i = 0; i < len && cnt < B64_BLOCK; i++) {
if (buf[i] != ' ') {
block[cnt++] = buf[i];
}
buf_consumed++;
}
if (cnt != 0) {
memcpy(state->bvremain, block, cnt);
}
memcpy(state->bvremain, tmp, cnt);
state->bvr_len += remainder;
remainder += rem;
state->bvr_len = cnt;
}
/* If data chunk buffer will be full, then clear it now */
if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
/* Invoke pre-processor and callback */
ret = ProcessDecodedDataChunk(state->data_chunk, state->data_chunk_len,
state);
uint32_t ret = ProcessDecodedDataChunk(state->data_chunk, state->data_chunk_len, state);
if (ret != MIME_DEC_OK) {
SCLogDebug("Error: ProcessDecodedDataChunk() function failed");
}
}
if (state->bvr_len == B64_BLOCK || force) {
uint32_t avail_space = DATA_CHUNK_SIZE - state->data_chunk_len;
uint32_t consumed_bytes = 0;
uint32_t remdec = 0;
const uint32_t avail_space = DATA_CHUNK_SIZE - state->data_chunk_len;
Base64Ecode code = DecodeBase64(state->data_chunk + state->data_chunk_len, avail_space,
state->bvremain, state->bvr_len, &consumed_bytes, &remdec, BASE64_MODE_RFC2045);
if (remdec > 0 && (code == BASE64_ECODE_OK || code == BASE64_ECODE_BUF)) {
@ -1283,8 +1282,8 @@ static uint8_t ProcessBase64Remainder(
if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
/* Invoke pre-processor and callback */
ret = ProcessDecodedDataChunk(state->data_chunk,
state->data_chunk_len, state);
uint32_t ret =
ProcessDecodedDataChunk(state->data_chunk, state->data_chunk_len, state);
if (ret != MIME_DEC_OK) {
SCLogDebug("Error: ProcessDecodedDataChunk() function "
"failed");
@ -1302,7 +1301,8 @@ static uint8_t ProcessBase64Remainder(
state->bvr_len = 0;
}
return remainder;
DEBUG_VALIDATE_BUG_ON(buf_consumed > len);
return buf_consumed;
}
/**
@ -1319,9 +1319,9 @@ static int ProcessBase64BodyLine(const uint8_t *buf, uint32_t len,
MimeDecParseState *state)
{
int ret = MIME_DEC_OK;
uint8_t rem1 = 0;
uint32_t numDecoded, remaining, offset;
/* Track long line */
uint32_t numDecoded, remaining = len, offset = 0;
/* Track long line TODO should we count space padding too? */
if (len > MAX_ENC_LINE_LEN) {
state->stack->top->data->anomaly_flags |= ANOM_LONG_ENC_LINE;
state->msg->anomaly_flags |= ANOM_LONG_ENC_LINE;
@ -1332,30 +1332,34 @@ static int ProcessBase64BodyLine(const uint8_t *buf, uint32_t len,
if (state->bvr_len + len < B64_BLOCK) {
memcpy(state->bvremain + state->bvr_len, buf, len);
state->bvr_len += len;
len = 0;
return MIME_DEC_OK;
}
/* First process remaining from previous line */
if (state->bvr_len > 0) {
/* Process remainder and return number of bytes pulled from current buffer */
rem1 = ProcessBase64Remainder(buf, (uint8_t)len, state, 0);
int32_t remainder_b64 = len - rem1;
if (remainder_b64 < B64_BLOCK) {
memcpy(state->bvremain, buf + rem1, remainder_b64);
state->bvr_len += remainder_b64;
return ret;
uint32_t consumed = ProcessBase64Remainder(buf, len, state, 0);
DEBUG_VALIDATE_BUG_ON(consumed > len);
if (consumed > len)
return MIME_DEC_ERR_PARSE;
uint32_t left = len - consumed;
if (left < B64_BLOCK) {
memcpy(state->bvremain, buf + consumed, left);
state->bvr_len = left;
return MIME_DEC_OK;
}
remaining -= consumed;
offset = consumed;
}
remaining = len - rem1;
offset = rem1;
while (remaining > 0 && remaining >= B64_BLOCK) {
uint32_t consumed_bytes = 0;
uint32_t avail_space = DATA_CHUNK_SIZE - state->data_chunk_len;
Base64Ecode code = DecodeBase64(state->data_chunk + state->data_chunk_len, avail_space,
buf + offset, remaining, &consumed_bytes, &numDecoded, BASE64_MODE_RFC2045);
DEBUG_VALIDATE_BUG_ON(consumed_bytes > remaining);
if (consumed_bytes > remaining)
return MIME_DEC_ERR_PARSE;
uint32_t leftover_bytes = remaining - consumed_bytes;
if (numDecoded > 0 && (code == BASE64_ECODE_OK || code == BASE64_ECODE_BUF)) {
@ -1382,29 +1386,35 @@ static int ProcessBase64BodyLine(const uint8_t *buf, uint32_t len,
state->stack->top->data->anomaly_flags |= ANOM_INVALID_BASE64;
state->msg->anomaly_flags |= ANOM_INVALID_BASE64;
SCLogDebug("Error: DecodeBase64() function failed");
ret = MIME_DEC_ERR_DATA;
break;
return MIME_DEC_ERR_DATA;
}
if (leftover_bytes < B64_BLOCK) {
/* corner case: multiples spaces in the last data, leading it to exceed the block
* size. We strip of spaces this while storing it in bvremain */
if (consumed_bytes == 0 && leftover_bytes > B64_BLOCK) {
DEBUG_VALIDATE_BUG_ON(state->bvr_len != 0);
for (uint32_t i = 0; i < leftover_bytes; i++) {
if (buf[offset] != ' ') {
DEBUG_VALIDATE_BUG_ON(state->bvr_len >= B64_BLOCK);
if (state->bvr_len >= B64_BLOCK)
return MIME_DEC_ERR_DATA;
state->bvremain[state->bvr_len++] = buf[offset];
}
offset++;
}
return MIME_DEC_OK;
} else if (leftover_bytes > 0 && leftover_bytes <= B64_BLOCK) {
/* If remaining is 4 by this time, we encountered spaces during processing */
DEBUG_VALIDATE_BUG_ON(state->bvr_len != 0);
memcpy(state->bvremain, buf + offset + consumed_bytes, leftover_bytes);
state->bvr_len = leftover_bytes;
return MIME_DEC_OK;
}
/* Update counts */
remaining -= consumed_bytes;
remaining = leftover_bytes;
offset += consumed_bytes;
/* If remaining is 4 by this time, it's likely due to error/spaces during processing */
if (remaining == 4) {
memcpy(state->bvremain, buf + offset, remaining);
state->bvr_len = remaining;
break;
}
if ((remaining > 0 && remaining < B64_BLOCK) ||
(remaining > 0 && (remaining < B64_BLOCK) && consumed_bytes < remaining)) {
memcpy(state->bvremain, buf + offset, remaining);
state->bvr_len = remaining;
break;
}
}
return ret;
}

Loading…
Cancel
Save