nfs: keep partial FILE_SYNC write tx open until its data is done

A NFSv3 WRITE with stable set to FILE_SYNC is assumed to be the last chunk of
its file, and its file transaction was closed as soon as the record was seen.
If the record was only partially received, the continuation could no longer
find the transaction by its file handle. Its file data was then not consumed
but parsed as a new RPC record, which failed and disabled the parser for the
flow, and the file was truncated.

Like the READ reply path already does, only close the transaction right away
if the record holds all of its file data. Otherwise filetracker_update closes
it once the file tracker is done or, as a truncated tracker is never done,
once the data of the last chunk is complete. The latter also applies to a
partial READ reply with eof set.

The NFSv4 WRITE path makes the same FILE_SYNC assumption, but it only handles
complete records, so it is not affected.

This is also the root cause of Redmine #8009.

Ticket: #9196
pull/16514/head
Jason Ish 2 weeks ago committed by Victor Julien
parent 1dcb549109
commit 15f9403d9e

@ -1158,6 +1158,9 @@ impl NFSState {
// for now assume that stable FILE_SYNC flags means a single chunk
let is_last = w.stable == 2;
// a partial record's file tx must stay open (found by handle) until
// filetracker_update has received the rest of its data
let is_partial = w.file_data.len() < w.file_len as usize;
let file_handle = w.handle.value.to_vec();
let file_name = if let Some(name) = self.namemap.get(w.handle.value) {
SCLogDebug!("WRITE name {:?}", name);
@ -1243,7 +1246,9 @@ impl NFSState {
tdf.file_last_xid = r.hdr.xid;
tx.is_last = true;
tx.response_done = true;
tx.is_file_closed = true;
if !is_partial {
tx.is_file_closed = true;
}
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToClient as i32,
@ -1289,12 +1294,16 @@ impl NFSState {
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;
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToServer as i32,
);
/* if this is a partial record we will close the tx
* when we've received the final data */
if !is_partial {
tx.request_done = true;
tx.is_file_closed = true;
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToServer as i32,
);
}
}
}
tx.procedure = NFSPROC3_WRITE;
@ -1706,8 +1715,10 @@ impl NFSState {
tdf.chunk_count += 1;
let cs = filetracker_update(&mut tdf.file_tracker, data, gap_size);
/* see if we need to close the tx */
if tdf.file_tracker.is_done() {
/* see if we need to close the tx. A truncated tracker is
* never done, so also close it when the data of its last
* chunk is complete. */
if tdf.file_tracker.is_done() || (tx.is_last && chunk_left == 0) {
if direction == Direction::ToClient {
tx.response_done = true;
tx.is_file_closed = true;

Loading…
Cancel
Save