]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
base64: make decoder handle decoded data space constraints
authorShivani Bhardwaj <shivanib134@gmail.com>
Thu, 2 Jun 2022 15:20:07 +0000 (20:50 +0530)
committerVictor Julien <vjulien@oisf.net>
Tue, 14 Jun 2022 08:10:19 +0000 (10:10 +0200)
So far, it was the job of caller to send the bae64 decoder a perfect
block of data and take care of the destination buffer (decoded data)
size. Now, make it the decoder's job to take care of any space
constraints that the destination buffer may have and return accordingly.

Also, handle space characters in base64 encoded data as per RFC 2045.

Update MIME parser accordingly to handle the base64 data.

Ticket: 5315
(cherry picked from commit 5b2761977871a94d559a9ba3b026593bb1ffd68b)

src/datasets.c
src/detect-base64-decode.c
src/util-base64.c
src/util-base64.h
src/util-decode-mime.c

index 9233695a2c57ea0e69b600b6c91b5339a91b0af4..9d5a162dd18eb6b4716a47840a926097de2e673b 100644 (file)
@@ -319,13 +319,14 @@ static int DatasetLoadString(Dataset *set)
             SCLogDebug("line: '%s'", line);
 
             uint8_t decoded[strlen(line)];
-            uint32_t len =
-                    DecodeBase64(decoded, (const uint8_t *)line, strlen(line), BASE64_MODE_STRICT);
-            if (len == 0)
+            uint32_t consumed = 0, num_decoded = 0;
+            Base64Ecode code = DecodeBase64(decoded, strlen(line), (const uint8_t *)line,
+                    strlen(line), &consumed, &num_decoded, BASE64_MODE_STRICT);
+            if (code == BASE64_ECODE_ERR)
                 FatalError(SC_ERR_FATAL, "bad base64 encoding %s/%s",
                         set->name, set->load);
 
-            if (DatasetAdd(set, (const uint8_t *)decoded, len) < 0)
+            if (DatasetAdd(set, (const uint8_t *)decoded, num_decoded) < 0)
                 FatalError(SC_ERR_FATAL, "dataset data add failed %s/%s",
                         set->name, set->load);
             cnt++;
@@ -336,9 +337,10 @@ static int DatasetLoadString(Dataset *set)
             *r = '\0';
 
             uint8_t decoded[strlen(line)];
-            uint32_t len =
-                    DecodeBase64(decoded, (const uint8_t *)line, strlen(line), BASE64_MODE_STRICT);
-            if (len == 0)
+            uint32_t consumed = 0, num_decoded = 0;
+            Base64Ecode code = DecodeBase64(decoded, strlen(line), (const uint8_t *)line,
+                    strlen(line), &consumed, &num_decoded, BASE64_MODE_STRICT);
+            if (code == BASE64_ECODE_ERR)
                 FatalError(SC_ERR_FATAL, "bad base64 encoding %s/%s",
                         set->name, set->load);
 
@@ -350,7 +352,7 @@ static int DatasetLoadString(Dataset *set)
                 FatalError(SC_ERR_FATAL, "die: bad rep");
             SCLogDebug("rep %u", rep.value);
 
-            if (DatasetAddwRep(set, (const uint8_t *)decoded, len, &rep) < 0)
+            if (DatasetAddwRep(set, (const uint8_t *)decoded, num_decoded, &rep) < 0)
                 FatalError(SC_ERR_FATAL, "dataset data add failed %s/%s",
                         set->name, set->load);
             cnt++;
