]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
mime: move FindMimeHeaderTokenRestrict to rust
authorPhilippe Antoine <contact@catenacyber.fr>
Fri, 4 Jun 2021 13:05:14 +0000 (15:05 +0200)
committerVictor Julien <vjulien@oisf.net>
Tue, 7 Dec 2021 06:56:36 +0000 (07:56 +0100)
Also fixes the case where the token name is present
in a value

rust/src/lib.rs
rust/src/mime/mod.rs [new file with mode: 0644]
src/util-decode-mime.c

index 2e5d6d45b1a36de064214a3d45d3119e8b38b81f..fee5fe4d0a41bed0c71976ad11837660a5a960b8 100644 (file)
@@ -124,6 +124,7 @@ pub mod applayertemplate;
 pub mod rdp;
 pub mod x509;
 pub mod asn1;
+pub mod mime;
 pub mod ssh;
 pub mod http2;
 pub mod plugin;
diff --git a/rust/src/mime/mod.rs b/rust/src/mime/mod.rs
new file mode 100644 (file)
index 0000000..7af3f77
--- /dev/null
@@ -0,0 +1,165 @@
+/* Copyright (C) 2021 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use std;
+use std::collections::HashMap;
+
+use nom::combinator::rest;
+use nom::error::ErrorKind;
+use nom::Err;
+use nom::IResult;
+
+#[derive(Clone)]
+pub struct MIMEHeaderTokens<'a> {
+    pub tokens: HashMap<&'a [u8], &'a [u8]>,
+}
+
+pub fn mime_parse_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> {
+    let (i2, _) = tag!(input, "\"")?;
+    let (i3, value) = take_until!(i2, "\"")?;
+    let (i4, _) = tag!(i3, "\"")?;
+    return Ok((i4, value));
+}
+
+pub fn mime_parse_header_token(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
+    // from RFC2047 : like ch.is_ascii_whitespace but without 0x0c FORM-FEED
+    let (i1, _) = take_while!(input, |ch: u8| ch == 0x20
+        || ch == 0x09
+        || ch == 0x0a
+        || ch == 0x0d)?;
+    let (i2, name) = take_until!(i1, "=")?;
+    let (i3, _) = tag!(i2, "=")?;
+    let (i4, value) = alt!(
+        i3,
+        mime_parse_value_delimited | complete!(take_until!(";")) | rest
+    )?;
+    let (i5, _) = opt!(i4, complete!(tag!(";")))?;
+    return Ok((i5, (name, value)));
+}
+
+fn mime_parse_header_tokens(input: &[u8]) -> IResult<&[u8], MIMEHeaderTokens> {
+    let (mut i2, _) = take_until_and_consume!(input, ";")?;
+    let mut tokens = HashMap::new();
+    while i2.len() > 0 {
+        match mime_parse_header_token(i2) {
+            Ok((rem, t)) => {
+                tokens.insert(t.0, t.1);
+                // should never happen
+                debug_validate_bug_on!(i2.len() == rem.len());
+                if i2.len() == rem.len() {
+                    //infinite loop
+                    return Err(Err::Error((input, ErrorKind::Eof)));
+                }
+                i2 = rem;
+            }
+            Err(_) => {
+                // keep first tokens is error in remaining buffer
+                break;
+            }
+        }
+    }
+    return Ok((i2, MIMEHeaderTokens { tokens }));
+}
+
+fn mime_find_header_token<'a>(header: &'a [u8], token: &[u8]) -> Result<&'a [u8], ()> {
+    match mime_parse_header_tokens(header) {
+        Ok((_rem, t)) => {
+            // look for the specific token
+            match t.tokens.get(token) {
+                // easy nominal case
+                Some(value) => return Ok(value),
+                None => return Err(()),
+            }
+        }
+        Err(_) => {
+            return Err(());
+        }
+    }
+}
+
+// TODO ? export with "constants" in cbindgen
+// and use in outbuf definition for rs_mime_find_header_token
+// but other constants are now defined twice in rust and in C
+pub const RS_MIME_MAX_TOKEN_LEN: usize = 255;
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_mime_find_header_token(
+    hinput: *const u8, hlen: u32, tinput: *const u8, tlen: u32, outbuf: &mut [u8; 255],
+    outlen: *mut u32,
+) -> bool {
+    let hbuf = build_slice!(hinput, hlen as usize);
+    let tbuf = build_slice!(tinput, tlen as usize);
+    match mime_find_header_token(hbuf, tbuf) {
+        Ok(value) => {
+            // limit the copy to the supplied buffer size
+            if value.len() <= RS_MIME_MAX_TOKEN_LEN {
+                outbuf[..value.len()].clone_from_slice(value);
+            } else {
+                outbuf.clone_from_slice(&value[..RS_MIME_MAX_TOKEN_LEN]);
+            }
+            *outlen = value.len() as u32;
+            return true;
+        }
+        _ => {}
+    }
+    return false;
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn test_mime_find_header_token() {
+        let undelimok = mime_find_header_token(
+            "attachment; filename=test;".as_bytes(),
+            "filename".as_bytes(),
+        );
+        assert_eq!(undelimok, Ok("test".as_bytes()));
+
+        let delimok = mime_find_header_token(
+            "attachment; filename=\"test2\";".as_bytes(),
+            "filename".as_bytes(),
+        );
+        assert_eq!(delimok, Ok("test2".as_bytes()));
+
+        let evasion_othertoken = mime_find_header_token(
+            "attachment; dummy=\"filename=wrong\"; filename=real;".as_bytes(),
+            "filename".as_bytes(),
+        );
+        assert_eq!(evasion_othertoken, Ok("real".as_bytes()));
+
+        let evasion_suffixtoken = mime_find_header_token(
+            "attachment; notafilename=wrong; filename=good;".as_bytes(),
+            "filename".as_bytes(),
+            &mut outvec,
+        );
+        assert_eq!(evasion_suffixtoken, Ok("good".as_bytes()));
+
+        let badending = mime_find_header_token(
+            "attachment; filename=oksofar; badending".as_bytes(),
+            "filename".as_bytes(),
+        );
+        assert_eq!(badending, Ok("oksofar".as_bytes()));
+
+        let missend = mime_find_header_token(
+            "attachment; filename=test".as_bytes(),
+            "filename".as_bytes(),
+        );
+        assert_eq!(missend, Ok("test".as_bytes()));
+    }
+}
index 1bbe0e284fe0c54a90cc05c87d7b0ab9d606bce3..b3598658d7107f5f36a235133280c4c26b257e8b 100644 (file)
@@ -65,8 +65,6 @@
 #define CTNT_DISP_STR     "content-disposition"
 #define CTNT_TRAN_STR     "content-transfer-encoding"
 #define MSG_ID_STR        "message-id"
-#define BND_START_STR     "boundary="
-#define TOK_END_STR       "\""
 #define MSG_STR           "message/"
 #define MULTIPART_STR     "multipart/"
 #define QP_STR            "quoted-printable"
@@ -1826,70 +1824,6 @@ static int FindMimeHeader(const uint8_t *buf, uint32_t blen,
     return ret;
 }
 
-/**
- * \brief Finds a mime header token within the specified field
- *
- * \param field The current field
- * \param search_start The start of the search (ie. boundary=\")
- * \param search_end The end of the search (ie. \")
- * \param tlen The output length of the token (if found)
- * \param max_len The maximum offset in which to search
- * \param toolong Set if the field value was truncated to max_len.
- *
- * \return A pointer to the token if found, otherwise NULL if not found
- */
-static uint8_t * FindMimeHeaderTokenRestrict(MimeDecField *field, const char *search_start,
-        const char *search_end, uint32_t *tlen, uint32_t max_len, bool *toolong)
-{
-    uint8_t *fptr, *tptr = NULL, *tok = NULL;
-
-    if (toolong)
-        *toolong = false;
-
-    SCLogDebug("Looking for token: %s", search_start);
-
-    /* Check for token definition */
-    size_t ss_len = strlen(search_start);
-    fptr = FindBuffer(field->value, field->value_len, (const uint8_t *)search_start, ss_len);
-    if (fptr != NULL) {
-        fptr += ss_len; /* Start at end of start string */
-        uint32_t offset = fptr - field->value;
-        if (offset > field->value_len) {
-            return tok;
-        }
-        tok = GetToken(fptr, field->value_len - offset, search_end, &tptr, tlen);
-        if (tok == NULL) {
-            return tok;
-        }
-        SCLogDebug("Found mime token");
-
-        /* Compare the actual token length against the maximum */
-        if (toolong && max_len && *tlen > max_len) {
-            SCLogDebug("Token length %d exceeds length restriction %d; truncating", *tlen, max_len);
-            *toolong = true;
-            *tlen = max_len;
-        }
-    }
-
-    return tok;
-}
-
-/**
- * \brief Finds a mime header token within the specified field
- *
- * \param field The current field
- * \param search_start The start of the search (ie. boundary=\")
- * \param search_end The end of the search (ie. \")
- * \param tlen The output length of the token (if found)
- *
- * \return A pointer to the token if found, otherwise NULL if not found
- */
-static uint8_t * FindMimeHeaderToken(MimeDecField *field, const char *search_start,
-        const char *search_end, uint32_t *tlen)
-{
-    return FindMimeHeaderTokenRestrict(field, search_start, search_end, tlen, 0, NULL);
-}
-
 /**
  * \brief Processes the current line for mime headers and also does post-processing
  * when all headers found
@@ -1905,9 +1839,10 @@ static int ProcessMimeHeaders(const uint8_t *buf, uint32_t len,
 {
     int ret = MIME_DEC_OK;
     MimeDecField *field;
-    uint8_t *bptr = NULL, *rptr = NULL;
+    uint8_t *rptr = NULL;
     uint32_t blen = 0;
     MimeDecEntity *entity = (MimeDecEntity *) state->stack->top->data;
+    uint8_t bptr[NAME_MAX];
 
     /* Look for mime header in current line */
     ret = FindMimeHeader(buf, len, state);
