]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Pull request #3360: mime: handle MIME header lines split between inspection sections...
authorTom Peters (thopeter) <thopeter@cisco.com>
Thu, 21 Apr 2022 16:14:16 +0000 (16:14 +0000)
committerTom Peters (thopeter) <thopeter@cisco.com>
Thu, 21 Apr 2022 16:14:16 +0000 (16:14 +0000)
Merge in SNORT/snort3 from ~KATHARVE/snort3:mime_header_parsing_copy to master

Squashed commit of the following:

commit 37fe918d4680d3c0528937889fa7a73f1a650db8
Author: Katura Harvey <katharve@cisco.com>
Date:   Mon Mar 28 10:48:51 2022 -0400

    mime: handle MIME header lines split between inspection sections and improve folded header line processing

src/mime/file_mime_decode.cc
src/mime/file_mime_decode.h
src/mime/file_mime_process.cc
src/mime/file_mime_process.h

index 7d388777be608a3eed67a77cb967e6dc98254192..b360a943276e136c34fc7489090624475e6a7d8d 100644 (file)
@@ -49,85 +49,95 @@ void MimeDecode::reset_decoded_bytes()
 void MimeDecode::clear_decode_state()
 {
     decode_type = DECODE_NONE;
-    if (decoder)
-        decoder->reset_decode_state();
+    delete decoder;
+    decoder = nullptr;
 }
 
-void MimeDecode::process_decode_type(const char* start, int length, bool cnt_xf,
-    MimeStats* mime_stats)
+// Called if MIME_FLAG_FILE_ATTACH flag is set 
+void MimeDecode::finalize_decoder(MimeStats* mime_stats)
 {
+    // If this isn't the first body section, the decoder is already set
     if (decoder)
-        delete decoder;
+        return;
 
-    decoder = nullptr;
+    switch (decode_type)
+    {
+        case DECODE_B64:
+            if (mime_stats)
+                mime_stats->b64_attachments++;
+            decoder = new B64Decode(config->get_max_depth(config->get_b64_depth()),
+                config->get_b64_depth());
+            break;
+        case DECODE_QP:
+            if (mime_stats)
+                mime_stats->qp_attachments++;
+            decoder = new QPDecode(config->get_max_depth(config->get_qp_depth()),
+                config->get_qp_depth());
+            break;
+        case DECODE_UU:
+            if (mime_stats)
+                mime_stats->uu_attachments++;
+            decoder = new UUDecode(config->get_max_depth(config->get_uu_depth()),
+                config->get_uu_depth());
+            break;
+        case DECODE_NONE:
+            // This occurs if we only saw Content-Type header and no Cont-Trans-Enc header
+            if (config->get_bitenc_depth() < 0)
+                break;
+            decode_type = DECODE_BITENC;
+        // Fallthrough
+        case DECODE_BITENC:
+            if (mime_stats)
+                mime_stats->bitenc_attachments++;
+            decoder = new BitDecode(config->get_max_depth(config->get_bitenc_depth()),
+                config->get_bitenc_depth());
+            break;
+    }
+}
 
