mime: move FindMimeHeaderTokenRestrict to rust

Also fixes the case where the token name is present
in a value
pull/6680/head
Philippe Antoine 4 years ago committed by Victor Julien
parent 76131c8cff
commit 8feb9c35ae

@ -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;

@ -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()));
}
}

@ -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)) {

Loading…
Cancel
Save