From: Philippe Antoine Date: Mon, 29 Sep 2025 13:11:41 +0000 (+0200) Subject: mime: retain some stateful data for quoted-printable X-Git-Tag: suricata-8.0.2~51 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F13976%2Fhead;p=thirdparty%2Fsuricata.git mime: retain some stateful data for quoted-printable In case a sequence like =3D is split over 2 calls to SCSmtpMimeParseLine Ticket: 7950 (cherry picked from commit 56e08c9134236ed851ccab3430219ca60354b455) --- diff --git a/rust/src/mime/smtp.rs b/rust/src/mime/smtp.rs index d53ff92dd0..10e98d2ff8 100644 --- a/rust/src/mime/smtp.rs +++ b/rust/src/mime/smtp.rs @@ -477,6 +477,7 @@ fn mime_smtp_parse_line( ctx.filename.clear(); ctx.headers.truncate(ctx.main_headers_nb); ctx.encoding = MimeSmtpEncoding::Plain; + ctx.bufeolen = 0; if i.len() >= b.len() + 2 && i[b.len()] == b'-' && i[b.len() + 1] == b'-' { ctx.boundaries.pop(); } @@ -571,17 +572,49 @@ fn mime_smtp_parse_line( if i.len() > MAX_ENC_LINE_LEN { warnings |= MIME_ANOM_LONG_ENC_LINE; } - let mut c = 0; + let mut c = 0usize; let mut eol_equal = false; let mut quoted_buffer = Vec::with_capacity(i.len()); + if ctx.bufeolen > 0 && ctx.bufeol[0] == b'=' { + // we were escaping + if i.len() >= 2 { + let (h1, h2) = if ctx.bufeolen == 1 { + (i[0], i[1]) + } else { + (ctx.bufeol[1], i[0]) + }; + if let Some(v) = hex(h1) { + if let Some(v2) = hex(h2) { + quoted_buffer.push((v << 4) | v2); + } + } + } + if quoted_buffer.is_empty() { + warnings |= MIME_ANOM_INVALID_QP; + } + c = 3 - ctx.bufeolen as usize; + ctx.bufeolen = 0; + } else if ctx.bufeolen > 0 { + quoted_buffer.extend_from_slice(&ctx.bufeol[..ctx.bufeolen as usize]); + ctx.bufeolen = 0; + } while c < i.len() { if i[c] == b'=' { - if c == i.len() - 1 { + if c == i.len() - 1 && full.len() > i.len() { eol_equal = true; break; } else if c + 2 >= i.len() { - // log event ? - warnings |= MIME_ANOM_INVALID_QP; + if full.len() > i.len() { + // log event ? + warnings |= MIME_ANOM_INVALID_QP; + } else { + // keep in state the bytes to unescape + ctx.bufeolen = (i.len() - c) as u8; + ctx.bufeol[0] = b'='; + if ctx.bufeolen == 2 { + ctx.bufeol[1] = i[c + 1]; + } + } break; } if let Some(v) = hex(i[c + 1]) { @@ -599,7 +632,13 @@ fn mime_smtp_parse_line( c += 1; } } - if !eol_equal { + if i.is_empty() { + ctx.bufeolen = (full.len() - i.len()) as u8; + if ctx.bufeolen > 0 { + ctx.bufeol[..ctx.bufeolen as usize] + .copy_from_slice(&full[i.len()..]); + } + } else if !eol_equal { quoted_buffer.extend_from_slice(&full[i.len()..]); } mime_smtp_find_url_strings(ctx, "ed_buffer); @@ -679,7 +718,11 @@ pub unsafe extern "C" fn SCMimeSmtpGetHeader( let name: &CStr = CStr::from_ptr(str); //unsafe // Convert to lowercase, mime::slice_equals_lowercase expects it. - let name: Vec = name.to_bytes().iter().map(|b| b.to_ascii_lowercase()).collect(); + let name: Vec = name + .to_bytes() + .iter() + .map(|b| b.to_ascii_lowercase()) + .collect(); for h in &ctx.headers[ctx.main_headers_nb..] { if mime::slice_equals_lowercase(&h.name, &name) { *buffer = h.value.as_ptr();