From: Pierre Chifflier Date: Sat, 30 Oct 2021 14:32:38 +0000 (+0200) Subject: rust/ssh: convert parser to nom7 functions X-Git-Tag: suricata-7.0.0-beta1~1268 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=74be8b94eceefcb21937ce53f18e2192538f04aa;p=thirdparty%2Fsuricata.git rust/ssh: convert parser to nom7 functions --- diff --git a/rust/src/ssh/parser.rs b/rust/src/ssh/parser.rs index e512e60cc6..90f9e9e65e 100644 --- a/rust/src/ssh/parser.rs +++ b/rust/src/ssh/parser.rs @@ -15,10 +15,15 @@ * 02110-1301, USA. */ -use nom::combinator::rest; -use nom::number::streaming::{be_u32, be_u8}; -use md5::Md5; use digest::Digest; +use md5::Md5; +use nom7::branch::alt; +use nom7::bytes::streaming::{is_not, tag, take}; +use nom7::character::streaming::char; +use nom7::combinator::{complete, rest, verify}; +use nom7::multi::length_data; +use nom7::number::streaming::{be_u32, be_u8}; +use nom7::IResult; use std::fmt; #[derive(PartialEq, Eq, Copy, Clone, Debug)] @@ -84,16 +89,14 @@ pub struct SshBanner<'a> { // Could be simplified adding dummy \n at the end // or use nom5 nom::bytes::complete::is_not -named!(pub ssh_parse_banner, - do_parse!( - tag!("SSH-") >> - protover: is_not!("-") >> - char!('-') >> - swver: alt!( complete!( is_not!(" \r\n") ) | rest ) >> - //remaining after space is comments - (SshBanner{protover, swver}) - ) -); +pub fn ssh_parse_banner(i: &[u8]) -> IResult<&[u8], SshBanner> { + let (i, _) = tag("SSH-")(i)?; + let (i, protover) = is_not("-")(i)?; + let (i, _) = char('-')(i)?; + let (i, swver) = alt((complete(is_not(" \r\n")), rest))(i)?; + //remaining after space is comments + Ok((i, SshBanner { protover, swver })) +} #[derive(PartialEq)] pub struct SshRecordHeader { @@ -112,33 +115,39 @@ impl fmt::Display for SshRecordHeader { } } -named!(pub ssh_parse_record_header, - do_parse!( - pkt_len: verify!(be_u32, |&val| val > 1) >> - padding_len: be_u8 >> - msg_code: be_u8 >> - (SshRecordHeader{pkt_len: pkt_len, - padding_len: padding_len, - msg_code: MessageCode::from_u8(msg_code)}) - ) -); +pub fn ssh_parse_record_header(i: &[u8]) -> IResult<&[u8], SshRecordHeader> { + let (i, pkt_len) = verify(be_u32, |&val| val > 1)(i)?; + let (i, padding_len) = be_u8(i)?; + let (i, msg_code) = be_u8(i)?; + Ok(( + i, + SshRecordHeader { + pkt_len, + padding_len, + msg_code: MessageCode::from_u8(msg_code), + }, + )) +} //test for evasion against pkt_len=0or1... -named!(pub ssh_parse_record, - do_parse!( - pkt_len: verify!(be_u32, |&val| val > 1) >> - padding_len: be_u8 >> - msg_code: be_u8 >> - take!((pkt_len-2) as usize) >> - (SshRecordHeader{pkt_len: pkt_len, - padding_len: padding_len, - msg_code: MessageCode::from_u8(msg_code)}) - ) -); +pub fn ssh_parse_record(i: &[u8]) -> IResult<&[u8], SshRecordHeader> { + let (i, pkt_len) = verify(be_u32, |&val| val > 1)(i)?; + let (i, padding_len) = be_u8(i)?; + let (i, msg_code) = be_u8(i)?; + let (i, _) = take((pkt_len - 2) as usize)(i)?; + Ok(( + i, + SshRecordHeader { + pkt_len, + padding_len, + msg_code: MessageCode::from_u8(msg_code), + }, + )) +} -#[derive(Debug,PartialEq)] +#[derive(Debug, PartialEq)] pub struct SshPacketKeyExchange<'a> { - pub cookie: &'a[u8], + pub cookie: &'a [u8], pub kex_algs: &'a [u8], pub server_host_key_algs: &'a [u8], pub encr_algs_client_to_server: &'a [u8], @@ -156,67 +165,84 @@ pub struct SshPacketKeyExchange<'a> { const SSH_HASSH_STRING_DELIMITER_SLICE: [u8; 1] = [b';']; impl<'a> SshPacketKeyExchange<'a> { - pub fn generate_hassh(&self, hassh_string: &mut Vec, hassh: &mut Vec, to_server: &bool) { - let slices = if *to_server { - [self.kex_algs, &SSH_HASSH_STRING_DELIMITER_SLICE, - self.encr_algs_server_to_client, &SSH_HASSH_STRING_DELIMITER_SLICE, - self.mac_algs_server_to_client, &SSH_HASSH_STRING_DELIMITER_SLICE, - self.comp_algs_server_to_client]} - else { - [self.kex_algs, &SSH_HASSH_STRING_DELIMITER_SLICE, - self.encr_algs_client_to_server, &SSH_HASSH_STRING_DELIMITER_SLICE, - self.mac_algs_client_to_server, &SSH_HASSH_STRING_DELIMITER_SLICE, - self.comp_algs_client_to_server] + pub fn generate_hassh( + &self, hassh_string: &mut Vec, hassh: &mut Vec, to_server: &bool, + ) { + let slices = if *to_server { + [ + self.kex_algs, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.encr_algs_server_to_client, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.mac_algs_server_to_client, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.comp_algs_server_to_client, + ] + } else { + [ + self.kex_algs, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.encr_algs_client_to_server, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.mac_algs_client_to_server, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.comp_algs_client_to_server, + ] }; // reserving memory hassh_string.reserve_exact(slices.iter().fold(0, |acc, x| acc + x.len())); // copying slices to hassh string - slices.iter().for_each(|&x| hassh_string.extend_from_slice(x)); + slices + .iter() + .for_each(|&x| hassh_string.extend_from_slice(x)); hassh.extend(format!("{:x}", Md5::new().chain(&hassh_string).finalize()).as_bytes()); } } -named!(parse_string<&[u8]>, do_parse!( - len: be_u32 >> - string: take!(len) >> - ( string ) -)); +#[inline] +fn parse_string(i: &[u8]) -> IResult<&[u8], &[u8]> { + length_data(be_u32)(i) +} -named!(pub ssh_parse_key_exchange, do_parse!( - cookie: take!(16) >> - kex_algs: parse_string >> - server_host_key_algs: parse_string >> - encr_algs_client_to_server: parse_string >> - encr_algs_server_to_client: parse_string >> - mac_algs_client_to_server: parse_string >> - mac_algs_server_to_client: parse_string >> - comp_algs_client_to_server: parse_string >> - comp_algs_server_to_client: parse_string >> - langs_client_to_server: parse_string >> - langs_server_to_client: parse_string >> - first_kex_packet_follows: be_u8 >> - reserved: be_u32 >> - ( SshPacketKeyExchange { - cookie: cookie, - kex_algs: kex_algs, - server_host_key_algs: server_host_key_algs, - encr_algs_client_to_server: encr_algs_client_to_server, - encr_algs_server_to_client: encr_algs_server_to_client, - mac_algs_client_to_server: mac_algs_client_to_server, - mac_algs_server_to_client: mac_algs_server_to_client, - comp_algs_client_to_server: comp_algs_client_to_server, - comp_algs_server_to_client: comp_algs_server_to_client, - langs_client_to_server: langs_client_to_server, - langs_server_to_client: langs_server_to_client, - first_kex_packet_follows: first_kex_packet_follows, - reserved: reserved, - } ) -)); +pub fn ssh_parse_key_exchange(i: &[u8]) -> IResult<&[u8], SshPacketKeyExchange> { + let (i, cookie) = take(16_usize)(i)?; + let (i, kex_algs) = parse_string(i)?; + let (i, server_host_key_algs) = parse_string(i)?; + let (i, encr_algs_client_to_server) = parse_string(i)?; + let (i, encr_algs_server_to_client) = parse_string(i)?; + let (i, mac_algs_client_to_server) = parse_string(i)?; + let (i, mac_algs_server_to_client) = parse_string(i)?; + let (i, comp_algs_client_to_server) = parse_string(i)?; + let (i, comp_algs_server_to_client) = parse_string(i)?; + let (i, langs_client_to_server) = parse_string(i)?; + let (i, langs_server_to_client) = parse_string(i)?; + let (i, first_kex_packet_follows) = be_u8(i)?; + let (i, reserved) = be_u32(i)?; + Ok(( + i, + SshPacketKeyExchange { + cookie, + kex_algs, + server_host_key_algs, + encr_algs_client_to_server, + encr_algs_server_to_client, + mac_algs_client_to_server, + mac_algs_server_to_client, + comp_algs_client_to_server, + comp_algs_server_to_client, + langs_client_to_server, + langs_server_to_client, + first_kex_packet_follows, + reserved, + }, + )) +} #[cfg(test)] mod tests { use super::*; + use nom7::{Err, Needed}; /// Simple test of some valid data. #[test] @@ -695,7 +721,7 @@ mod tests { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; if let Err(e) = ssh_parse_key_exchange(&server_key_exchange) { - assert_eq!(e, nom::Err::Incomplete(nom::Needed::Size(16755))); + assert_eq!(e, Err::Incomplete(Needed::new(15964))); } else { panic!("ssh_parse_key_exchange() parsed malicious key_exchange"); diff --git a/rust/src/ssh/ssh.rs b/rust/src/ssh/ssh.rs index 7a09b6db07..85c09d9967 100644 --- a/rust/src/ssh/ssh.rs +++ b/rust/src/ssh/ssh.rs @@ -19,6 +19,7 @@ use super::parser; use crate::applayer::*; use crate::core::STREAM_TOSERVER; use crate::core::{self, AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use nom7::Err; use std::ffi::CString; use std::sync::atomic::{AtomicBool, Ordering}; @@ -200,7 +201,7 @@ impl SSHState { input = rem; //header and complete data (not returned) } - Err(nom::Err::Incomplete(_)) => { + Err(Err::Incomplete(_)) => { match parser::ssh_parse_record_header(input) { Ok((rem, head)) => { SCLogDebug!("SSH valid record header {}", head); @@ -231,7 +232,7 @@ impl SSHState { } return AppLayerResult::ok(); } - Err(nom::Err::Incomplete(_)) => { + Err(Err::Incomplete(_)) => { //we may have consumed data from previous records if input.len() < SSH_RECORD_HEADER_LEN { //do not trust nom incomplete value