From c90e9c396c917e9495f8f4cc6ac731c9d9fd1b8a Mon Sep 17 00:00:00 2001 From: Victor Julien Date: Fri, 18 Mar 2022 15:33:27 -0600 Subject: [PATCH] nfs/rpc: update full record parsers to be more exact Instead of 'take'ing all data for the RPC prog_data and then letting the higher level parsers figure out which part to use take the exact amount. Note: Differs a bit from the original commit as this series of commits was not backported in the same order. (cherry picked from commit 64d8a1e16e07148a8b5839452be3f7481e4e3623) --- rust/src/nfs/nfs.rs | 27 +++++++++---------- rust/src/nfs/rpc_records.rs | 53 +++++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/rust/src/nfs/nfs.rs b/rust/src/nfs/nfs.rs index 33f90fbde1..c13958187b 100644 --- a/rust/src/nfs/nfs.rs +++ b/rust/src/nfs/nfs.rs @@ -1149,7 +1149,7 @@ impl NFSState { SCLogDebug!("CONFIRMED WRITE: large record {}, file chunk xfer", rec_size); // lets try to parse the RPC record. Might fail with Incomplete. - match parse_rpc(cur_i) { + match parse_rpc(cur_i, false) { Ok((remaining, ref rpc_record)) => { match parse_nfs3_request_write(rpc_record.prog_data, false) { Ok((_, ref nfs_request_write)) => { @@ -1184,17 +1184,11 @@ impl NFSState { // we have the full records size worth of data, // let's parse it - match parse_rpc(&cur_i[..rec_size]) { + match parse_rpc(cur_i, true) { Ok((_, ref rpc_record)) => { - cur_i = &cur_i[rec_size..]; status |= self.process_request_record(rpc_record); }, Err(nom::Err::Incomplete(_)) => { - cur_i = &cur_i[rec_size..]; // progress input past parsed record - - // we shouldn't get incomplete as we have the full data - // so if we got incomplete anyway it's the data that is - // bad. self.set_event(NFSEvent::MalformedData); status = 1; @@ -1206,12 +1200,15 @@ impl NFSState { return 1; }, } + cur_i = &cur_i[rec_size..]; }, Err(nom::Err::Incomplete(_)) => { SCLogDebug!("Fragmentation required (TCP level) 2"); self.tcp_buffer_ts.extend_from_slice(cur_i); break; }, + /* This error is fatal. If we failed to parse the RPC hdr we don't + * have a length and we don't know where the next record starts. */ Err(nom::Err::Error(_e)) | Err(nom::Err::Failure(_e)) => { self.set_event(NFSEvent::MalformedData); @@ -1312,7 +1309,7 @@ impl NFSState { SCLogDebug!("CONFIRMED large READ record {}, likely file chunk xfer", rec_size); // we should have enough data to parse the RPC record - match parse_rpc_reply(cur_i) { + match parse_rpc_reply(cur_i, false) { Ok((remaining, ref rpc_record)) => { match parse_nfs3_reply_read(rpc_record.prog_data, false) { Ok((_, ref nfs_reply_read)) => { @@ -1350,14 +1347,11 @@ impl NFSState { } // we have the full data of the record, lets parse - match parse_rpc_reply(&cur_i[..rec_size]) { + match parse_rpc_reply(cur_i, true) { Ok((_, ref rpc_record)) => { - cur_i = &cur_i[rec_size..]; // progress input past parsed record status |= self.process_reply_record(rpc_record); }, Err(nom::Err::Incomplete(_)) => { - cur_i = &cur_i[rec_size..]; // progress input past parsed record - // we shouldn't get incomplete as we have the full data // so if we got incomplete anyway it's the data that is // bad. @@ -1372,12 +1366,15 @@ impl NFSState { return 1; } } + cur_i = &cur_i[rec_size..]; // progress input past parsed record }, Err(nom::Err::Incomplete(_)) => { SCLogDebug!("REPLY: insufficient data for HDR"); self.tcp_buffer_tc.extend_from_slice(cur_i); break; }, + /* This error is fatal. If we failed to parse the RPC hdr we don't + * have a length and we don't know where the next record starts. */ Err(nom::Err::Error(_e)) | Err(nom::Err::Failure(_e)) => { self.set_event(NFSEvent::MalformedData); @@ -1842,7 +1839,7 @@ fn nfs_probe_dir(i: &[u8], rdir: *mut u8) -> i8 { pub fn nfs_probe(i: &[u8], direction: u8) -> i8 { if direction == STREAM_TOCLIENT { - match parse_rpc_reply(i) { + match parse_rpc_reply(i, false) { Ok((_, ref rpc)) => { if rpc.hdr.frag_len >= 24 && rpc.hdr.frag_len <= 35000 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); @@ -1875,7 +1872,7 @@ pub fn nfs_probe(i: &[u8], direction: u8) -> i8 { }, } } else { - match parse_rpc(i) { + match parse_rpc(i, false) { Ok((_, ref rpc)) => { if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 && rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) && diff --git a/rust/src/nfs/rpc_records.rs b/rust/src/nfs/rpc_records.rs index 23d574279f..1ddf651c44 100644 --- a/rust/src/nfs/rpc_records.rs +++ b/rust/src/nfs/rpc_records.rs @@ -17,7 +17,7 @@ //! Nom parsers for RPCv2 -use nom::{be_u32, rest}; +use nom::{be_u32, rest, rest_len}; use nom::IResult; pub const RPC_MAX_MACHINE_SIZE: u32 = 256; // Linux kernel defines 64. @@ -153,6 +153,7 @@ pub struct RpcReplyPacket<'a> { pub reply_state: u32, pub accept_state: u32, + pub prog_data_size: u32, pub prog_data: &'a[u8], } @@ -201,10 +202,22 @@ pub struct RpcPacket<'a> { pub verifier_len: u32, pub verifier: &'a[u8], + pub prog_data_size: u32, pub prog_data: &'a[u8], } -pub fn parse_rpc(start_i: &[u8]) -> IResult<&[u8], RpcPacket> { +/// Parse request RPC record. +/// +/// Can be called from 2 paths: +/// 1. we have all data -> do full parsing +/// 2. we have partial data (large records) -> allow partial prog_data parsing +/// +/// Arguments: +/// * `complete`: +/// type: bool +/// description: do full parsing, including of `prog_data` +/// +pub fn parse_rpc(start_i: &[u8], complete: bool) -> IResult<&[u8], RpcPacket> { let (i, hdr) = parse_rpc_packet_header(start_i)?; let rec_size = hdr.frag_len as usize + 4; @@ -228,8 +241,12 @@ pub fn parse_rpc(start_i: &[u8]) -> IResult<&[u8], RpcPacket> { if consumed > rec_size as usize { return Err(nom::Err::Error(error_position!(i, nom::ErrorKind::LengthValue))); } - - let (i, prog_data) = rest(i)?; + let data_size: u32 = (rec_size as usize - consumed) as u32; + let (i, prog_data) = if !complete { + rest(i)? + } else { + take!(i, data_size)? + }; Ok((i, RpcPacket { hdr, @@ -242,12 +259,23 @@ pub fn parse_rpc(start_i: &[u8]) -> IResult<&[u8], RpcPacket> { verifier_flavor, verifier_len, verifier, + prog_data_size: data_size, prog_data, })) } -// to be called with data <= hdr.frag_len + 4. Sending more data is undefined. -pub fn parse_rpc_reply(start_i: &[u8]) -> IResult<&[u8], RpcReplyPacket> { +/// Parse reply RPC record. +/// +/// Can be called from 2 paths: +/// 1. we have all data -> do full parsing +/// 2. we have partial data (large records) -> allow partial prog_data parsing +/// +/// Arguments: +/// * `complete`: +/// type: bool +/// description: do full parsing, including of `prog_data` +/// +pub fn parse_rpc_reply(start_i: &[u8], complete: bool) -> IResult<&[u8], RpcReplyPacket> { let (i, hdr) = parse_rpc_packet_header(start_i)?; let rec_size = hdr.frag_len + 4; @@ -264,7 +292,12 @@ pub fn parse_rpc_reply(start_i: &[u8]) -> IResult<&[u8], RpcReplyPacket> { return Err(nom::Err::Error(error_position!(i, nom::ErrorKind::LengthValue))); } - let (i, prog_data) = rest(i)?; + let data_size: u32 = (rec_size as usize - consumed) as u32; + let (i, prog_data) = if !complete { + rest(i)? + } else { + take!(i, data_size)? + }; let packet = RpcReplyPacket { hdr, @@ -275,7 +308,7 @@ pub fn parse_rpc_reply(start_i: &[u8]) -> IResult<&[u8], RpcReplyPacket> { reply_state, accept_state, - //prog_data_size: data_size, + prog_data_size: data_size, prog_data, }; Ok((i, packet)) @@ -315,6 +348,7 @@ named!(pub parse_rpc_udp_request, >> verifier_len: verify!(be_u32, |size| size < RPC_MAX_VERIFIER_SIZE) >> verifier: take!(verifier_len as usize) + >> prog_data_size: rest_len >> pl: rest >> ( @@ -333,6 +367,7 @@ named!(pub parse_rpc_udp_request, verifier_len:verifier_len, verifier:verifier, + prog_data_size: prog_data_size as u32, prog_data:pl, } )) @@ -349,6 +384,7 @@ named!(pub parse_rpc_udp_reply, >> reply_state: verify!(be_u32, |v| v <= 1) >> accept_state: be_u32 + >> data_size: rest_len >> pl: rest >> ( @@ -362,6 +398,7 @@ named!(pub parse_rpc_udp_reply, reply_state:reply_state, accept_state:accept_state, + prog_data_size: data_size as u32, prog_data:pl, } )) -- 2.47.2