From: Victor Julien Date: Fri, 18 Mar 2022 18:02:45 +0000 (-0600) Subject: nfs3: fix partial write record handling X-Git-Tag: suricata-5.0.9~56 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9e527f26925a4ecaac5a35e420cdb3de23cf5826;p=thirdparty%2Fsuricata.git nfs3: fix partial write record handling Note: This was more of a manual cherry-pick converting some parsers from named macros into functions in order to handle more arguments than just the input data -- Jason Ish (cherry picked from commit 4418fc1b02f47533439fe00789d9c850a24271b2) --- diff --git a/rust/src/nfs/nfs.rs b/rust/src/nfs/nfs.rs index 8518a8c278..a4876f3e32 100644 --- a/rust/src/nfs/nfs.rs +++ b/rust/src/nfs/nfs.rs @@ -658,15 +658,19 @@ impl NFSState { } pub fn process_write_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { - // for now assume that stable FILE_SYNC flags means a single chunk - let is_last = if w.stable == 2 { true } else { false }; - let mut fill_bytes = 0; - let pad = w.file_len % 4; + let pad = w.count % 4; if pad != 0 { fill_bytes = 4 - pad; } + // linux defines a max of 1mb. Allow several multiples. + if w.count == 0 || w.count > 16777216 { + return 0; + } + + // for now assume that stable FILE_SYNC flags means a single chunk + let is_last = if w.stable == 2 { true } else { false }; let file_handle = w.handle.value.to_vec(); let file_name = match self.namemap.get(w.handle.value) { Some(n) => { @@ -717,8 +721,8 @@ impl NFSState { } if !self.is_udp { self.ts_chunk_xid = r.hdr.xid; - let file_data_len = w.file_data.len() as u32 - fill_bytes as u32; - self.ts_chunk_left = w.file_len as u32 - file_data_len as u32; + //debug_validate_bug_on!(w.file_data.len() as u32 > w.count); + self.ts_chunk_left = w.count - w.file_data.len() as u32; self.ts_chunk_fh = file_handle; SCLogDebug!("REQUEST chunk_xid {:04X} chunk_left {}", self.ts_chunk_xid, self.ts_chunk_left); } @@ -1141,7 +1145,7 @@ impl NFSState { // lets try to parse the RPC record. Might fail with Incomplete. match parse_rpc(cur_i) { Ok((remaining, ref rpc_record)) => { - match parse_nfs3_request_write(rpc_record.prog_data) { + match parse_nfs3_request_write(rpc_record.prog_data, false) { Ok((_, ref nfs_request_write)) => { // deal with the partial nfs write data status |= self.process_partial_write_request_record(rpc_record, nfs_request_write); diff --git a/rust/src/nfs/nfs3.rs b/rust/src/nfs/nfs3.rs index bf76274c92..3e587c3ce4 100644 --- a/rust/src/nfs/nfs3.rs +++ b/rust/src/nfs/nfs3.rs @@ -83,7 +83,7 @@ impl NFSState { }, }; } else if r.procedure == NFSPROC3_WRITE { - match parse_nfs3_request_write(r.prog_data) { + match parse_nfs3_request_write(r.prog_data, true) { Ok((_, w)) => { self.process_write_record(r, &w); }, diff --git a/rust/src/nfs/nfs3_records.rs b/rust/src/nfs/nfs3_records.rs index b8920f5f52..66bba96e35 100644 --- a/rust/src/nfs/nfs3_records.rs +++ b/rust/src/nfs/nfs3_records.rs @@ -17,6 +17,7 @@ //! Nom parsers for RPC & NFSv3 +use std::cmp; use nom::{IResult, be_u32, be_u64, rest}; use crate::nfs::nfs_records::*; @@ -389,25 +390,45 @@ pub struct Nfs3RequestWrite<'a> { pub file_data: &'a[u8], } -named!(pub parse_nfs3_request_write, - do_parse!( - handle: parse_nfs3_handle - >> offset: be_u64 - >> count: be_u32 - >> stable: verify!(be_u32, |v| v <= 2) - >> file_len: verify!(be_u32, |v| v <= count) - >> file_data: rest // likely partial - >> ( - Nfs3RequestWrite { - handle:handle, - offset:offset, - count:count, - stable:stable, - file_len:file_len, - file_data:file_data, - } - )) -); +/// Complete data expected +fn parse_nfs3_request_write_data_complete(i: &[u8], file_len: usize, fill_bytes: usize) -> IResult<&[u8], &[u8]> { + let (i, file_data) = take!(i, file_len as usize)?; + let (i, _) = cond!(i, fill_bytes > 0, take!(fill_bytes))?; + Ok((i, file_data)) +} + +/// Partial data. We have all file_len, but need to consider fill_bytes +fn parse_nfs3_request_write_data_partial(i: &[u8], file_len: usize, fill_bytes: usize) -> IResult<&[u8], &[u8]> { + let (i, file_data) = take!(i, file_len as usize)?; + let fill_bytes = cmp::min(fill_bytes as usize, i.len()); + let (i, _) = cond!(i, fill_bytes > 0, take!(fill_bytes))?; + Ok((i, file_data)) +} + +pub fn parse_nfs3_request_write(i: &[u8], complete: bool) -> IResult<&[u8], Nfs3RequestWrite> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, offset) = be_u64(i)?; + let (i, count) = be_u32(i)?; + let (i, stable) = verify!(i, be_u32, |v| v <= 2)?; + let (i, file_len) = verify!(i, be_u32, |v| v <= count)?; + let fill_bytes = if file_len % 4 != 0 { 4 - file_len % 4 } else { 0 }; + let (i, file_data) = if complete { + parse_nfs3_request_write_data_complete(i, file_len as usize, fill_bytes as usize)? + } else if i.len() >= file_len as usize { + parse_nfs3_request_write_data_partial(i, file_len as usize, fill_bytes as usize)? + } else { + rest(i)? + }; + Ok((i, Nfs3RequestWrite { + handle, + offset, + count, + stable, + file_len, + file_data, + })) +} + /* #[derive(Debug,PartialEq)] pub struct Nfs3ReplyRead<'a> { diff --git a/rust/src/nfs/rpc_records.rs b/rust/src/nfs/rpc_records.rs index c6a37c63b8..d05e1f68a7 100644 --- a/rust/src/nfs/rpc_records.rs +++ b/rust/src/nfs/rpc_records.rs @@ -18,6 +18,7 @@ //! Nom parsers for RPCv2 use nom::{be_u32, rest}; +use nom::IResult; pub const RPC_MAX_MACHINE_SIZE: u32 = 256; // Linux kernel defines 64. pub const RPC_MAX_CREDS_SIZE: u32 = 4096; // Linux kernel defines 400. @@ -203,48 +204,47 @@ pub struct RpcPacket<'a> { pub prog_data: &'a[u8], } -named!(pub parse_rpc, - do_parse!( - hdr: parse_rpc_packet_header - - >> rpcver: be_u32 - >> program: be_u32 - >> progver: be_u32 - >> procedure: be_u32 - - >> creds_flavor: be_u32 - >> creds_len: verify!(be_u32, |size| size < RPC_MAX_CREDS_SIZE) - >> creds: flat_map!(take!(creds_len), switch!(value!(creds_flavor), - 1 => call!(parse_rpc_request_creds_unix) | - 6 => call!(parse_rpc_request_creds_gssapi) | - _ => call!(parse_rpc_request_creds_unknown) )) - - >> verifier_flavor: be_u32 - >> verifier_len: verify!(be_u32, |size| size < RPC_MAX_VERIFIER_SIZE) - >> verifier: take!(verifier_len as usize) - - >> pl: rest - - >> ( - RpcPacket { - hdr:hdr, - - rpcver:rpcver, - program:program, - progver:progver, - procedure:procedure, - - creds_flavor:creds_flavor, - creds:creds, - - verifier_flavor:verifier_flavor, - verifier_len:verifier_len, - verifier:verifier, +pub fn parse_rpc(start_i: &[u8]) -> IResult<&[u8], RpcPacket> { + let (i, hdr) = parse_rpc_packet_header(start_i)?; + let rec_size = hdr.frag_len as usize + 4; + + let (i, rpcver) = be_u32(i)?; + let (i, program) = be_u32(i)?; + let (i, progver) = be_u32(i)?; + let (i, procedure) = be_u32(i)?; + + let (i, creds_flavor) = be_u32(i)?; + let (i, creds_len) = verify!(i, be_u32, |size| size < RPC_MAX_CREDS_SIZE)?; + let (i, creds) = flat_map!(i, take!(creds_len), switch!(value!(creds_flavor), + 1 => call!(parse_rpc_request_creds_unix) | + 6 => call!(parse_rpc_request_creds_gssapi) | + _ => call!(parse_rpc_request_creds_unknown) ))?; + + let (i, verifier_flavor) = be_u32(i)?; + let (i, verifier_len) = verify!(i, be_u32, |size| size < RPC_MAX_VERIFIER_SIZE)?; + let (i, verifier) = take!(i, verifier_len as usize)?; + + let consumed = start_i.len() - i.len(); + if consumed > rec_size as usize { + return Err(nom::Err::Error(error_position!(i, nom::ErrorKind::LengthValue))); + } - prog_data:pl, - } - )) -); + let (i, prog_data) = rest(i)?; + + Ok((i, RpcPacket { + hdr, + rpcver, + program, + progver, + procedure, + creds_flavor, + creds, + verifier_flavor, + verifier_len, + verifier, + prog_data, + })) +} // to be called with data <= hdr.frag_len + 4. Sending more data is undefined. named!(pub parse_rpc_reply,