@@ -1936,11 +1871,17 @@ static int ProcessMimeHeaders(const uint8_t *buf, uint32_t len,
         field = MimeDecFindField(entity, CTNT_DISP_STR);
         if (field != NULL) {
             bool truncated_name = false;
-            bptr = FindMimeHeaderTokenRestrict(field, "filename=", TOK_END_STR, &blen, NAME_MAX, &truncated_name);
-            if (bptr != NULL) {
+            // NAME_MAX is RS_MIME_MAX_TOKEN_LEN on the rust side
+            if (rs_mime_find_header_token(field->value, field->value_len,
+                        (const uint8_t *)"filename", strlen("filename"), &bptr, &blen)) {
                 SCLogDebug("File attachment found in disposition");
                 entity->ctnt_flags |= CTNT_IS_ATTACHMENT;
 
+                if (blen > NAME_MAX) {
+                    blen = NAME_MAX;
+                    truncated_name = true;
+                }
+
                 /* Copy over using dynamic memory */
                 entity->filename = SCMalloc(blen);
                 if (unlikely(entity->filename == NULL)) {
@@ -1961,8 +1902,9 @@ static int ProcessMimeHeaders(const uint8_t *buf, uint32_t len,
         field = MimeDecFindField(entity, CTNT_TYPE_STR);
         if (field != NULL) {
             /* Check if child entity boundary definition found */
-            bptr = FindMimeHeaderToken(field, BND_START_STR, TOK_END_STR, &blen);
-            if (bptr != NULL) {
+            // NAME_MAX is RS_MIME_MAX_TOKEN_LEN on the rust side
+            if (rs_mime_find_header_token(field->value, field->value_len,
+                        (const uint8_t *)"boundary", strlen("boundary"), &bptr, &blen)) {
                 state->found_child = 1;
                 entity->ctnt_flags |= CTNT_IS_MULTIPART;
 
@@ -1984,11 +1926,17 @@ static int ProcessMimeHeaders(const uint8_t *buf, uint32_t len,
             /* Look for file name (if not already found) */
             if (!(entity->ctnt_flags & CTNT_IS_ATTACHMENT)) {
                 bool truncated_name = false;
-                bptr = FindMimeHeaderTokenRestrict(field, "name=", TOK_END_STR, &blen, NAME_MAX, &truncated_name);
-                if (bptr != NULL) {
+                // NAME_MAX is RS_MIME_MAX_TOKEN_LEN on the rust side
+                if (rs_mime_find_header_token(field->value, field->value_len,
+                            (const uint8_t *)"name", strlen("name"), &bptr, &blen)) {
                     SCLogDebug("File attachment found");
                     entity->ctnt_flags |= CTNT_IS_ATTACHMENT;
 
+                    if (blen > NAME_MAX) {
+                        blen = NAME_MAX;
+                        truncated_name = true;
+                    }
+
                     /* Copy over using dynamic memory */
                     entity->filename = SCMalloc(blen);
                     if (unlikely(entity->filename == NULL)) {