From: Victor Julien Date: Mon, 12 Jun 2017 07:41:31 +0000 (+0200) Subject: nfs2: basic record parsing and tracking X-Git-Tag: suricata-4.0.0-rc1~51 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5153271b87ef41e22c43c6e50645ef5859c88bea;p=thirdparty%2Fsuricata.git nfs2: basic record parsing and tracking --- diff --git a/rust/src/nfs/mod.rs b/rust/src/nfs/mod.rs index 275dee9540..0b515a7c9f 100644 --- a/rust/src/nfs/mod.rs +++ b/rust/src/nfs/mod.rs @@ -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 index 0000000000..0d8733d346 --- /dev/null +++ b/rust/src/nfs/nfs2_records.rs @@ -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, + do_parse!( + handle: take!(32) + >> ( + Nfs2Handle { + value:handle, + } + )) +); + +#[derive(Debug,PartialEq)] +pub struct Nfs2RequestLookup<'a> { + pub handle: Nfs2Handle<'a>, + pub name_vec: Vec, +} + +named!(pub parse_nfs2_request_lookup, + 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, + do_parse!( + handle: parse_nfs2_handle + >> offset: be_u32 + >> count: be_u32 + >> ( + Nfs2RequestRead { + handle:handle, + offset:offset, + } + )) +); + +named!(pub parse_nfs2_reply_read, + 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, + do_parse!( + atype: be_u32 + >> blob1: take!(16) + >> asize: be_u32 + >> blob2: take!(44) + >> ( + Nfs2Attributes { + atype:atype, + asize:asize, + } + )) +); diff --git a/rust/src/nfs/nfs3.rs b/rust/src/nfs/nfs3.rs index 0adbc73703..b0152f22b8 100644 --- a/rust/src/nfs/nfs3.rs +++ b/rust/src/nfs/nfs3.rs @@ -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, @@ -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, file_name: &Vec, 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; } diff --git a/src/app-layer-nfs3-udp.c b/src/app-layer-nfs3-udp.c index bbd3ebe5c2..b615d40c56 100644 --- a/src/app-layer-nfs3-udp.c +++ b/src/app-layer-nfs3-udp.c @@ -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. */