nfs2: basic record parsing and tracking

pull/2787/head
Victor Julien 8 years ago
parent c7e10c73f9
commit 5153271b87

@ -18,6 +18,7 @@
pub mod types;
#[macro_use]
pub mod parser;
pub mod nfs2_records;
pub mod nfs3;
pub mod log;

@ -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,
}
))
);

@ -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;
}

@ -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. */

Loading…
Cancel
Save