From: Philippe Antoine Date: Fri, 4 Jun 2021 13:05:14 +0000 (+0200) Subject: mime: move FindMimeHeaderTokenRestrict to rust X-Git-Tag: suricata-7.0.0-beta1~1132 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8feb9c35ae24045d80cc4499923d66da589d35cb;p=thirdparty%2Fsuricata.git mime: move FindMimeHeaderTokenRestrict to rust Also fixes the case where the token name is present in a value --- diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2e5d6d45b1..fee5fe4d0a 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -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 index 0000000000..7af3f77438 --- /dev/null +++ b/rust/src/mime/mod.rs @@ -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())); + } +} diff --git a/src/util-decode-mime.c b/src/util-decode-mime.c index 1bbe0e284f..b3598658d7 100644 --- a/src/util-decode-mime.c +++ b/src/util-decode-mime.c @@ -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)) {