-    if (cnt_xf)
+// This may be called more than once due to folding. 
+void MimeDecode::process_decode_type(const char* start, int length)
+{
+    if (config->get_b64_depth() > -1)
     {
-        if (config->get_b64_depth() > -1)
+        const char* tmp = SnortStrcasestr(start, length, "base64");
+        if ( tmp )
         {
-            const char* tmp = SnortStrcasestr(start, length, "base64");
-
-            if ( tmp )
-            {
-                decode_type = DECODE_B64;
-                if (mime_stats)
-                    mime_stats->b64_attachments++;
-                decoder = new B64Decode(config->get_max_depth(config->get_b64_depth()),
-                        config->get_b64_depth());
-                file_decomp_reset();
-                return;
-            }
+            decode_type = DECODE_B64;
+            return;
         }
+    }
 
-        if (config->get_qp_depth() > -1)
+    if (config->get_qp_depth() > -1)
+    {
+        const char* tmp = SnortStrcasestr(start, length, "quoted-printable");
+        if ( tmp )
         {
-            const char* tmp = SnortStrcasestr(start, length, "quoted-printable");
-
-            if ( tmp )
-            {
-                decode_type = DECODE_QP;
-                if (mime_stats)
-                    mime_stats->qp_attachments++;
-                decoder = new QPDecode(config->get_max_depth(config->get_qp_depth()),
-                        config->get_qp_depth());
-                file_decomp_reset();
-                return;
-            }
+            decode_type = DECODE_QP;
+            return;
         }
+    }
 
-        if (config->get_uu_depth() > -1)
+    if (config->get_uu_depth() > -1)
+    {
+        const char* tmp = SnortStrcasestr(start, length, "uuencode");
+        if ( tmp )
         {
-            const char* tmp = SnortStrcasestr(start, length, "uuencode");
-
-            if ( tmp )
-            {
-                decode_type = DECODE_UU;
-                if (mime_stats)
-                    mime_stats->uu_attachments++;
-                decoder = new UUDecode(config->get_max_depth(config->get_uu_depth()),
-                        config->get_uu_depth());
-                file_decomp_reset();
-                return;
-            }
+            decode_type = DECODE_UU;
+            return;
         }
     }
 
-    if (config->get_bitenc_depth() > -1)
-    {
+    // Default and "7bit" / "8bit" / "binary". This means the body is not encoded. This will not
+    // overwrite any of the three encoding types above
+    if (config->get_bitenc_depth() > -1 and decode_type == DECODE_NONE)
         decode_type = DECODE_BITENC;
-        if (mime_stats)
-            mime_stats->bitenc_attachments++;
-        decoder = new BitDecode(config->get_max_depth(config->get_bitenc_depth()),
-            config->get_bitenc_depth());
-        file_decomp_reset();
-        return;
-    }
 }
 
 DecodeResult MimeDecode::decode_data(const uint8_t* start, const uint8_t* end)
 {
     uint8_t* decode_buf = MimeDecodeContextData::get_decode_buf();
-    return (decoder ? decoder->decode_data(start,end, decode_buf) : DECODE_SUCCESS);
+    return (decoder ? decoder->decode_data(start, end, decode_buf) : DECODE_SUCCESS);
 }
 
 int MimeDecode::get_detection_depth()
index 3095bff9692c21de0cfd8bbae7651423cc6b706e..709edd7f535d3d2519d5f814ea70e25dc1edb9ca 100644 (file)
@@ -35,11 +35,10 @@ namespace snort
 enum DecodeType
 {
     DECODE_NONE = 0,
+    DECODE_BITENC,
     DECODE_B64,
     DECODE_QP,
     DECODE_UU,
-    DECODE_BITENC,
-    DECODE_ALL
 };
 
 struct MimeStats
@@ -61,8 +60,8 @@ public:
     ~MimeDecode();
 
     // get the decode type from buffer
-    // bool cnt_xf: true if there is transfer encode defined, false otherwise
-    void process_decode_type(const char* start, int length, bool cnt_xf, MimeStats* mime_stats);
+    void process_decode_type(const char* start, int length);
+    void finalize_decoder(MimeStats* mime_stats);
 
     // Main function to decode file data
     DecodeResult decode_data(const uint8_t* start, const uint8_t* end);
index 6e29cf22b137eda713f510dcdaf3a55feb839437..fa1bc609bbd3015de89430d20c20b38a96f674f0 100644 (file)
@@ -78,7 +78,7 @@ static THREAD_LOCAL MIMESearch* mime_current_search = nullptr;
 SearchTool* mime_hdr_search_mpse = nullptr;
 MIMESearch mime_hdr_search[HDR_LAST];
 
