]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
nfs2: basic record parsing and tracking
authorVictor Julien <victor@inliniac.net>
Mon, 12 Jun 2017 07:41:31 +0000 (09:41 +0200)
committerVictor Julien <victor@inliniac.net>
Fri, 16 Jun 2017 11:11:36 +0000 (13:11 +0200)
rust/src/nfs/mod.rs
rust/src/nfs/nfs2_records.rs [new file with mode: 0644]
rust/src/nfs/nfs3.rs
src/app-layer-nfs3-udp.c

index 275dee954001dafc362599a99aba81af7496a03f..0b515a7c9f5f2a5827b0ce8fe9246997c8251fc2 100644 (file)
@@ -18,6 +18,7 @@
 pub mod types;
 #[macro_use]
 pub mod parser;
+pub mod nfs2_records;
 pub mod nfs3;
 pub mod log;
 
diff --git a/rust/src/nfs/nfs2_records.rs b/rust/src/nfs/nfs2_records.rs
new file mode 100644 (file)
index 0000000..0d8733d
--- /dev/null
@@ -0,0 +1,113 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+//! Nom parsers for NFSv2 records
+use nom::{be_u32, rest};
+use nfs::parser::*;
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs2Handle<'a> {
+    pub value: &'a[u8],
+}
+
+named!(pub parse_nfs2_handle<Nfs2Handle>,
+    do_parse!(
+        handle: take!(32)
+        >> (
+            Nfs2Handle {
+                value:handle,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs2RequestLookup<'a> {
+    pub handle: Nfs2Handle<'a>,
+    pub name_vec: Vec<u8>,
+}
+
+named!(pub parse_nfs2_request_lookup<Nfs2RequestLookup>,
+    do_parse!(
+            handle: parse_nfs2_handle
+        >>  name_len: be_u32
+        >>  name_contents: take!(name_len)
+        >>  name_padding: rest
+        >> (
+            Nfs2RequestLookup {
+                handle:handle,
+                name_vec:name_contents.to_vec(),
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs2RequestRead<'a> {
+    pub handle: Nfs2Handle<'a>,
+    pub offset: u32,
+}
+
+named!(pub parse_nfs2_request_read<Nfs2RequestRead>,
+    do_parse!(
+            handle: parse_nfs2_handle
+        >>  offset: be_u32
+        >>  count: be_u32
+        >> (
+            Nfs2RequestRead {
+                handle:handle,
+                offset:offset,
+            }
+        ))
+);
+
+named!(pub parse_nfs2_reply_read<Nfs3ReplyRead>,
+    do_parse!(
+            status: be_u32
+        >>  attr_blob: take!(68)
+        >>  data_len: be_u32
+        >>  data_contents: rest
+        >> (
+            Nfs3ReplyRead {
+                status:status,
+                attr_follows:1,
+                attr_blob:attr_blob,
+                count:data_len,
+                eof:false,
+                data_len:data_len,
+                data:data_contents,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs2Attributes<> {
+    pub atype: u32,
+    pub asize: u32,
+}
+
+named!(pub parse_nfs2_attribs<Nfs2Attributes>,
+    do_parse!(
+            atype: be_u32
+        >>  blob1: take!(16)
+        >>  asize: be_u32
+        >>  blob2: take!(44)
+        >> (
+            Nfs2Attributes {
+                atype:atype,
+                asize:asize,
+            }
+        ))
+);
index 0adbc73703e2d5ce8b868420c25fb8eb2056bc09..b0152f22b8fc8c508935fb1544bf84e6430b6952 100644 (file)
@@ -36,6 +36,7 @@ use filecontainer::*;
 
 use nfs::types::*;
 use nfs::parser::*;
+use nfs::nfs2_records::*;
 
 /// nom bug leads to this wrappers being necessary
 /// TODO for some reason putting these in parser.rs and making them public
@@ -196,6 +197,7 @@ impl NFS3Transaction {
 
 #[derive(Debug)]
 pub struct NFS3RequestXidMap {
+    progver: u32,
     procedure: u32,
     chunk_offset: u64,
     file_name:Vec<u8>,
@@ -205,9 +207,11 @@ pub struct NFS3RequestXidMap {
 }
 
 impl NFS3RequestXidMap {
-    pub fn new(procedure: u32, chunk_offset: u64) -> NFS3RequestXidMap {
+    pub fn new(progver: u32, procedure: u32, chunk_offset: u64) -> NFS3RequestXidMap {
         NFS3RequestXidMap {
-            procedure:procedure, chunk_offset:chunk_offset,
+            progver:progver,
+            procedure:procedure,
+            chunk_offset:chunk_offset,
             file_name:Vec::new(),
             file_handle:Vec::new(),
         }
@@ -417,7 +421,7 @@ impl NFS3State {
                 SCLogDebug!("LOOKUP {:?}", lookup);
                 xidmap.file_name = lookup.name_vec;
             },
-            IResult::Incomplete(_) => { panic!("WEIRD"); },
+            IResult::Incomplete(_) => { panic!("WEIRD: parse_nfs3_request_lookup said: incomplete"); },
             IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
         };
     }
@@ -440,7 +444,7 @@ impl NFS3State {
         SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}",
                 r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
 
-        let mut xidmap = NFS3RequestXidMap::new(r.procedure, 0);
+        let mut xidmap = NFS3RequestXidMap::new(r.progver, r.procedure, 0);
         let mut aux_file_name = Vec::new();
 
         if r.procedure == NFSPROC3_LOOKUP {
@@ -589,6 +593,69 @@ impl NFS3State {
         0
     }
 
+    /// complete request record
+    fn process_request_record_v2<'b>(&mut self, r: &RpcPacket<'b>) -> u32 {
+        SCLogDebug!("NFSv2 REQUEST {} procedure {} ({}) blob size {}",
+                r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
+
+        let mut xidmap = NFS3RequestXidMap::new(r.progver, r.procedure, 0);
+        let aux_file_name = Vec::new();
+
+        if r.procedure == NFSPROC3_LOOKUP {
+            match parse_nfs2_request_lookup(r.prog_data) {
+                IResult::Done(_, ar) => {
+                    xidmap.file_handle = ar.handle.value.to_vec();
+                    self.xidmap_handle2name(&mut xidmap);
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        } else if r.procedure == NFSPROC3_READ {
+            match parse_nfs2_request_read(r.prog_data) {
+                IResult::Done(_, read_record) => {
+                    xidmap.chunk_offset = read_record.offset as u64;
+                    xidmap.file_handle = read_record.handle.value.to_vec();
+                    self.xidmap_handle2name(&mut xidmap);
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        }
+
+        if !(r.procedure == NFSPROC3_COMMIT || // commit handled separately
+             r.procedure == NFSPROC3_WRITE  || // write handled in file tx
+             r.procedure == NFSPROC3_READ)     // read handled in file tx at reply
+        {
+            let mut tx = self.new_tx();
+            tx.xid = r.hdr.xid;
+            tx.procedure = r.procedure;
+            tx.request_done = true;
+            tx.file_name = xidmap.file_name.to_vec();
+            //self.ts_txs_updated = true;
+
+            if r.procedure == NFSPROC3_RENAME {
+                tx.type_data = Some(NFS3TransactionTypeData::RENAME(aux_file_name));
+            }
+
+            match &r.creds_unix {
+                &Some(ref u) => {
+                    tx.request_machine_name = u.machine_name_buf.to_vec();
+                    tx.request_uid = u.uid;
+                    tx.request_gid = u.gid;
+                    tx.has_creds = true;
+                },
+                _ => { },
+            }
+            SCLogDebug!("NFSv2 TX created: ID {} XID {} PROCEDURE {}",
+                    tx.id, tx.xid, tx.procedure);
+            self.transactions.push(tx);
+        }
+
+        SCLogDebug!("NFSv2: TS creating xidmap {}", r.hdr.xid);
+        self.requestmap.insert(r.hdr.xid, xidmap);
+        0
+    }
+
     fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
         -> (&mut NFS3Transaction, &mut FileContainer, u16)
     {
@@ -704,27 +771,15 @@ impl NFS3State {
             panic!("call me for procedure WRITE *only*");
         }
 
-        let mut xidmap = NFS3RequestXidMap::new(r.procedure, 0);
+        let mut xidmap = NFS3RequestXidMap::new(r.progver, r.procedure, 0);
         xidmap.file_handle = w.handle.value.to_vec();
         self.requestmap.insert(r.hdr.xid, xidmap);
 
         return self.process_write_record(r, w);
     }
 
-    fn process_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>) -> u32 {
+    fn process_reply_record_v3<'b>(&mut self, r: &RpcReplyPacket<'b>, xidmap: &mut NFS3RequestXidMap) -> u32 {
         let status;
-        let xidmap;
-        match self.requestmap.remove(&r.hdr.xid) {
-            Some(p) => { xidmap = p; },
-            _ => {
-                SCLogDebug!("REPLY: xid {} NOT FOUND. GAPS? TS:{} TC:{}",
-                        r.hdr.xid, self.ts_ssn_gap, self.tc_ssn_gap);
-
-                // TODO we might be able to try to infer from the size + data
-                // that this is a READ reply and pass the data to the file API anyway?
-                return 0;
-            },
-        }
 
         if xidmap.procedure == NFSPROC3_LOOKUP {
             match parse_nfs3_response_lookup(r.prog_data) {
@@ -735,7 +790,7 @@ impl NFS3State {
                     status = lookup.status;
 
                     SCLogDebug!("LOOKUP handle {:?}", lookup.handle);
-                    self.namemap.insert(lookup.handle.value.to_vec(), xidmap.file_name);
+                    self.namemap.insert(lookup.handle.value.to_vec(), xidmap.file_name.to_vec());
                 },
                 IResult::Incomplete(_) => { panic!("WEIRD"); },
                 IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
@@ -751,7 +806,7 @@ impl NFS3State {
                     match nfs3_create_record.handle {
                         Some(h) => {
                             SCLogDebug!("handle {:?}", h);
-                            self.namemap.insert(h.value.to_vec(), xidmap.file_name);
+                            self.namemap.insert(h.value.to_vec(), xidmap.file_name.to_vec());
                         },
                         _ => { },
                     }
@@ -829,6 +884,63 @@ impl NFS3State {
         0
     }
 
+    fn process_reply_record_v2<'b>(&mut self, r: &RpcReplyPacket<'b>, xidmap: &NFS3RequestXidMap) -> u32 {
+        let status;
+
+        if xidmap.procedure == NFSPROC3_READ {
+            match parse_nfs2_reply_read(r.prog_data) {
+                IResult::Done(_, ref reply) => {
+                    SCLogDebug!("NFSv2 READ reply record");
+                    self.process_read_record(r, reply, Some(&xidmap));
+                    status = reply.status;
+                },
+                IResult::Incomplete(_) => { panic!("Incomplete!"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+            }
+        } else {
+            let stat = match nom::be_u32(&r.prog_data) {
+                nom::IResult::Done(_, stat) => {
+                    stat as u32
+                }
+                _ => 0 as u32
+            };
+            status = stat;
+        }
+        SCLogDebug!("REPLY {} to procedure {} blob size {}",
+                r.hdr.xid, xidmap.procedure, r.prog_data.len());
+
+        self.mark_response_tx_done(r.hdr.xid, status);
+
+        0
+    }
+
+    fn process_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>) -> u32 {
+        let mut xidmap;
+        match self.requestmap.remove(&r.hdr.xid) {
+            Some(p) => { xidmap = p; },
+            _ => {
+                SCLogDebug!("REPLY: xid {} NOT FOUND. GAPS? TS:{} TC:{}",
+                        r.hdr.xid, self.ts_ssn_gap, self.tc_ssn_gap);
+
+                // TODO we might be able to try to infer from the size + data
+                // that this is a READ reply and pass the data to the file API anyway?
+                return 0;
+            },
+        }
+
+        match xidmap.progver {
+            3 => {
+                SCLogDebug!("NFSv3 reply record");
+                return self.process_reply_record_v3(r, &mut xidmap);
+            },
+            2 => {
+                SCLogDebug!("NFSv2 reply record");
+                return self.process_reply_record_v2(r, &xidmap);
+            },
+            _ => { panic!("unsupported NFS version"); },
+        }
+    }
+
     // update in progress chunks for file transfers
     // return how much data we consumed
     fn filetracker_update(&mut self, direction: u8, data: &[u8], gap_size: u32) -> u32 {
@@ -931,12 +1043,14 @@ impl NFS3State {
         let file_name;
         let file_handle;
         let chunk_offset;
+        let nfs_version;
 
         match xidmapr {
             Some(xidmap) => {
                 file_name = xidmap.file_name.to_vec();
                 file_handle = xidmap.file_handle.to_vec();
                 chunk_offset = xidmap.chunk_offset;
+                nfs_version = xidmap.progver;
             },
             None => {
                 match self.requestmap.get(&r.hdr.xid) {
@@ -944,19 +1058,36 @@ impl NFS3State {
                         file_name = xidmap.file_name.to_vec();
                         file_handle = xidmap.file_handle.to_vec();
                         chunk_offset = xidmap.chunk_offset;
+                        nfs_version = xidmap.progver;
                     },
                     _ => { panic!("REPLY: xid {} NOT FOUND", r.hdr.xid); },
                 }
             },
         }
 
-        let is_last = reply.eof;
+        let mut is_last = reply.eof;
         let mut fill_bytes = 0;
         let pad = reply.count % 4;
         if pad != 0 {
             fill_bytes = 4 - pad;
         }
 
+        if nfs_version == 2 {
+            let size = match parse_nfs2_attribs(reply.attr_blob) {
+                IResult::Done(_, ref attr) => {
+                    attr.asize
+                },
+                _ => { 0 },
+            };
+            SCLogDebug!("NFSv2 READ reply record: File size {}. Offset {} data len {}: total {}",
+                    size, chunk_offset, reply.data_len, chunk_offset + reply.data_len as u64);
+
+            if size as u64 == chunk_offset + reply.data_len as u64 {
+                is_last = true;
+            }
+
+        }
+
         let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOCLIENT) {
             Some((tx, files, flags)) => {
                 let ref mut tdf = match tx.type_data {
@@ -1336,7 +1467,15 @@ impl NFS3State {
             match parse_rpc_udp_request(input) {
                 IResult::Done(_, ref rpc_record) => {
                     self.is_udp = true;
-                    status |= self.process_request_record(rpc_record);
+                    match rpc_record.progver {
+                        3 => {
+                            status |= self.process_request_record(rpc_record);
+                        },
+                        2 => {
+                            status |= self.process_request_record_v2(rpc_record);
+                        },
+                        _ => { panic!("unsupported NFS version"); },
+                    }
                 },
                 IResult::Incomplete(_) => {
                 },
@@ -1678,7 +1817,7 @@ pub fn nfs3_probe(i: &[u8], direction: u8) -> i8 {
         match parse_rpc_reply(i) {
             IResult::Done(_, 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 {
-                    SCLogNotice!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
+                    SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
                     return 1;
                 } else {
                     return -1;
@@ -1688,7 +1827,7 @@ pub fn nfs3_probe(i: &[u8], direction: u8) -> i8 {
                 match parse_rpc_packet_header (i) {
                     IResult::Done(_, ref rpc_hdr) => {
                         if rpc_hdr.frag_len >= 24 && rpc_hdr.frag_len <= 35000 && rpc_hdr.xid != 0 && rpc_hdr.msgtype == 1 {
-                            SCLogNotice!("TC PROBE LEN {} XID {} TYPE {}", rpc_hdr.frag_len, rpc_hdr.xid, rpc_hdr.msgtype);
+                            SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc_hdr.frag_len, rpc_hdr.xid, rpc_hdr.msgtype);
                             return 1;
                         } else {
                             return -1;
@@ -1731,7 +1870,7 @@ pub fn nfs3_probe_udp(i: &[u8], direction: u8) -> i8 {
         match parse_rpc_udp_reply(i) {
             IResult::Done(_, ref rpc) => {
                 if i.len() >= 32 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 {
-                    SCLogNotice!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
+                    SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
                     return 1;
                 } else {
                     return -1;
@@ -1749,6 +1888,9 @@ pub fn nfs3_probe_udp(i: &[u8], direction: u8) -> i8 {
             IResult::Done(_, ref rpc) => {
                 if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 3 && rpc.program == 100003 {
                     return 1;
+                } else if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 2 && rpc.program == 100003 {
+                    SCLogDebug!("NFSv2!");
+                    return 1;
                 } else {
                     return -1;
                 }
index bbd3ebe5c2313740481a69ced8270c4ea2865c59..b615d40c567dca65dd0189480cdfaada240953e7 100644 (file)
@@ -143,44 +143,44 @@ static int NFS3HasEvents(void *state)
 static AppProto NFS3ProbingParserTS(uint8_t *input, uint32_t input_len,
     uint32_t *offset)
 {
-    SCLogNotice("probing");
+    SCLogDebug("probing");
     if (input_len < NFS3_MIN_FRAME_LEN) {
-        SCLogNotice("unknown");
+        SCLogDebug("unknown");
         return ALPROTO_UNKNOWN;
     }
 
     int8_t r = rs_nfs_probe_udp_ts(input, input_len);
     if (r == 1) {
-        SCLogNotice("nfs3");
+        SCLogDebug("nfs3");
         return ALPROTO_NFS3;
     } else if (r == -1) {
-        SCLogNotice("failed");
+        SCLogDebug("failed");
         return ALPROTO_FAILED;
     }
 
-    SCLogNotice("Protocol not detected as ALPROTO_NFS3.");
+    SCLogDebug("Protocol not detected as ALPROTO_NFS3.");
     return ALPROTO_UNKNOWN;
 }
 
 static AppProto NFS3ProbingParserTC(uint8_t *input, uint32_t input_len,
     uint32_t *offset)
 {
-    SCLogNotice("probing");
+    SCLogDebug("probing");
     if (input_len < NFS3_MIN_FRAME_LEN) {
-        SCLogNotice("unknown");
+        SCLogDebug("unknown");
         return ALPROTO_UNKNOWN;
     }
 
     int8_t r = rs_nfs_probe_tc(input, input_len);
     if (r == 1) {
-        SCLogNotice("nfs3");
+        SCLogDebug("nfs3");
         return ALPROTO_NFS3;
     } else if (r == -1) {
-        SCLogNotice("failed");
+        SCLogDebug("failed");
         return ALPROTO_FAILED;
     }
 
-    SCLogNotice("Protocol not detected as ALPROTO_NFS3.");
+    SCLogDebug("Protocol not detected as ALPROTO_NFS3.");
     return ALPROTO_UNKNOWN;
 }
 
@@ -286,13 +286,13 @@ void RegisterNFS3UDPParsers(void)
 
         rs_nfs3_init(&sfc);
 
-        SCLogNotice("NFS3 UDP protocol detection enabled.");
+        SCLogDebug("NFS3 UDP protocol detection enabled.");
 
         AppLayerProtoDetectRegisterProtocol(ALPROTO_NFS3, proto_name);
 
         if (RunmodeIsUnittests()) {
 
-            SCLogNotice("Unittest mode, registering default configuration.");
+            SCLogDebug("Unittest mode, registering default configuration.");
             AppLayerProtoDetectPPRegister(IPPROTO_UDP, NFS3_DEFAULT_PORT,
                 ALPROTO_NFS3, 0, NFS3_MIN_FRAME_LEN, STREAM_TOSERVER,
                 NFS3ProbingParserTS, NFS3ProbingParserTC);
@@ -303,7 +303,7 @@ void RegisterNFS3UDPParsers(void)
             if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP,
                     proto_name, ALPROTO_NFS3, 0, NFS3_MIN_FRAME_LEN,
                     NFS3ProbingParserTS, NFS3ProbingParserTC)) {
-                SCLogNotice("No NFS3 app-layer configuration, enabling NFS3"
+                SCLogDebug("No NFS3 app-layer configuration, enabling NFS3"
                     " detection TCP detection on port %s.",
                     NFS3_DEFAULT_PORT);
                 AppLayerProtoDetectPPRegister(IPPROTO_UDP,
@@ -317,13 +317,13 @@ void RegisterNFS3UDPParsers(void)
     }
 
     else {
-        SCLogNotice("Protocol detecter and parser disabled for NFS3.");
+        SCLogDebug("Protocol detecter and parser disabled for NFS3.");
         return;
     }
 
     if (AppLayerParserConfParserEnabled("udp", proto_name))
     {
-        SCLogNotice("Registering NFS3 protocol parser.");
+        SCLogDebug("Registering NFS3 protocol parser.");
 
         /* Register functions for state allocation and freeing. A
          * state is allocated for every new NFS3 flow. */