|
|
|
|
/* 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::direction::Direction;
|
|
|
|
|
use crate::flow::Flow;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
nfs: validate WRITE and READ records sizes
Replace the hardcoded 16 MiB record caps with configurable
max-{write,read}-size and observable events: an oversized WRITE
request or READ response is rejected, logged, and its payload skipped
to keep the stream in sync. 0 disables the check.
For v4 records, the issue is more complex due to the COMPOUND type:
A partial v4 COMPOUND record whose frag_len claim is huge must not be
buffered toward that claim (31-bit, attacker-controlled → OOM). The
full parser can't help: on a partial buffer it only returns Incomplete
("need more bytes"), with no information about what's coming. The scan
instead walks the buffered prefix's ops by wire extent and reads the
file op's header words alone, answering the gate's four-way question:
oversized claim detected (reject + skip to the record boundary before
any data buffers), data not yet present (buffer normally), structurall
malformed (bounded rejection), or no oversized op (fall through). It
exists because rejecting before buffering requires the claimed length,
which only a prefix walk can extract.
Ticket: #8791.
2 months ago
|
|
|
if w.write_len == 0 {
|
|
|
|
|
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()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Reject an oversized WRITE: log it as a tx and skip to the record
|
|
|
|
|
// boundary to keep the flow in sync.
|
nfs: validate WRITE and READ records sizes
Replace the hardcoded 16 MiB record caps with configurable
max-{write,read}-size and observable events: an oversized WRITE
request or READ response is rejected, logged, and its payload skipped
to keep the stream in sync. 0 disables the check.
For v4 records, the issue is more complex due to the COMPOUND type:
A partial v4 COMPOUND record whose frag_len claim is huge must not be
buffered toward that claim (31-bit, attacker-controlled → OOM). The
full parser can't help: on a partial buffer it only returns Incomplete
("need more bytes"), with no information about what's coming. The scan
instead walks the buffered prefix's ops by wire extent and reads the
file op's header words alone, answering the gate's four-way question:
oversized claim detected (reject + skip to the record boundary before
any data buffers), data not yet present (buffer normally), structurall
malformed (bounded rejection), or no oversized op (fall through). It
exists because rejecting before buffering requires the claimed length,
which only a prefix walk can extract.
Ticket: #8791.
2 months ago
|
|
|
let max_write_size = unsafe { NFS_CFG_MAX_WRITE_SIZE };
|
|
|
|
|
if max_write_size != 0 && w.write_len > max_write_size {
|
|
|
|
|
let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer);
|
|
|
|
|
tx.procedure = NFSPROC4_WRITE;
|
|
|
|
|
tx.xid = r.hdr.xid;
|
|
|
|
|
tx.is_first = true;
|
|
|
|
|
tx.nfs_version = r.progver as u16;
|
|
|
|
|
tx.request_done = true;
|
|
|
|
|
tx.response_done = true;
|
|
|
|
|
// Close the tx so a later same-handle op cannot reuse it.
|
|
|
|
|
tx.is_file_closed = true;
|
nfs: validate WRITE and READ records sizes
Replace the hardcoded 16 MiB record caps with configurable
max-{write,read}-size and observable events: an oversized WRITE
request or READ response is rejected, logged, and its payload skipped
to keep the stream in sync. 0 disables the check.
For v4 records, the issue is more complex due to the COMPOUND type:
A partial v4 COMPOUND record whose frag_len claim is huge must not be
buffered toward that claim (31-bit, attacker-controlled → OOM). The
full parser can't help: on a partial buffer it only returns Incomplete
("need more bytes"), with no information about what's coming. The scan
instead walks the buffered prefix's ops by wire extent and reads the
file op's header words alone, answering the gate's four-way question:
oversized claim detected (reject + skip to the record boundary before
any data buffers), data not yet present (buffer normally), structurall
malformed (bounded rejection), or no oversized op (fall through). It
exists because rejecting before buffering requires the claimed length,
which only a prefix walk can extract.
Ticket: #8791.
2 months ago
|
|
|
tx.tx_data.set_event(NFSEvent::WriteRequestTooLarge as u8);
|
|
|
|
|
// Complete the rejected transaction immediately: without
|
|
|
|
|
// this the tx and its event only surface at flow teardown.
|
|
|
|
|
tx.tx_data.0.updated_ts = true;
|
|
|
|
|
tx.tx_data.0.updated_tc = true;
|
nfs: validate WRITE and READ records sizes
Replace the hardcoded 16 MiB record caps with configurable
max-{write,read}-size and observable events: an oversized WRITE
request or READ response is rejected, logged, and its payload skipped
to keep the stream in sync. 0 disables the check.
For v4 records, the issue is more complex due to the COMPOUND type:
A partial v4 COMPOUND record whose frag_len claim is huge must not be
buffered toward that claim (31-bit, attacker-controlled → OOM). The
full parser can't help: on a partial buffer it only returns Incomplete
("need more bytes"), with no information about what's coming. The scan
instead walks the buffered prefix's ops by wire extent and reads the
file op's header words alone, answering the gate's four-way question:
oversized claim detected (reject + skip to the record boundary before
any data buffers), data not yet present (buffer normally), structurall
malformed (bounded rejection), or no oversized op (fall through). It
exists because rejecting before buffering requires the claimed length,
which only a prefix walk can extract.
Ticket: #8791.
2 months ago
|
|
|
// Rejected tx never opened a file: zero files_opened, else the tx
|
|
|
|
|
// (and file) stays live until teardown and grows unbounded on repeats.
|
|
|
|
|
tx.tx_data.0.files_opened = 0;
|
|
|
|
|
// Skip to the RPC record boundary: claim vs data can disagree, and
|
|
|
|
|
// claimed-count math lands mid-padding or in a later record.
|
|
|
|
|
let pending = r.prog_data_size.saturating_sub(r.prog_data.len() as u32);
|
nfs: validate WRITE and READ records sizes
Replace the hardcoded 16 MiB record caps with configurable
max-{write,read}-size and observable events: an oversized WRITE
request or READ response is rejected, logged, and its payload skipped
to keep the stream in sync. 0 disables the check.
For v4 records, the issue is more complex due to the COMPOUND type:
A partial v4 COMPOUND record whose frag_len claim is huge must not be
buffered toward that claim (31-bit, attacker-controlled → OOM). The
full parser can't help: on a partial buffer it only returns Incomplete
("need more bytes"), with no information about what's coming. The scan
instead walks the buffered prefix's ops by wire extent and reads the
file op's header words alone, answering the gate's four-way question:
oversized claim detected (reject + skip to the record boundary before
any data buffers), data not yet present (buffer normally), structurall
malformed (bounded rejection), or no oversized op (fall through). It
exists because rejecting before buffering requires the claimed length,
which only a prefix walk can extract.
Ticket: #8791.
2 months ago
|
|
|
self.set_skip(Direction::ToServer, pending);
|
|
|
|
|
self.ts_chunk_xid = 0;
|
|
|
|
|
self.ts_chunk_left = 0;
|
|
|
|
|
SCLogDebug!(
|
|
|
|
|
"WRITEv4 too large (claim {} > {}): skipping {} stream bytes",
|
|
|
|
|
w.write_len,
|
|
|
|
|
unsafe { NFS_CFG_MAX_WRITE_SIZE },
|
|
|
|
|
pending
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut queue_exceeded = false;
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
if let Some(event) =
|
|
|
|
|
write_queue_limit_event(&tdf.file_tracker, w.offset, w.data.len() as u64)
|
|
|
|
|
{
|
|
|
|
|
// Attach to the affected file tx, not the newest tx.
|
|
|
|
|
tx.tx_data.set_event(event as u8);
|
|
|
|
|
tx.tx_data.0.updated_ts = true;
|
|
|
|
|
tx.tx_data.0.updated_tc = true;
|
|
|
|
|
queue_exceeded = true;
|
|
|
|
|
} else {
|
|
|
|
|
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 {
|
|
|
|
|
if let Some(event) =
|
|
|
|
|
write_queue_limit_event(&tdf.file_tracker, w.offset, w.data.len() as u64)
|
|
|
|
|
{
|
|
|
|
|
// tx just created: set the event on it directly.
|
|
|
|
|
tx.tx_data.set_event(event as u8);
|
|
|
|
|
tx.tx_data.0.updated_ts = true;
|
|
|
|
|
tx.tx_data.0.updated_tc = true;
|
|
|
|
|
queue_exceeded = true;
|
|
|
|
|
// Chunk rejected before the file was opened: complete + close the
|
|
|
|
|
// tx and zero files_opened, else it dangles until teardown.
|
|
|
|
|
tx.request_done = true;
|
|
|
|
|
tx.response_done = true;
|
|
|
|
|
tx.is_file_closed = true;
|
|
|
|
|
tx.tx_data.0.files_opened = 0;
|
|
|
|
|
} else {
|
|
|
|
|
filetracker_newchunk(
|
|
|
|
|
&mut tdf.file_tracker,
|
|
|
|
|
&file_name,
|
|
|
|
|
w.data,
|
|
|
|
|
w.offset,
|
|
|
|
|
w.write_len,
|
|
|
|
|
fill_bytes as u8,
|
|
|
|
|
is_last,
|
|
|
|
|
&r.hdr.xid,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
if is_last && !queue_exceeded {
|
|
|
|
|
tdf.file_last_xid = r.hdr.xid;
|
|
|
|
|
tx.is_last = true;
|
|
|
|
|
tx.request_done = true;
|
|
|
|
|
tx.is_file_closed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tx.procedure = NFSPROC4_WRITE;
|
|
|
|
|
tx.xid = r.hdr.xid;
|
|
|
|
|
tx.is_first = true;
|
|
|
|
|
tx.nfs_version = r.progver as u16;
|
|
|
|
|
}
|
|
|
|
|
if queue_exceeded {
|
|
|
|
|
// Don't buffer the rest of the record: skip to its RPC boundary.
|
|
|
|
|
let pending = r.prog_data_size.saturating_sub(r.prog_data.len() as u32);
|
|
|
|
|
self.set_skip(Direction::ToServer, pending);
|
|
|
|
|
self.ts_chunk_xid = 0;
|
|
|
|
|
self.ts_chunk_left = 0;
|
|
|
|
|
self.ts_chunk_tail = 0;
|
|
|
|
|
SCLogDebug!("WRITE queue exceeded: skipping {} stream bytes", pending);
|
|
|
|
|
} else {
|
|
|
|
|
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;
|
|
|
|
|
// keep the record-tail bookkeeping in sync with the v3 path
|
|
|
|
|
self.ts_chunk_tail = r.prog_data_size.saturating_sub(r.prog_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.put(r.hdr.xid, xidmap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn compound_response<'b>(
|
|
|
|
|
&mut self, flow: *mut Flow, 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(flow, 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
|
|
|
|
|
.put(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(
|
|
|
|
|
flow,
|
|
|
|
|
r.hdr.xid,
|
|
|
|
|
r.reply_state,
|
|
|
|
|
main_opcode_status,
|
|
|
|
|
&resp_handle,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn process_reply_record_v4(
|
|
|
|
|
&mut self, flow: *mut Flow, 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(flow, 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);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|