@@ -1025,13 +1027,14 @@ int DatasetAddSerialized(Dataset *set, const char *string)
     switch (set->type) {
         case DATASET_TYPE_STRING: {
             uint8_t decoded[strlen(string)];
-            uint32_t len = DecodeBase64(
-                    decoded, (const uint8_t *)string, strlen(string), BASE64_MODE_STRICT);
-            if (len == 0) {
+            uint32_t consumed = 0, num_decoded = 0;
+            Base64Ecode code = DecodeBase64(decoded, strlen(string), (const uint8_t *)string,
+                    strlen(string), &consumed, &num_decoded, BASE64_MODE_STRICT);
+            if (code == BASE64_ECODE_ERR) {
                 return -2;
             }
 
-            return DatasetAddString(set, decoded, len);
+            return DatasetAddString(set, decoded, num_decoded);
         }
         case DATASET_TYPE_MD5: {
             if (strlen(string) != 32)
@@ -1107,13 +1110,14 @@ int DatasetRemoveSerialized(Dataset *set, const char *string)
     switch (set->type) {
         case DATASET_TYPE_STRING: {
             uint8_t decoded[strlen(string)];
-            uint32_t len = DecodeBase64(
-                    decoded, (const uint8_t *)string, strlen(string), BASE64_MODE_STRICT);
-            if (len == 0) {
+            uint32_t consumed = 0, num_decoded = 0;
+            Base64Ecode code = DecodeBase64(decoded, strlen(string), (const uint8_t *)string,
+                    strlen(string), &consumed, &num_decoded, BASE64_MODE_STRICT);
+            if (code == BASE64_ECODE_ERR) {
                 return -2;
             }
 
-            return DatasetRemoveString(set, decoded, len);
+            return DatasetRemoveString(set, decoded, num_decoded);
         }
         case DATASET_TYPE_MD5: {
             if (strlen(string) != 32)
index 020d00537cb2495bf94e68d6a33745635c598111..b4684ba56ebed800ad90d1a53553906699ce3130 100644 (file)
@@ -84,8 +84,10 @@ int DetectBase64DecodeDoMatch(DetectEngineThreadCtx *det_ctx, const Signature *s
     PrintRawDataFp(stdout, payload, decode_len);
 #endif
 
-    det_ctx->base64_decoded_len =
-            DecodeBase64(det_ctx->base64_decoded, payload, decode_len, BASE64_MODE_RELAX);
+    uint32_t consumed = 0, num_decoded = 0;
+    (void)DecodeBase64(det_ctx->base64_decoded, det_ctx->base64_decoded_len_max, payload,
+            decode_len, &consumed, &num_decoded, BASE64_MODE_RELAX);
+    det_ctx->base64_decoded_len = num_decoded;
     SCLogDebug("Decoded %d bytes from base64 data.",
         det_ctx->base64_decoded_len);
 #if 0
index 5c6e0ac6e20544a7803929ae055110ebd38da4f2..108903ecfa1dadc4e0c28615205978473f42150c 100644 (file)
@@ -81,35 +81,45 @@ static inline void DecodeBase64Block(uint8_t ascii[ASCII_BLOCK], uint8_t b64[B64
  * \brief Decodes a base64-encoded string buffer into an ascii-encoded byte buffer
  *
  * \param dest The destination byte buffer
+ * \param dest_size The destination byte buffer size
  * \param src The source string
  * \param len The length of the source string
- * \param strict If set file on invalid byte, otherwise return what has been
- *    decoded.
+ * \param consumed_bytes The bytes that were actually processed/consumed
+ * \param decoded_bytes The bytes that were decoded
+ * \param mode The mode in which decoding should happen
  *
- * \return Number of bytes decoded, or 0 if no data is decoded or it fails
+ * \return Error code indicating success or failures with parsing
  */
-uint32_t DecodeBase64(uint8_t *dest, const uint8_t *src, uint32_t len, Base64Mode mode)
+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, numDecoded = 0, bbidx = 0, valid = 1, i;
+    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;
     /* Traverse through each alpha-numeric letter in the source array */
-    for(i = 0; i < len && src[i] != 0; i++) {
-
+    for (uint32_t i = 0; i < len && src[i] != 0; i++) {
         /* Get decimal representation */
         val = GetBase64Value(src[i]);
         if (val < 0) {
-            if (mode == BASE64_MODE_RFC2045 && src[i] == ' ') {
+            if ((mode == BASE64_MODE_RFC2045) && (src[i] == ' ')) {
+                if (bbidx == 0) {
+                    /* Special case where last block of data has a leading space */
+                    leading_sp++;
+                }
+                sp++;
                 continue;
             }
             /* Invalid character found, so decoding fails */
             if (src[i] != '=') {
-                valid = 0;
+                valid = false;
                 if (mode != BASE64_MODE_RELAX) {
-                    numDecoded = 0;
+                    *decoded_bytes = 0;
                 }
+                ecode = BASE64_ECODE_ERR;
                 break;
             }
             padding++;
@@ -123,55 +133,156 @@ uint32_t DecodeBase64(uint8_t *dest, const uint8_t *src, uint32_t len, Base64Mod
         if (bbidx == B64_BLOCK) {
 
             /* For every 4 bytes, add 3 bytes but deduct the '=' padded blocks */
-            numDecoded += ASCII_BLOCK - (padding < B64_BLOCK ?
-                    padding : ASCII_BLOCK);
+            uint32_t numDecoded_blk = ASCII_BLOCK - (padding < B64_BLOCK ? padding : ASCII_BLOCK);
+            if (dest_size < *decoded_bytes + numDecoded_blk) {
+                SCLogDebug("Destination buffer full");
+                ecode = BASE64_ECODE_BUF;
+                break;
+            }
 
             /* Decode base-64 block into ascii block and move pointer */
             DecodeBase64Block(dptr, b64);
             dptr += ASCII_BLOCK;
-
+            *decoded_bytes += numDecoded_blk;
             /* Reset base-64 block and index */
             bbidx = 0;
             padding = 0;
+            *consumed_bytes += B64_BLOCK + sp;
+            sp = 0;
+            leading_sp = 0;
+            memset(&b64, 0, sizeof(b64));
         }
     }
-
     /* Finish remaining b64 bytes by padding */
-    if (valid && bbidx > 0) {
-
+    if (valid && bbidx > 0 && (mode != BASE64_MODE_RFC2045)) {
         /* Decode remaining */
-        numDecoded += ASCII_BLOCK - (B64_BLOCK - bbidx);
+        *decoded_bytes += ASCII_BLOCK - (B64_BLOCK - bbidx);
         DecodeBase64Block(dptr, b64);
     }
 
-    if (numDecoded == 0) {
+    if (*decoded_bytes == 0) {
         SCLogDebug("base64 decoding failed");
     }
 
-    return numDecoded;
+    *consumed_bytes += leading_sp;
+    return ecode;
 }
 
 #ifdef UNITTESTS
+static int B64DecodeCompleteString(void)
+{
+    /*
+     * SGVsbG8gV29ybGR6 : Hello Worldz
+     * */
+    const char *src = "SGVsbG8gV29ybGR6";
+    const char *fin_str = "Hello Worldz";
+    uint32_t consumed_bytes = 0, num_decoded = 0;
+    uint8_t dst[strlen(fin_str)];
+    Base64Ecode code = DecodeBase64(dst, strlen(fin_str), (const uint8_t *)src, strlen(src),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
+    FAIL_IF(code != BASE64_ECODE_OK);
+    FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0);
+    FAIL_IF(num_decoded != 12);
+    FAIL_IF(consumed_bytes != strlen(src));
+    PASS;
+}
+
+static int B64DecodeInCompleteString(void)
+{
+    /*
+     * SGVsbG8gV29ybGR6 : Hello Worldz
+     * */
+    const char *src = "SGVsbG8gV29ybGR";
+    const char *fin_str = "Hello Wor"; // bc it'll error out on last 3 bytes
+    uint32_t consumed_bytes = 0, num_decoded = 0;
+    uint8_t dst[strlen(fin_str)];
+    Base64Ecode code = DecodeBase64(dst, strlen(fin_str), (const uint8_t *)src, strlen(src),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
+    FAIL_IF(code != BASE64_ECODE_OK);
+    FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0);
+    FAIL_IF(num_decoded != 9);
+    FAIL_IF(consumed_bytes == strlen(src));
+    PASS;
+}
+
+static int B64DecodeCompleteStringWSp(void)
+{
+    /*
+     * SGVsbG8gV29ybGQ= : Hello World
+     * */
+
+    const char *src = "SGVs bG8 gV29y bGQ=";
+    const char *fin_str = "Hello World";
+    uint8_t dst[strlen(fin_str) + 1]; // 1 for the padding byte
+    uint32_t consumed_bytes = 0, num_decoded = 0;
+    Base64Ecode code = DecodeBase64(dst, strlen(fin_str), (const uint8_t *)src, strlen(src),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
+    FAIL_IF(code != BASE64_ECODE_OK);
+    FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0);
+    FAIL_IF(num_decoded != 11);
+    FAIL_IF(consumed_bytes != strlen(src));
+    PASS;
+}
+
+static int B64DecodeInCompleteStringWSp(void)
+{
+    /*
+     * SGVsbG8gV29ybGQ= : Hello World
+     * Special handling for this case (sp in remainder) done in ProcessBase64Remainder
+     * */
+
+    const char *src = "SGVs bG8 gV29y bGQ";
+    const char *fin_str = "Hello Wor";
+    uint32_t consumed_bytes = 0, num_decoded = 0;
+    uint8_t dst[strlen(fin_str)];
+    Base64Ecode code = DecodeBase64(dst, strlen(fin_str), (const uint8_t *)src, strlen(src),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
+    FAIL_IF(code != BASE64_ECODE_OK);
+    FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0);
+    FAIL_IF(num_decoded != 9); // bc we don't put padding in RFC2045 mode
+    FAIL_IF(consumed_bytes != strlen(src) - 3);
+    PASS;
+}
 
-static int DecodeString(void)
+static int B64DecodeStringBiggerThanBuffer(void)
 {
     /*
-     * SGV sbG8= : Hello
      * SGVsbG8gV29ybGQ= : Hello World
      * */
 
     const char *src = "SGVs bG8 gV29y bGQ=";
-    uint8_t *dst = SCMalloc(sizeof(src) * 30);
-    int res = DecodeBase64(dst, (const uint8_t *)src, 30, 1);
-    printf("%d\n", res);
-    printf("dst str = \"%s\"", (const char *)dst);
-    FAIL_IF(res <= 0);
-    SCFree(dst);
+    const char *fin_str = "Hello Wor";
+    uint32_t consumed_bytes = 0, num_decoded = 0;
+    uint8_t dst[strlen(fin_str)];
+    Base64Ecode code = DecodeBase64(dst, strlen(fin_str), (const uint8_t *)src, strlen(src),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
+    FAIL_IF(code != BASE64_ECODE_BUF);
+    FAIL_IF(memcmp(dst, fin_str, strlen(fin_str)) != 0);
+    FAIL_IF(num_decoded != 9); // dest buf is 10, so 9 got consumed
+    FAIL_IF(consumed_bytes != 15);
+    PASS;
+}
+
+static int B64DecodeStringEndingSpaces(void)
+{
+    const char *src = "0YPhA d H";
+    uint32_t consumed_bytes = 0, num_decoded = 0;
+    uint8_t dst[10];
+    Base64Ecode code = DecodeBase64(dst, sizeof(dst), (const uint8_t *)src, strlen(src),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
+    FAIL_IF(code != BASE64_ECODE_OK);
+    FAIL_IF(num_decoded != 3);
+    FAIL_IF(consumed_bytes != 4);
     PASS;
 }
 
 void Base64RegisterTests(void)
 {
-    UtRegisterTest("DecodeString", DecodeString);
+    UtRegisterTest("B64DecodeCompleteStringWSp", B64DecodeCompleteStringWSp);
+    UtRegisterTest("B64DecodeInCompleteStringWSp", B64DecodeInCompleteStringWSp);
+    UtRegisterTest("B64DecodeCompleteString", B64DecodeCompleteString);
+    UtRegisterTest("B64DecodeInCompleteString", B64DecodeInCompleteString);
+    UtRegisterTest("B64DecodeStringBiggerThanBuffer", B64DecodeStringBiggerThanBuffer);
+    UtRegisterTest("B64DecodeStringEndingSpaces", B64DecodeStringEndingSpaces);
 }
 #endif
index c57cc28766f542105b3dad7c1388f3b4c3330807..ac78d0806dc60b5a23d4b0b654dbb10714faa67f 100644 (file)
@@ -61,7 +61,8 @@ typedef enum {
 } Base64Ecode;
 
 /* Function prototypes */
-uint32_t DecodeBase64(uint8_t *dest, const uint8_t *src, uint32_t len, Base64Mode mode);
+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);
 
 #endif
 
index 8ece5522a5c01f125a353caf41056b1694773787..8733b433cf4d4624bcc46a87d62d98e5876571a5 100644 (file)
@@ -31,6 +31,8 @@
 #include "util-unittest.h"
 #include "util-memcmp.h"
 #include "util-print.h"
+#include "util-validate.h"
+#include "rust.h"
 
 /* Character constants */
 #ifndef CR
@@ -1210,18 +1212,39 @@ static uint8_t ProcessBase64Remainder(const uint8_t *buf, uint32_t len,
         MimeDecParseState *state, int force)
 {
     uint32_t ret;
-    uint8_t remainder = 0, remdec = 0;
-
-    SCLogDebug("Base64 line remainder found: %u", state->bvr_len);
+    uint8_t remainder = 0;
+    uint32_t remdec = 0;
+    uint32_t consumed_bytes = 0;
+    uint32_t cnt = 0;
 
     /* Fill in block with first few bytes of current line */
     remainder = B64_BLOCK - state->bvr_len;
     remainder = remainder < len ? remainder : len;
-    if (remainder && buf) {
-        memcpy(state->bvremain + state->bvr_len, buf, remainder);
+    if (buf) {
+        uint8_t tmp[B64_BLOCK];
+        uint32_t rem = 0;
+        for (uint8_t i = 0; i < state->bvr_len; i++) {
+            /* Special case of SP in remainder */
+            if (state->bvremain[i] != ' ') {
+                tmp[cnt++] = state->bvremain[i];
+            } else {
+                rem++;
+            }
+        }
+        if (cnt != 4) {
+            /* Special case where the buf where we take extra bytes from contains SP */
+            for (uint32_t i = 0; i < len && cnt < 4; i++) {
+                if (buf[i] != ' ') {
+                    tmp[cnt++] = buf[i];
+                } else {
+                    rem++;
+                }
+            }
+        }
+        memcpy(state->bvremain, tmp, cnt);
+        state->bvr_len += remainder;
+        remainder += rem;
     }
-    state->bvr_len += remainder;
-
     /* If data chunk buffer will be full, then clear it now */
     if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
 
@@ -1233,18 +1256,17 @@ static uint8_t ProcessBase64Remainder(const uint8_t *buf, uint32_t len,
         }
     }
 
-    /* Only decode if divisible by 4 */
     if (state->bvr_len == B64_BLOCK || force) {
-        remdec = DecodeBase64(state->data_chunk + state->data_chunk_len, state->bvremain,
-                state->bvr_len, BASE64_MODE_RFC2045);
-        if (remdec > 0) {
+        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)) {
 
             /* Track decoded length */
             state->stack->top->data->decoded_body_len += remdec;
 
             /* Update length */
             state->data_chunk_len += remdec;
-
             /* If data chunk buffer is now full, then clear */
             if (DATA_CHUNK_SIZE - state->data_chunk_len < ASCII_BLOCK) {
 
@@ -1256,7 +1278,7 @@ static uint8_t ProcessBase64Remainder(const uint8_t *buf, uint32_t len,
                             "failed");
                 }
             }
-        } else {
+        } else if (code == BASE64_ECODE_ERR) {
             /* Track failed base64 */
             state->stack->top->data->anomaly_flags |= ANOM_INVALID_BASE64;
             state->msg->anomaly_flags |= ANOM_INVALID_BASE64;
@@ -1285,9 +1307,8 @@ static int ProcessBase64BodyLine(const uint8_t *buf, uint32_t len,
         MimeDecParseState *state)
 {
     int ret = MIME_DEC_OK;
-    uint8_t rem1 = 0, rem2 = 0;
-    uint32_t numDecoded, remaining, offset, avail, tobuf;
-
+    uint8_t rem1 = 0;
+    uint32_t numDecoded, remaining, offset;
     /* Track long line */
     if (len > MAX_ENC_LINE_LEN) {
         state->stack->top->data->anomaly_flags |= ANOM_LONG_ENC_LINE;
@@ -1296,89 +1317,83 @@ static int ProcessBase64BodyLine(const uint8_t *buf, uint32_t len,
                 len, MAX_ENC_LINE_LEN);
     }
 
+    if (state->bvr_len + len < B64_BLOCK) {
+        memcpy(state->bvremain + state->bvr_len, buf, len);
+        state->bvr_len += len;
+        len = 0;
+    }
+
     /* First process remaining from previous line */
     if (state->bvr_len > 0) {
-
-        SCLogDebug("Base64 line remainder found: %u", state->bvr_len);
-
         /* Process remainder and return number of bytes pulled from current buffer */
-        rem1 = ProcessBase64Remainder(buf, len, state, 0);
+        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;
+        }
     }
 
-    /* No error and at least some more data needs to be decoded */
-    if ((int) (len - rem1) > 0) {
-
-        /* Determine whether we need to save a remainder if not divisible by 4 */
-        rem2 = (len - rem1) % B64_BLOCK;
-        if (rem2 > 0) {
-
-            SCLogDebug("Base64 saving remainder: %u", rem2);
+    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);
 
-            memcpy(state->bvremain, buf + (len - rem2), rem2);
-            state->bvr_len = rem2;
-        }
-
-        /* Process remaining in loop in case buffer fills up */
-        remaining = len - rem1 - rem2;
-        offset = rem1;
-        while (remaining > 0) {
+        DEBUG_VALIDATE_BUG_ON(consumed_bytes > remaining);
 
-            /* Determine amount to add to buffer */
-            avail = (DATA_CHUNK_SIZE - state->data_chunk_len) * B64_BLOCK / ASCII_BLOCK;
-            tobuf = avail > remaining ? remaining : avail;
-            while (tobuf % 4 != 0) {
-                tobuf--;
-            }
+        uint32_t leftover_bytes = remaining - consumed_bytes;
+        if (numDecoded > 0 && (code == BASE64_ECODE_OK || code == BASE64_ECODE_BUF)) {
+            /* Track decoded length */
+            state->stack->top->data->decoded_body_len += numDecoded;
+            /* Update length */
+            state->data_chunk_len += numDecoded;
 
-            if (tobuf < B64_BLOCK) {
-                SCLogDebug("Error: Invalid state for decoding base-64 block");
+            if ((int)(DATA_CHUNK_SIZE - state->data_chunk_len) < 0) {
+                SCLogDebug("Error: Invalid Chunk length: %u", state->data_chunk_len);
                 return MIME_DEC_ERR_PARSE;
             }
-
-            SCLogDebug("Decoding: %u", len - rem1 - rem2);
-
-            numDecoded = DecodeBase64(state->data_chunk + state->data_chunk_len, buf + offset,
-                    tobuf, BASE64_MODE_RFC2045);
-            if (numDecoded > 0) {
-
-                /* Track decoded length */
-                state->stack->top->data->decoded_body_len += numDecoded;
-
-                /* Update length */
-                state->data_chunk_len += numDecoded;
-
-                if ((int) (DATA_CHUNK_SIZE - state->data_chunk_len) < 0) {
-                    SCLogDebug("Error: Invalid Chunk length: %u",
-                            state->data_chunk_len);
-                    ret = MIME_DEC_ERR_PARSE;
+            /* If buffer full, then invoke callback */
+            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);
+                if (ret != MIME_DEC_OK) {
+                    SCLogDebug("Error: ProcessDecodedDataChunk() function failed");
                     break;
                 }
-
-                /* If buffer full, then invoke callback */
-                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);
-                    if (ret != MIME_DEC_OK) {
-                        SCLogDebug("Error: ProcessDecodedDataChunk() "
-                                "function failed");
-                    }
-                }
-            } else {
-                /* Track failed base64 */
-                state->stack->top->data->anomaly_flags |= ANOM_INVALID_BASE64;
-                state->msg->anomaly_flags |= ANOM_INVALID_BASE64;
-                SCLogDebug("Error: DecodeBase64() function failed");
-                PrintChars(SC_LOG_DEBUG, "Base64 failed string", buf + offset, tobuf);
             }
-
-            /* Update counts */
-            remaining -= tobuf;
-            offset += tobuf;
+        } else if (code == BASE64_ECODE_ERR) {
+            /* Track failed base64 */
+            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;
+        }
+        if (leftover_bytes < B64_BLOCK) {
+            memcpy(state->bvremain, buf + offset + consumed_bytes, leftover_bytes);
+            state->bvr_len = leftover_bytes;
+            return MIME_DEC_OK;
+        }
+        /* Update counts */
+        remaining -= consumed_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;
 }
 
@@ -2986,8 +3001,8 @@ static int MimeDecParseFullMsgTest02(void)
     MimeDecFreeEntity(entity);
 
     if (expected_count != line_count) {
-        SCLogInfo("Warning: Line count is invalid: expected - %d actual - %d",
-                expected_count, line_count);
+        SCLogInfo("Warning: Line count is invalid: expected - %d actual - %d", expected_count,
+                line_count);
         return 0;
     }
 
@@ -2997,6 +3012,7 @@ static int MimeDecParseFullMsgTest02(void)
 static int MimeBase64DecodeTest01(void)
 {
     int ret = 0;
+    uint32_t consumed_bytes = 0, num_decoded = 0;
 
     const char *msg = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890@"
             "#$%^&*()-=_+,./;'[]<>?:";
@@ -3007,7 +3023,8 @@ static int MimeBase64DecodeTest01(void)
     if (dst == NULL)
         return 0;
 
-    ret = DecodeBase64(dst, (const uint8_t *)base64msg, strlen(base64msg), BASE64_MODE_RFC2045);
+    ret = DecodeBase64(dst, strlen(msg) + 1, (const uint8_t *)base64msg, strlen(base64msg),
+            &consumed_bytes, &num_decoded, BASE64_MODE_RFC2045);
 
     if (memcmp(dst, msg, strlen(msg)) == 0) {
         ret = 1;
@@ -3137,6 +3154,207 @@ static int MimeDecParseLongFilename01(void)
     PASS;
 }
 
+static int MimeDecParseSmallRemInp(void)
+{
+    // Remainder dA
+    // New input: AAAA
+    char mimemsg[] = "TWltZSBkZWNvZGluZyB pcyBzbyBO T1QgZnV uISBJIGNhbm5vdA";
+
+    uint32_t line_count = 0;
+
+    MimeDecGetConfig()->decode_base64 = true;
+    MimeDecGetConfig()->decode_quoted_printable = true;
+    MimeDecGetConfig()->extract_urls = true;
+
+    /* Init parser */
+    MimeDecParseState *state = MimeDecInitParser(&line_count, TestDataChunkCallback);
+    state->stack->top->data->ctnt_flags |= CTNT_IS_ATTACHMENT;
+
+    const char *str = "From: Sender1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "To: Recipient1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Type: text/plain";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Transfer-Encoding: base64";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)mimemsg, strlen(mimemsg), 1, state));
+
+    str = "AAAA";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    /* Completed */
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseComplete(state));
+
+    MimeDecEntity *msg = state->msg;
+    FAIL_IF_NOT(msg);
+
+    /* filename is not too long */
+    FAIL_IF(msg->anomaly_flags & ANOM_LONG_FILENAME);
+
+    MimeDecFreeEntity(msg);
+
+    /* De Init parser */
+    MimeDecDeInitParser(state);
+
+    PASS;
+}
+
+static int MimeDecParseRemSp(void)
+{
+    // Should have remainder vd A
+    char mimemsg[] = "TWltZSBkZWNvZGluZyBpc yBzbyBOT1QgZnVuISBJIGNhbm5vd A";
+
+    uint32_t line_count = 0;
+
+    MimeDecGetConfig()->decode_base64 = true;
+    MimeDecGetConfig()->decode_quoted_printable = true;
+    MimeDecGetConfig()->extract_urls = true;
+
+    /* Init parser */
+    MimeDecParseState *state = MimeDecInitParser(&line_count, TestDataChunkCallback);
+    state->stack->top->data->ctnt_flags |= CTNT_IS_ATTACHMENT;
+
+    const char *str = "From: Sender1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "To: Recipient1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Type: text/plain";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Transfer-Encoding: base64";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)mimemsg, strlen(mimemsg), 1, state));
+    /* Completed */
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseComplete(state));
+
+    MimeDecEntity *msg = state->msg;
+    FAIL_IF_NOT(msg);
+
+    /* filename is not too long */
+    FAIL_IF(msg->anomaly_flags & ANOM_LONG_FILENAME);
+
+    MimeDecFreeEntity(msg);
+
+    /* De Init parser */
+    MimeDecDeInitParser(state);
+
+    PASS;
+}
+
+static int MimeDecVerySmallInp(void)
+{
+    // Remainder: A
+    // New input: aA
+    char mimemsg[] = "TWltZSBkZWNvZGluZyB pcyBzbyBO T1QgZnV uISBJIGNhbm5vA";
+
+    uint32_t line_count = 0;
+
+    MimeDecGetConfig()->decode_base64 = true;
+    MimeDecGetConfig()->decode_quoted_printable = true;
+    MimeDecGetConfig()->extract_urls = true;
+
+    /* Init parser */
+    MimeDecParseState *state = MimeDecInitParser(&line_count, TestDataChunkCallback);
+    state->stack->top->data->ctnt_flags |= CTNT_IS_ATTACHMENT;
+
+    const char *str = "From: Sender1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "To: Recipient1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Type: text/plain";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Transfer-Encoding: base64";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)mimemsg, strlen(mimemsg), 1, state));
+
+    str = "aA";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    /* Completed */
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseComplete(state));
+
+    MimeDecEntity *msg = state->msg;
+    FAIL_IF_NOT(msg);
+
+    /* filename is not too long */
+    FAIL_IF(msg->anomaly_flags & ANOM_LONG_FILENAME);
+
+    MimeDecFreeEntity(msg);
+
+    /* De Init parser */
+    MimeDecDeInitParser(state);
+
+    PASS;
+}
+
+static int MimeDecParseOddLen(void)
+{
+    char mimemsg[] = "TWltZSBkZWNvZGluZyB pcyBzbyBO T1QgZnV uISBJIGNhbm5vdA";
+
+    uint32_t line_count = 0;
+
+    MimeDecGetConfig()->decode_base64 = true;
+    MimeDecGetConfig()->decode_quoted_printable = true;
+    MimeDecGetConfig()->extract_urls = true;
+
+    /* Init parser */
+    MimeDecParseState *state = MimeDecInitParser(&line_count, TestDataChunkCallback);
+    state->stack->top->data->ctnt_flags |= CTNT_IS_ATTACHMENT;
+
+    const char *str = "From: Sender1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "To: Recipient1";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Type: text/plain";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "Content-Transfer-Encoding: base64";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    str = "";
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)str, strlen(str), 1, state));
+
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseLine((uint8_t *)mimemsg, strlen(mimemsg), 1, state));
+    /* Completed */
+    FAIL_IF_NOT(MIME_DEC_OK == MimeDecParseComplete(state));
+
+    MimeDecEntity *msg = state->msg;
+    FAIL_IF_NOT(msg);
+
+    /* filename is not too long */
+    FAIL_IF(msg->anomaly_flags & ANOM_LONG_FILENAME);
+
+    MimeDecFreeEntity(msg);
+
+    /* De Init parser */
+    MimeDecDeInitParser(state);
+
+    PASS;
+}
+
 static int MimeDecParseLongFilename02(void)
 {
     /* contains 40 character filename and 500+ characters following filename */
@@ -3216,5 +3434,9 @@ void MimeDecRegisterTests(void)
     UtRegisterTest("MimeIsIpv6HostTest01", MimeIsIpv6HostTest01);
     UtRegisterTest("MimeDecParseLongFilename01", MimeDecParseLongFilename01);
     UtRegisterTest("MimeDecParseLongFilename02", MimeDecParseLongFilename02);
+    UtRegisterTest("MimeDecParseSmallRemInp", MimeDecParseSmallRemInp);
+    UtRegisterTest("MimeDecParseRemSp", MimeDecParseRemSp);
+    UtRegisterTest("MimeDecVerySmallInp", MimeDecVerySmallInp);
+    UtRegisterTest("MimeDecParseOddLen", MimeDecParseOddLen);
 #endif /* UNITTESTS */
 }