]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
nfs3: fix partial write record handling
authorVictor Julien <vjulien@oisf.net>
Fri, 18 Mar 2022 18:02:45 +0000 (12:02 -0600)
committerJason Ish <jason.ish@oisf.net>
Fri, 18 Mar 2022 18:03:21 +0000 (12:03 -0600)
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)

rust/src/nfs/nfs.rs
rust/src/nfs/nfs3.rs
rust/src/nfs/nfs3_records.rs
rust/src/nfs/rpc_records.rs

index 8518a8c27842eb5be8fb150794d518e47ff807b0..a4876f3e32d43aad20546f9c03f60f6faf2fbbb3 100644 (file)
@@ -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);
index bf76274c92f4cf03957c28adadf0b10815b617c1..3e587c3ce443aeccb711e83e8ff12c6a7268b7d4 100644 (file)
@@ -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);
                 },
index b8920f5f52f1695268c2e75f11df40a9d5a92cd8..66bba96e3540aa8fb6fa89cee82af5aac686da8b 100644 (file)
@@ -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<Nfs3RequestWrite>,
-    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> {
index c6a37c63b83c0bc52d335900db46d1de20cfe123..d05e1f68a7ca779fcce99ce1d06d642b15b79ffd 100644 (file)
@@ -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<RpcPacket>,
-   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<RpcReplyPacket>,