summaryrefslogtreecommitdiffstats
path: root/rust/src/nfs/nfs4.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/nfs/nfs4.rs')
-rw-r--r--rust/src/nfs/nfs4.rs425
1 files changed, 425 insertions, 0 deletions
diff --git a/rust/src/nfs/nfs4.rs b/rust/src/nfs/nfs4.rs
new file mode 100644
index 0000000..730e82b
--- /dev/null
+++ b/rust/src/nfs/nfs4.rs
@@ -0,0 +1,425 @@
+/* Copyright (C) 2018-2020 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.
+ */
+
+// written by Victor Julien
+
+use nom7::bytes::streaming::take;
+use nom7::number::streaming::be_u32;
+use nom7::{Err, IResult};
+
+use crate::core::*;
+use crate::nfs::nfs::*;
+use crate::nfs::nfs4_records::*;
+use crate::nfs::nfs_records::*;
+use crate::nfs::rpc_records::*;
+use crate::nfs::types::*;
+
+use crate::kerberos::{parse_kerberos5_request, Kerberos5Ticket, SecBlobError};
+
+fn parse_req_gssapi(i: &[u8]) -> IResult<&[u8], Kerberos5Ticket, SecBlobError> {
+ let (i, len) = be_u32(i)?;
+ let (i, buf) = take(len as usize)(i)?;
+ let (_, ap) = parse_kerberos5_request(buf)?;
+ Ok((i, ap))
+}
+
+impl NFSState {
+ /* normal write: PUTFH (file handle), WRITE (write opts/data). File handle
+ * is not part of the write record itself so we pass it in here. */
+ fn write_v4<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs4RequestWrite<'b>, fh: &'b [u8]) {
+ // for now assume that stable FILE_SYNC flags means a single chunk
+ let is_last = w.stable == 2;
+ SCLogDebug!("is_last {}", is_last);
+
+ let mut fill_bytes = 0;
+ let pad = w.write_len % 4;
+ if pad != 0 {
+ fill_bytes = 4 - pad;
+ }
+
+ // linux defines a max of 1mb. Allow several multiples.
+ if w.write_len == 0 || w.write_len > 16777216 {
+ return;
+ }
+
+ let file_handle = fh.to_vec();
+ let file_name = if let Some(name) = self.namemap.get(fh) {
+ SCLogDebug!("WRITE name {:?}", name);
+ name.to_vec()
+ } else {
+ SCLogDebug!("WRITE object {:?} not found", w.stateid.data);
+ Vec::new()
+ };
+
+ let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
+ Some(tx) => {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, w.data, w.offset,
+ w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
+ tdf.chunk_count += 1;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.response_done = true;
+ }
+ }
+ true
+ }
+ None => false,
+ };
+ if !found {
+ let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer);
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, w.data, w.offset,
+ w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
+ tx.procedure = NFSPROC4_WRITE;
+ tx.xid = r.hdr.xid;
+ tx.is_first = true;
+ tx.nfs_version = r.progver as u16;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ }
+ }
+ }
+ self.ts_chunk_xid = r.hdr.xid;
+ debug_validate_bug_on!(w.data.len() as u32 > w.write_len);
+ self.ts_chunk_left = w.write_len - w.data.len() as u32;
+ }
+
+ fn close_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) {
+ self.commit_v4(r, fh)
+ }
+
+ fn commit_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) {
+ SCLogDebug!("COMMIT, closing shop");
+
+ let file_handle = fh.to_vec();
+ if let Some(tx) = self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_close(&mut tdf.file_tracker);
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ }
+ }
+ }
+
+ fn new_tx_v4(
+ &mut self, r: &RpcPacket, xidmap: &NFSRequestXidMap, procedure: u32,
+ _aux_opcodes: &[u32],
+ ) {
+ let mut tx = self.new_tx();
+ tx.xid = r.hdr.xid;
+ tx.procedure = procedure;
+ tx.request_done = true;
+ tx.file_name = xidmap.file_name.to_vec();
+ tx.nfs_version = r.progver as u16;
+ tx.file_handle = xidmap.file_handle.to_vec();
+
+ tx.auth_type = r.creds_flavor;
+ #[allow(clippy::single_match)]
+ match r.creds {
+ RpcRequestCreds::Unix(ref u) => {
+ tx.request_machine_name = u.machine_name_buf.to_vec();
+ tx.request_uid = u.uid;
+ tx.request_gid = u.gid;
+ }
+ _ => {}
+ }
+ SCLogDebug!(
+ "NFSv4: TX created: ID {} XID {} PROCEDURE {}",
+ tx.id,
+ tx.xid,
+ tx.procedure
+ );
+ self.transactions.push(tx);
+ }
+
+ /* A normal READ request looks like: PUTFH (file handle) READ (read opts).
+ * We need the file handle for the READ.
+ */
+ fn compound_request<'b>(
+ &mut self, r: &RpcPacket<'b>, cr: &Nfs4RequestCompoundRecord<'b>,
+ xidmap: &mut NFSRequestXidMap,
+ ) {
+ let mut last_putfh: Option<&'b [u8]> = None;
+ let mut main_opcode: u32 = 0;
+ let mut aux_opcodes: Vec<u32> = Vec::new();
+
+ for c in &cr.commands {
+ SCLogDebug!("c {:?}", c);
+ match *c {
+ Nfs4RequestContent::PutFH(ref rd) => {
+ last_putfh = Some(rd.value);
+ aux_opcodes.push(NFSPROC4_PUTFH);
+ }
+ Nfs4RequestContent::Read(ref rd) => {
+ SCLogDebug!("READv4: {:?}", rd);
+ if let Some(fh) = last_putfh {
+ xidmap.chunk_offset = rd.offset;
+ xidmap.file_handle = fh.to_vec();
+ self.xidmap_handle2name(xidmap);
+ }
+ }
+ Nfs4RequestContent::Open(ref rd) => {
+ SCLogDebug!("OPENv4: {}", String::from_utf8_lossy(rd.filename));
+ xidmap.file_name = rd.filename.to_vec();
+ }
+ Nfs4RequestContent::Lookup(ref rd) => {
+ SCLogDebug!("LOOKUPv4: {}", String::from_utf8_lossy(rd.filename));
+ xidmap.file_name = rd.filename.to_vec();
+ }
+ Nfs4RequestContent::Write(ref rd) => {
+ SCLogDebug!("WRITEv4: {:?}", rd);
+ if let Some(fh) = last_putfh {
+ self.write_v4(r, rd, fh);
+ }
+ }
+ Nfs4RequestContent::Commit => {
+ SCLogDebug!("COMMITv4");
+ if let Some(fh) = last_putfh {
+ self.commit_v4(r, fh);
+ }
+ }
+ Nfs4RequestContent::Close(ref _rd) => {
+ SCLogDebug!("CLOSEv4: {:?}", _rd);
+ if let Some(fh) = last_putfh {
+ self.close_v4(r, fh);
+ }
+ }
+ Nfs4RequestContent::Create(ref rd) => {
+ SCLogDebug!("CREATEv4: {:?}", rd);
+ if let Some(fh) = last_putfh {
+ xidmap.file_handle = fh.to_vec();
+ }
+ xidmap.file_name = rd.filename.to_vec();
+ main_opcode = NFSPROC4_CREATE;
+ }
+ Nfs4RequestContent::Remove(rd) => {
+ SCLogDebug!("REMOVEv4: {:?}", rd);
+ xidmap.file_name = rd.to_vec();
+ main_opcode = NFSPROC4_REMOVE;
+ }
+ Nfs4RequestContent::SetClientId(ref _rd) => {
+ SCLogDebug!(
+ "SETCLIENTIDv4: client id {} r_netid {} r_addr {}",
+ String::from_utf8_lossy(_rd.client_id),
+ String::from_utf8_lossy(_rd.r_netid),
+ String::from_utf8_lossy(_rd.r_addr)
+ );
+ }
+ _ => {}
+ }
+ }
+
+ if main_opcode != 0 {
+ self.new_tx_v4(r, xidmap, main_opcode, &aux_opcodes);
+ }
+ }
+
+ /// complete request record
+ pub fn process_request_record_v4(&mut self, r: &RpcPacket) {
+ SCLogDebug!(
+ "NFSv4 REQUEST {} procedure {} ({}) blob size {}",
+ r.hdr.xid,
+ r.procedure,
+ self.requestmap.len(),
+ r.prog_data.len()
+ );
+
+ let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
+
+ if r.procedure == NFSPROC4_NULL {
+ if let RpcRequestCreds::GssApi(ref creds) = r.creds {
+ if creds.procedure == 1 {
+ let _x = parse_req_gssapi(r.prog_data);
+ SCLogDebug!("RPCSEC_GSS_INIT {:?}", _x);
+ }
+ }
+ } else if r.procedure == NFSPROC4_COMPOUND {
+ let mut data = r.prog_data;
+
+ if let RpcRequestCreds::GssApi(ref creds) = r.creds {
+ if creds.procedure == 0 && creds.service == 2 {
+ SCLogDebug!("GSS INTEGRITY: {:?}", creds);
+ match parse_rpc_gssapi_integrity(r.prog_data) {
+ Ok((_rem, rec)) => {
+ SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
+ data = rec.data;
+ // store proc and serv for the reply
+ xidmap.gssapi_proc = creds.procedure;
+ xidmap.gssapi_service = creds.service;
+ }
+ Err(Err::Incomplete(_n)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!(
+ "NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}",
+ _e
+ );
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ }
+ }
+ }
+
+ match parse_nfs4_request_compound(data) {
+ Ok((_, rd)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND: {:?}", rd);
+ self.compound_request(r, &rd, &mut xidmap);
+ }
+ Err(Err::Incomplete(_n)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND: INCOMPLETE {:?}", _n);
+ self.set_event(NFSEvent::MalformedData);
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND: Parsing failed: {:?}", _e);
+ self.set_event(NFSEvent::MalformedData);
+ }
+ };
+ }
+
+ self.requestmap.insert(r.hdr.xid, xidmap);
+ }
+
+ fn compound_response<'b>(
+ &mut self, r: &RpcReplyPacket<'b>, cr: &Nfs4ResponseCompoundRecord<'b>,
+ xidmap: &mut NFSRequestXidMap,
+ ) {
+ let mut insert_filename_with_getfh = false;
+ let mut main_opcode_status: u32 = 0;
+ let mut main_opcode_status_set: bool = false;
+
+ for c in &cr.commands {
+ SCLogDebug!("c {:?}", c);
+ match *c {
+ Nfs4ResponseContent::ReadDir(_s, Some(ref rd)) => {
+ SCLogDebug!("READDIRv4: status {} eof {}", _s, rd.eof);
+
+ #[allow(clippy::manual_flatten)]
+ for d in &rd.listing {
+ if let Some(_d) = d {
+ SCLogDebug!("READDIRv4: dir {}", String::from_utf8_lossy(_d.name));
+ }
+ }
+ }
+ Nfs4ResponseContent::Remove(s) => {
+ SCLogDebug!("REMOVE4: status {}", s);
+ main_opcode_status = s;
+ main_opcode_status_set = true;
+ }
+ Nfs4ResponseContent::Create(s) => {
+ SCLogDebug!("CREATE4: status {}", s);
+ main_opcode_status = s;
+ main_opcode_status_set = true;
+ }
+ Nfs4ResponseContent::Read(s, Some(ref rd)) => {
+ SCLogDebug!(
+ "READ4: xidmap {:?} status {} data {}",
+ xidmap,
+ s,
+ rd.data.len()
+ );
+ // convert record to generic read reply
+ let reply = NfsReplyRead {
+ status: s,
+ attr_follows: 0,
+ attr_blob: &[],
+ count: rd.count,
+ eof: rd.eof,
+ data_len: rd.data.len() as u32,
+ data: rd.data,
+ };
+ self.process_read_record(r, &reply, Some(xidmap));
+ }
+ Nfs4ResponseContent::Open(_s, Some(ref _rd)) => {
+ SCLogDebug!("OPENv4: status {} opendata {:?}", _s, _rd);
+ insert_filename_with_getfh = true;
+ }
+ Nfs4ResponseContent::GetFH(_s, Some(ref rd)) => {
+ if insert_filename_with_getfh {
+ self.namemap
+ .insert(rd.value.to_vec(), xidmap.file_name.to_vec());
+ }
+ }
+ Nfs4ResponseContent::PutRootFH(s) => {
+ if s == NFS4_OK && xidmap.file_name.is_empty() {
+ xidmap.file_name = b"<mount_root>".to_vec();
+ SCLogDebug!("filename {:?}", xidmap.file_name);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ if main_opcode_status_set {
+ let resp_handle = Vec::new();
+ self.mark_response_tx_done(r.hdr.xid, r.reply_state, main_opcode_status, &resp_handle);
+ }
+ }
+
+ pub fn process_reply_record_v4(
+ &mut self, r: &RpcReplyPacket, xidmap: &mut NFSRequestXidMap,
+ ) {
+ if xidmap.procedure == NFSPROC4_COMPOUND {
+ let mut data = r.prog_data;
+
+ if xidmap.gssapi_proc == 0 && xidmap.gssapi_service == 2 {
+ SCLogDebug!("GSS INTEGRITY as set by call: {:?}", xidmap);
+ match parse_rpc_gssapi_integrity(r.prog_data) {
+ Ok((_rem, rec)) => {
+ SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
+ data = rec.data;
+ }
+ Err(Err::Incomplete(_n)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}", _e);
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ }
+ }
+ match parse_nfs4_response_compound(data) {
+ Ok((_, rd)) => {
+ SCLogDebug!("COMPOUNDv4: {:?}", rd);
+ self.compound_response(r, &rd, xidmap);
+ }
+ Err(Err::Incomplete(_)) => {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ self.set_event(NFSEvent::MalformedData);
+ }
+ };
+ }
+ }
+}