-static void get_mime_eol(const uint8_t* ptr, const uint8_t* end,
+static bool get_mime_eol(const uint8_t* ptr, const uint8_t* end,
     const uint8_t** eol, const uint8_t** eolm)
 {
     const uint8_t* tmp_eol;
@@ -89,14 +89,14 @@ static void get_mime_eol(const uint8_t* ptr, const uint8_t* end,
     if ( !ptr or !end )
     {
         *eol = *eolm = end;
-        return;
+        return false;
     }
 
     tmp_eol = (const uint8_t*)memchr(ptr, '\n', end - ptr);
     if (tmp_eol == nullptr)
     {
-        tmp_eol = end;
-        tmp_eolm = end;
+        *eol = *eolm = end;
+        return false;
     }
     else
     {
@@ -117,6 +117,7 @@ static void get_mime_eol(const uint8_t* ptr, const uint8_t* end,
 
     *eol = tmp_eol;
     *eolm = tmp_eolm;
+    return true;
 }
 
 /*
@@ -141,7 +142,7 @@ static int search_str_found(void* id, void*, int index, void*, void*)
     return 1;
 }
 
-void MimeSession::setup_decode(const char* data, int size, bool cnt_xf)
+void MimeSession::setup_attachment_processing()
 {
     /* Check for Encoding Type */
     if ( decode_conf && decode_conf->is_decoding_enabled())
@@ -150,12 +151,8 @@ void MimeSession::setup_decode(const char* data, int size, bool cnt_xf)
         {
             decode_state = new MimeDecode(decode_conf);
         }
-
         if (decode_state != nullptr)
-        {
-            decode_state->process_decode_type(data, size, cnt_xf, mime_stats);
             state_flags |= MIME_FLAG_FILE_ATTACH;
-        }
     }
 }
 
@@ -173,239 +170,239 @@ const uint8_t* MimeSession::process_mime_header(Packet* p, const uint8_t* ptr,
 {
     const uint8_t* eol = data_end_marker;
     const uint8_t* eolm = eol;
-    const uint8_t* colon;
-    const uint8_t* content_type_ptr = nullptr;
-    const uint8_t* cont_trans_enc = nullptr;
-    const uint8_t* cont_disp = nullptr;
-    int header_found;
     const uint8_t* start_hdr;
 
     start_hdr = ptr;
+    bool cont = true;
+    while (cont and ptr < data_end_marker)
+    {
+        bool found_end_marker = get_mime_eol(ptr, data_end_marker, &eol, &eolm);
+        // Save partial header lines until we have the full line
+        if (!found_end_marker)
+        {
+            if (partial_header)
+            {
+                // Single header line split into >2 sections, accumulate
+                // FIXIT-P: could allocate reasonable-size buf and only reallocate if needed
+                const uint32_t new_len = data_end_marker - ptr;
+                const uint32_t cum_len = partial_header_len + new_len;
+                uint8_t* tmp = new uint8_t[cum_len];
+                memcpy(tmp, partial_header, partial_header_len);
+                memcpy(tmp + partial_header_len, ptr, new_len);
+                delete[] partial_header;
+                partial_header_len = cum_len;
+                partial_header = tmp;
+            }
+            else
+            {
+                partial_header_len = data_end_marker - ptr;
+                partial_header = new uint8_t[partial_header_len];
+                memcpy(partial_header, ptr, partial_header_len);
+            }
+            return data_end_marker;
+        }
+        if (partial_header)
+        {
+            const uint32_t remainder_header_len = eol - ptr;
+            const uint32_t full_header_len = partial_header_len + remainder_header_len;
+            const uint8_t* full_header = new uint8_t[full_header_len];
+            const uint8_t* head_ptr = full_header;
+            memcpy((void*)full_header, partial_header, partial_header_len);
+            memcpy((void*)(full_header + partial_header_len), ptr, remainder_header_len);
+            ptr = eol;
+            // Update eol and eolm to point to full_header buffer. eol points to byte after end
+            // marker and eolm points to start of the end marker - either \n or \r if CR precedes LF
+            eol = full_header + full_header_len;
+            if ((full_header_len > 1) && (*(eol - 2) == '\r'))
+                eolm = eol - 2;
+            else
+                eolm = eol - 1;
 
-    /* if we got a content-type in a previous packet and are
-     * folding, the boundary still needs to be checked for */
-    if (state_flags & MIME_FLAG_IN_CONTENT_TYPE)
-        content_type_ptr = ptr;
+            cont = process_header_line(head_ptr, eol, eolm, start_hdr, p);
 
-    if (state_flags & MIME_FLAG_IN_CONT_TRANS_ENC)
-        cont_trans_enc = ptr;
+            delete[] partial_header;
+            partial_header = nullptr;
+            partial_header_len = 0;
+            delete[] full_header;
+        }
+        else
+        {
+            cont = process_header_line(ptr, eol, eolm, start_hdr, p);
+        }
+        state_flags |= MIME_FLAG_SEEN_HEADERS;
+    }
+    if (!cont)
+    {
+        data_state = STATE_DATA_BODY;
+    }
+    return ptr;
+}
 
-    if (state_flags & MIME_FLAG_IN_CONT_DISP)
-        cont_disp = ptr;
+static bool is_wsp(const uint8_t* c)
+{
+    return (*c == ' ' or *c == '\t');
+}
 
-    while (ptr < data_end_marker)
+bool MimeSession::process_header_line(const uint8_t*& ptr, const uint8_t* eol, const uint8_t* eolm,
+    const uint8_t* start_hdr, Packet* p)
+{
+    const uint8_t* colon;
+    const uint8_t* header_value_ptr = nullptr;
+    int max_header_name_len = 0;
+    
+    int header_found;
+    /* got a line with only end of line marker should signify end of header */
+    if (eolm == ptr)
     {
-        int max_header_name_len = 0;
-        get_mime_eol(ptr, data_end_marker, &eol, &eolm);
+        /* if no headers, treat as data */
+        if ((ptr == start_hdr) and !(state_flags & MIME_FLAG_SEEN_HEADERS))
+            ptr = eolm;
+        else
+            ptr = eol;
+        return false;
+    }
 
-        /* got a line with only end of line marker should signify end of header */
-        if (eolm == ptr)
-        {
-            /* reset global header state values */
-            state_flags &=
-                ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT
-                | MIME_FLAG_IN_CONT_TRANS_ENC );
+    // If we're not folding, process the header field name
+    if (!is_wsp(ptr))
+    {
+        // Clear flags from last header line
+        state_flags &= ~(MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_IN_CONT_TRANS_ENC);
 
-            data_state = STATE_DATA_BODY;
+        bool got_non_printable_in_header_name = false;
 
-            /* if no headers, treat as data */
-            if (ptr == start_hdr)
-                return eolm;
-            else
-                return eol;
+        // If we're not folding and the first char is a whitespace (other than space (0x20) or htab
+        // (0x09) that means folding), it's not a header
+        if (isspace((int)*ptr) || *ptr == ':')
+        {
+            return false;
         }
 
-        /* if we're not folding, see if we should interpret line as a data line
-         * instead of a header line */
-        if (!(state_flags & (MIME_FLAG_FOLDING | MIME_FLAG_DATA_HEADER_CONT)))
+        /* look for header field colon - if we're not folding then we need
+         * to find a header which will be all printables (except colon)
+         * followed by a colon */
+        colon = ptr;
+        while ((colon < eolm) && (*colon != ':'))
         {
-            char got_non_printable_in_header_name = 0;
-
-            /* if we're not folding and the first char is a space or
-             * colon, it's not a header */
-            if (isspace((int)*ptr) || *ptr == ':')
-            {
-                data_state = STATE_DATA_BODY;
-                return ptr;
-            }
-
-            /* look for header field colon - if we're not folding then we need
-             * to find a header which will be all printables (except colon)
-             * followed by a colon */
-            colon = ptr;
-            while ((colon < eolm) && (*colon != ':'))
-            {
-                if (((int)*colon < 33) || ((int)*colon > 126))
-                    got_non_printable_in_header_name = 1;
+            if (((int)*colon < 33) || ((int)*colon > 126))
+                got_non_printable_in_header_name = true;
 
-                colon++;
-            }
-
-            /* Check for Exim 4.32 exploit where number of chars before colon is greater than 64 */
-            int header_name_len = colon - ptr;
+            colon++;
+        }
+        
+        if (colon + 1 < eolm)
+            header_value_ptr = colon + 1;
 
-            if ((colon < eolm) && (header_name_len > MAX_HEADER_NAME_LEN))
-            {
-                max_header_name_len = header_name_len;
-            }
+        /* Check for Exim 4.32 exploit where number of chars before colon is greater than 64 */
+        int header_name_len = colon - ptr;
 
-            /* If the end on line marker and end of line are the same, assume
-             * header was truncated, so stay in data header state */
-            if ((eolm != eol) &&
-                ((colon == eolm) || got_non_printable_in_header_name))
-            {
-                /* no colon or got spaces in header name (won't be interpreted as a header)
-                 * assume we're in the body */
-                state_flags &=
-                    ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT
-                    |MIME_FLAG_IN_CONT_TRANS_ENC);
+        if ((colon < eolm) && (header_name_len > MAX_HEADER_NAME_LEN))
+        {
+            max_header_name_len = header_name_len;
+        }
 
-                data_state = STATE_DATA_BODY;
+        /* If the end on line marker and end of line are the same, assume
+         * header was truncated, so stay in data header state */
+        if ((eolm != eol) &&
+            ((colon == eolm) || got_non_printable_in_header_name))
+        {
+            /* no colon or got spaces in header name (won't be interpreted as a header)
+             * assume we're in the body */
+            return false;
+        }
 
-                return ptr;
-            }
+        if (tolower((int)*ptr) == 'c')
+        {
+            mime_current_search = &mime_hdr_search[0];
+            header_found = mime_hdr_search_mpse->find(
+                (const char*)ptr, eolm - ptr, search_str_found, true);
 
-            if (tolower((int)*ptr) == 'c')
+            /* Headers must start at beginning of line */
+            if ((header_found > 0) && (mime_search_info.index == 0))
             {
-                mime_current_search = &mime_hdr_search[0];
-                header_found = mime_hdr_search_mpse->find(
-                    (const char*)ptr, eolm - ptr, search_str_found, true);
-
-                /* Headers must start at beginning of line */
-                if ((header_found > 0) && (mime_search_info.index == 0))
+                switch (mime_search_info.id)
                 {
-                    switch (mime_search_info.id)
-                    {
                     case HDR_CONTENT_TYPE:
-                        content_type_ptr = ptr + mime_search_info.length;
                         state_flags |= MIME_FLAG_IN_CONTENT_TYPE;
                         break;
                     case HDR_CONT_TRANS_ENC:
-                        cont_trans_enc = ptr + mime_search_info.length;
                         state_flags |= MIME_FLAG_IN_CONT_TRANS_ENC;
                         break;
                     case HDR_CONT_DISP:
-                        cont_disp = ptr + mime_search_info.length;
                         state_flags |= MIME_FLAG_IN_CONT_DISP;
                         break;
                     default:
+                        assert(false);
                         break;
-                    }
                 }
             }
-            else if (tolower((int)*ptr) == 'e')
+        }
+        else if (tolower((int)*ptr) == 'e')
+        {
+            if ((eolm - ptr) >= 9)
             {
-                if ((eolm - ptr) >= 9)
+                if (strncasecmp((const char*)ptr, "Encoding:", 9) == 0)
                 {
-                    if (strncasecmp((const char*)ptr, "Encoding:", 9) == 0)
-                    {
-                        cont_trans_enc = ptr + 9;
-                        state_flags |= MIME_FLAG_IN_CONT_TRANS_ENC;
-                    }
+                    state_flags |= MIME_FLAG_IN_CONT_TRANS_ENC;
                 }
             }
         }
-        else
-        {
-            state_flags &= ~MIME_FLAG_DATA_HEADER_CONT;
-        }
+    }
+    else if (!(state_flags & MIME_FLAG_SEEN_HEADERS))
+    {
+        // If the line starts with folding whitespace but we haven't seen any headers yet, we can't
+        // be folding. Treat as body.
+        return false;
+    }
+    else
+    {
+        // If we are folding, we've already processed the header field and are somewhere in the
+        // header value
+        header_value_ptr = ptr;
+    }
 
-        int ret = handle_header_line(ptr, eol, max_header_name_len, p);
-        if (ret < 0)
-            return nullptr;
-        else if (ret > 0)
-        {
-            /* assume we guessed wrong and are in the body */
-            data_state = STATE_DATA_BODY;
-            state_flags &=
-                ~(MIME_FLAG_FOLDING | MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_DATA_HEADER_CONT
-                | MIME_FLAG_IN_CONT_TRANS_ENC | MIME_FLAG_IN_CONT_DISP);
-            return ptr;
-        }
+    int ret = handle_header_line(ptr, eol, max_header_name_len, p);
+    if (ret != 0)
+        return false;
 
-        /* check for folding
-         * if char on next line is a space and not \n or \r\n, we are folding */
-        if ((eol < data_end_marker) && isspace((int)eol[0]) && (eol[0] != '\n'))
-        {
-            if ((eol < (data_end_marker - 1)) && (eol[0] != '\r') && (eol[1] != '\n'))
-            {
-                state_flags |= MIME_FLAG_FOLDING;
-            }
-            else
-            {
-                state_flags &= ~MIME_FLAG_FOLDING;
-            }
-        }
-        else if (eol != eolm)
-        {
-            state_flags &= ~MIME_FLAG_FOLDING;
-        }
+    // Now handle the header value
+    const uint32_t header_value_len = eolm - header_value_ptr;
 
-        /* check if we're in a content-type header and not folding. if so we have the whole
-         * header line/lines for content-type - see if we got a multipart with boundary
-         * we don't check each folded line, but wait until we have the complete header
-         * because boundary=BOUNDARY can be split across multiple folded lines before
-         * or after the '=' */
-        if ((state_flags &
-                (MIME_FLAG_IN_CONTENT_TYPE | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONTENT_TYPE)
+    if (state_flags & MIME_FLAG_IN_CONTENT_TYPE)
+    {
+        if (data_state == STATE_MIME_HEADER)
         {
-            if ((data_state == STATE_MIME_HEADER) && !(state_flags &
-                    MIME_FLAG_FILE_ATTACH))
-            {
-                setup_decode((const char*)content_type_ptr, (eolm - content_type_ptr), false);
-            }
-
-            state_flags &= ~MIME_FLAG_IN_CONTENT_TYPE;
-            content_type_ptr = nullptr;
+            setup_attachment_processing();
         }
-        else if ((state_flags &
-                (MIME_FLAG_IN_CONT_TRANS_ENC | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONT_TRANS_ENC)
+        // We don't need the value, so it doesn't matter if we're folding
+        state_flags &= ~MIME_FLAG_IN_CONTENT_TYPE;
+    }
+    else if (state_flags & MIME_FLAG_IN_CONT_TRANS_ENC)
+    {
+        setup_attachment_processing();
+        if (decode_state != nullptr and header_value_len > 0)
         {
-            setup_decode((const char*)cont_trans_enc, (eolm - cont_trans_enc), true);
-
-            state_flags &= ~MIME_FLAG_IN_CONT_TRANS_ENC;
-
-            cont_trans_enc = nullptr;
+            decode_state->process_decode_type((const char*)header_value_ptr, header_value_len);
         }
-        else if (((state_flags &
-                    (MIME_FLAG_IN_CONT_DISP | MIME_FLAG_FOLDING)) == MIME_FLAG_IN_CONT_DISP) &&
-            cont_disp)
-        {
-            bool disp_cont = (state_flags & MIME_FLAG_IN_CONT_DISP_CONT) ? true : false;
-            int len = extract_file_name((const char*&)cont_disp, eolm - cont_disp, &disp_cont);
-
-            if (len > 0)
-            {
-                filename.assign((const char*)cont_disp, len);
+        // Don't clear the MIME_FLAG_IN_CONT_TRANS_ENC flag in case of folding
+    }
+    else if (state_flags & MIME_FLAG_IN_CONT_DISP)
+    {
+        int len = extract_file_name((const char*&)header_value_ptr, header_value_len);
 
-                if (log_config->log_filename && log_state)
-                {
-                    log_state->log_file_name(cont_disp, len);
-                }
-            }
-            else
-                filename.clear();
+        if (len > 0)
+        {
+            filename.assign((const char*)header_value_ptr, len);
 
-            if (disp_cont)
+            if (log_config->log_filename && log_state)
             {
-                state_flags |= MIME_FLAG_IN_CONT_DISP_CONT;
+                log_state->log_file_name(header_value_ptr, len);
             }
-            else
-            {
-                state_flags &= ~MIME_FLAG_IN_CONT_DISP;
-                state_flags &= ~MIME_FLAG_IN_CONT_DISP_CONT;
-            }
-
-            cont_disp = nullptr;
+            state_flags &= ~MIME_FLAG_IN_CONT_DISP;
         }
-
-        ptr = eol;
-
-        if (ptr == data_end_marker)
-            state_flags |= MIME_FLAG_DATA_HEADER_CONT;
     }
 
-    return ptr;
+    ptr = eol;
+    return true;
 }
 
 /* Get the end of data body (excluding boundary)*/
@@ -466,6 +463,7 @@ const uint8_t* MimeSession::process_mime_body(const uint8_t* ptr,
 
         if (( attach_start < attach_end ) && decode_state)
         {
+            decode_state->finalize_decoder(mime_stats);
             if (decode_state->decode_data(attach_start, attach_end) == DECODE_FAIL )
             {
                 decode_alert();
@@ -634,10 +632,16 @@ const uint8_t* MimeSession::process_mime_data_paf(
 void MimeSession::reset_part_state()
 {
     state_flags = 0;
+    filename_state = CONT_DISP_FILENAME_PARAM_NAME;
+    delete[] partial_header;
     if (decode_state)
+    {
         decode_state->clear_decode_state();
+        decode_state->file_decomp_reset();
+    }
 
     // Clear MIME's file data to prepare for next file
+    filename.clear();
     file_counter++;
     file_process_offset = 0;
     current_file_cache_file_id = 0;
@@ -727,63 +731,72 @@ MailLogState* MimeSession::get_log_state()
     return log_state;
 }
 
-int MimeSession::extract_file_name(const char*& start, int length, bool* disp_cont)
+int MimeSession::extract_file_name(const char*& start, int length)
 {
-    const char* tmp = nullptr;
+    const char* tmp = start;
     const char* end = start+length;
 
     if (length <= 0)
         return -1;
 
-    if (!(*disp_cont))
+    while (tmp < end)
     {
-        tmp = SnortStrcasestr(start, length, "filename");
-
-        if ( tmp == nullptr )
-            return -1;
-
-        tmp = tmp + 8;
-        while ( (tmp < end) && ((isspace(*tmp)) || (*tmp == '=') ))
-        {
-            tmp++;
-        }
-    }
-    else
-        tmp = start;
-
-    if (tmp < end)
-    {
-        if (*tmp == '"' || (*disp_cont))
+        switch (filename_state)
         {
-            if (*tmp == '"')
+            case CONT_DISP_FILENAME_PARAM_NAME:
             {
-                if (*disp_cont)
-                {
-                    *disp_cont = false;
-                    return (tmp - start);
-                }
-                tmp++;
+                tmp = SnortStrcasestr(start, length, "filename");
+                if ( tmp == nullptr )
+                    return -1;
+                tmp = tmp + 8;
+                filename_state = CONT_DISP_FILENAME_PARAM_EQUALS;
+                break;
             }
-            start = tmp;
-            tmp = SnortStrnPbrk(start,(end - tmp),"\"");
-            if (tmp == nullptr )
+            case CONT_DISP_FILENAME_PARAM_EQUALS:
             {
-                if ((end - tmp) > 0 )
+                //skip past whitespace and '='
+                if (isspace(*tmp) or (*tmp == '='))
+                    tmp++;
+                else if (*tmp == '"')
                 {
-                    tmp = end;
-                    *disp_cont = true;
+                    // Skip past the quote
+                    tmp++;
+                    filename_state = CONT_DISP_FILENAME_PARAM_VALUE_QUOTE;
                 }
                 else
-                    return -1;
+                {
+                    filename_state = CONT_DISP_FILENAME_PARAM_VALUE;
+                    start = tmp;
+                }
+                break;
             }
-            else
-                *disp_cont = false;
-            end = tmp;
-        }
-        else
-        {
-            start = tmp;
+            case CONT_DISP_FILENAME_PARAM_VALUE_QUOTE:
+                start = tmp;
+                tmp = SnortStrnPbrk(start,(end - tmp),"\"");
+                if (tmp)
+                {
+                    end = tmp;
+                    return (end - start);
+                }
+                // Since we have the full header line and there can't be wrapping within a quoted
+                // string, getting here means the line is malformed. Treat like unquoted string
+                filename_state = CONT_DISP_FILENAME_PARAM_VALUE;
+                tmp = start;
+                break;
+            case CONT_DISP_FILENAME_PARAM_VALUE:
+                // Go until we get a ';' or whitespace
+                if ((*tmp == ';') or isspace(*tmp))
+                {
+                    end = tmp;
+                    return (end - start);
+                }
+                tmp++;
+                break;
         }
+    }
+    if (filename_state == CONT_DISP_FILENAME_PARAM_VALUE)
+    {
+        // The filename is ended by the eol marker
         return (end - start);
     }
     return -1;
@@ -829,8 +842,8 @@ MimeSession::MimeSession(Packet* p, const DecodeConfig* dconf, MailLogConfig* lc
 
 MimeSession::~MimeSession()
 {
-    if ( decode_state )
-        delete(decode_state);
+    delete decode_state;
+    delete[] partial_header;
 }
 
 // File verdicts get cached with key (file_id, sip, dip). File_id is hash of filename if available.
index 616a947e400b07e133657257eede69c7961192d2..1ec2a1621932619cf6b35c0c93c2cec48f2b0a5f 100644 (file)
 namespace snort
 {
 /* state flags */
-#define MIME_FLAG_FOLDING                    0x00000001
 #define MIME_FLAG_IN_CONTENT_TYPE            0x00000002
 #define MIME_FLAG_GOT_BOUNDARY               0x00000004
-#define MIME_FLAG_DATA_HEADER_CONT           0x00000008
+#define MIME_FLAG_SEEN_HEADERS               0x00000008
 #define MIME_FLAG_IN_CONT_TRANS_ENC          0x00000010
 #define MIME_FLAG_FILE_ATTACH                0x00000020
 #define MIME_FLAG_MULTIPLE_EMAIL_ATTACH      0x00000040
@@ -49,6 +48,14 @@ namespace snort
 #define STATE_DATA_BODY    2    /* Data body section of data state */
 #define STATE_MIME_HEADER  3    /* MIME header section within data section */
 
+enum FilenameState
+{
+    CONT_DISP_FILENAME_PARAM_NAME,
+    CONT_DISP_FILENAME_PARAM_EQUALS, 
+    CONT_DISP_FILENAME_PARAM_VALUE_QUOTE,
+    CONT_DISP_FILENAME_PARAM_VALUE
+};
+
 /* Maximum length of header chars before colon, based on Exim 4.32 exploit */
 #define MAX_HEADER_NAME_LEN 64
 
@@ -87,6 +94,7 @@ private:
     MailLogConfig* log_config = nullptr;
     MailLogState* log_state = nullptr;
     MimeStats* mime_stats = nullptr;
+    FilenameState filename_state = CONT_DISP_FILENAME_PARAM_NAME;
     std::string filename;
     bool continue_inspecting_file = true;
     // This counter is not an accurate count of files; used only for creating a unique mime_file_id
@@ -112,12 +120,17 @@ private:
     virtual bool is_end_of_data(Flow*) { return false; }
 
     void reset_mime_state();
-    void setup_decode(const char* data, int size, bool cnt_xf);
+    void setup_attachment_processing();
     const uint8_t* process_mime_header(Packet*, const uint8_t* ptr, const uint8_t* data_end_marker);
+    bool process_header_line(const uint8_t*& ptr, const uint8_t* eol, const uint8_t* eolm, const
+        uint8_t* start_hdr, Packet* p);
     const uint8_t* process_mime_body(const uint8_t* ptr, const uint8_t* data_end,bool is_data_end);
     const uint8_t* process_mime_data_paf(Packet*, const uint8_t* start, const uint8_t* end,
         bool upload, FilePosition);
-    int extract_file_name(const char*& start, int length, bool* disp_cont);
+    int extract_file_name(const char*& start, int length);
+
+    uint8_t* partial_header = nullptr;
+    uint32_t partial_header_len = 0;
 };
 }
 #endif