]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
nfs/rpc: update full record parsers to be more exact 7151/head
authorVictor Julien <vjulien@oisf.net>
Fri, 18 Mar 2022 21:33:27 +0000 (15:33 -0600)
committerJason Ish <jason.ish@oisf.net>
Fri, 18 Mar 2022 22:16:57 +0000 (16:16 -0600)
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
rust/src/nfs/rpc_records.rs

index 33f90fbde1b9e88094f76f1769251db3f599575f..c13958187b4ca5022bfb4326859824bdbbd2e861 100644 (file)
@@ -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) &&
index 23d574279fe4a9a06ef0f7be8dc87ae5c40a33b3..1ddf651c44a4e48058743559dc742b934eae8a13 100644 (file)
@@ -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<RpcPacket>,
        >> 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<RpcPacket>,
                 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<RpcReplyPacket>,
        >> 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<RpcReplyPacket>,
                 reply_state:reply_state,
                 accept_state:accept_state,
 
+                prog_data_size: data_size as u32,
                 prog_data:pl,
            }
    ))