smb: enforce the write/read queue limits

The SMB1 WRITE_ANDX/READ_ANDX handlers fed the file tracker
unconditionally and SMB2 only checked raw in-flight occupancy. Apply
the max-{read,write}-queue-{size,cnt} checks, projected onto actual
queue growth, to both protocols' found and create-new tx branches;
on overflow raise the queue event and skip the record's remainder.

Ticket: #8791.
pull/16213/head
Victor Julien 2 months ago committed by Jason Ish
parent 96d943d1c1
commit b39984c87e

@ -19,6 +19,7 @@ use crate::applayer::AppLayerGetFileStateRust;
use crate::core::*;
use crate::direction::Direction;
use crate::filetracker::*;
use crate::smb::events::SMBEvent;
use std;
use crate::smb::smb::*;
@ -246,6 +247,39 @@ impl SMBState {
}
}
/// Event to raise if enqueuing `len` bytes at `offset` would exceed the
/// configured SMB queue limits. OOO data at a new offset grows the queue;
/// in-order appends drain it and are never rejected.
pub(crate) fn smb_queue_limit_event(
ft: &FileTransferTracker, offset: u64, len: u64, to_client: bool,
) -> Option<SMBEvent> {
let (max_size, max_cnt, ev_size, ev_cnt) = if to_client {
(
unsafe { SMB_CFG_MAX_READ_QUEUE_SIZE },
unsafe { SMB_CFG_MAX_READ_QUEUE_CNT },
SMBEvent::ReadQueueSizeExceeded,
SMBEvent::ReadQueueCntExceeded,
)
} else {
(
unsafe { SMB_CFG_MAX_WRITE_QUEUE_SIZE },
unsafe { SMB_CFG_MAX_WRITE_QUEUE_CNT },
SMBEvent::WriteQueueSizeExceeded,
SMBEvent::WriteQueueCntExceeded,
)
};
if max_size != 0
&& ft.is_ooo_offset(offset)
&& ft.get_inflight_size() + len > u64::from(max_size)
{
return Some(ev_size);
}
if max_cnt != 0 && ft.new_chunk_entry(offset) && ft.get_inflight_cnt() + 1 > max_cnt as usize {
return Some(ev_cnt);
}
None
}
use crate::applayer::AppLayerGetFileState;
pub(super) unsafe extern "C" fn smb_gettxfiles(

@ -1078,16 +1078,29 @@ pub fn smb1_write_request_record(
if rd.offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
rd.offset,
rd.len,
rd.len as u64,
false,
&file_id,
);
SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx);
) {
// Attach to the affected file tx (not the newest tx).
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = true;
state.set_skip(Direction::ToServer, nbss_remaining);
} else {
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
rd.offset,
rd.len,
false,
&file_id,
);
}
SCLogDebug!("FID {:?} found at tx {}", file_fid, file_id);
}
true
}
@ -1109,25 +1122,44 @@ pub fn smb1_write_request_record(
else {
return;
};
tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX);
SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx);
if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
let file_id: u32 = tx.id as u32;
if rd.offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
tdf.share_name = share_name;
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
rd.offset,
rd.len,
rd.len as u64,
false,
&file_id,
);
tdf.share_name = share_name;
SCLogDebug!("tdf {:?}", tdf);
) {
// tx just created: set the event on it directly.
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = 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.tx_data.0.files_opened = 0;
state.set_skip(Direction::ToServer, nbss_remaining);
// The trailing set_file_left also claims these bytes: safe -- records
// only reach here with no chunk armed, and handle_skip runs first.
} else {
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
rd.offset,
rd.len,
false,
&file_id,
);
}
}
tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX);
SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx);
}
}
if set_event_fileoverlap {
@ -1206,15 +1238,28 @@ pub fn smb1_read_response_record(
if offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
offset,
rd.len,
false,
&file_id,
);
rd.len as u64,
true,
) {
// Attach to the affected file tx (not the newest tx).
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = true;
state.set_skip(Direction::ToClient, nbss_remaining);
} else {
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
offset,
rd.len,
false,
&file_id,
);
}
}
true
}
@ -1226,24 +1271,44 @@ pub fn smb1_read_response_record(
else {
return;
};
tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX);
SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id);
if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
let file_id: u32 = tx.id as u32;
SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id);
if offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
offset,
rd.len,
false,
&file_id,
);
tdf.share_name = share_name;
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
offset,
rd.len as u64,
true,
) {
// tx just created: set the event on it directly.
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = 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.tx_data.0.files_opened = 0;
// The handler tail below also arms set_file_left for these bytes:
// safe -- reached only with no chunk armed, handle_skip first.
state.set_skip(Direction::ToClient, nbss_remaining);
} else {
filetracker_newchunk(
&mut tdf.file_tracker,
&file_name,
rd.data,
offset,
rd.len,
false,
&file_id,
);
}
}
tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX);
}
if set_event_fileoverlap {
state.set_event(SMBEvent::FileOverlap);

@ -116,9 +116,6 @@ fn smb2_read_response_record_generic(state: &mut SMBState, r: &Smb2Record) {
}
pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) {
let max_queue_size = unsafe { SMB_CFG_MAX_READ_QUEUE_SIZE };
let max_queue_cnt = unsafe { SMB_CFG_MAX_READ_QUEUE_CNT };
smb2_read_response_record_generic(state, r);
match parse_smb2_response_read(r.data) {
@ -172,16 +169,13 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema
if offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
if max_queue_size != 0
&& tdf.file_tracker.get_inflight_size() + rd.len as u64
> u64::from(max_queue_size)
{
state.set_event(SMBEvent::ReadQueueSizeExceeded);
state.set_skip(Direction::ToClient, nbss_remaining);
} else if max_queue_cnt != 0
&& tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize
if let Some(ev) =
smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true)
{
state.set_event(SMBEvent::ReadQueueCntExceeded);
// Attach to the affected file tx, not the newest tx.
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = true;
state.set_skip(Direction::ToClient, nbss_remaining);
} else {
filetracker_newchunk(
@ -264,17 +258,21 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema
if offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
if max_queue_size != 0
&& tdf.file_tracker.get_inflight_size() + rd.len as u64
> u64::from(max_queue_size)
{
state.set_event(SMBEvent::ReadQueueSizeExceeded);
state.set_skip(Direction::ToClient, nbss_remaining);
} else if max_queue_cnt != 0
&& tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize
if let Some(ev) =
smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true)
{
state.set_event(SMBEvent::ReadQueueCntExceeded);
// tx just created: set the event on it directly.
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = 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.tx_data.0.files_opened = 0;
state.set_skip(Direction::ToClient, nbss_remaining);
// The trailing set_file_left also claims these bytes: safe -- records
// only reach here with no chunk armed, and handle_skip runs first.
} else {
filetracker_newchunk(
&mut tdf.file_tracker,
@ -308,9 +306,6 @@ pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_rema
}
pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) {
let max_queue_size = unsafe { SMB_CFG_MAX_WRITE_QUEUE_SIZE };
let max_queue_cnt = unsafe { SMB_CFG_MAX_WRITE_QUEUE_CNT };
SCLogDebug!("SMBv2/WRITE: request record");
if smb2_create_new_tx(r.command) {
let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
@ -355,16 +350,16 @@ pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_rema
if wr.wr_offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
if max_queue_size != 0
&& tdf.file_tracker.get_inflight_size() + wr.wr_len as u64
> u64::from(max_queue_size)
{
state.set_event(SMBEvent::WriteQueueSizeExceeded);
state.set_skip(Direction::ToServer, nbss_remaining);
} else if max_queue_cnt != 0
&& tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize
{
state.set_event(SMBEvent::WriteQueueCntExceeded);
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
wr.wr_offset,
wr.wr_len as u64,
false,
) {
// Attach to the affected file tx, not the newest tx.
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = true;
state.set_skip(Direction::ToServer, nbss_remaining);
} else {
filetracker_newchunk(
@ -443,17 +438,24 @@ pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_rema
set_event_fileoverlap = true;
}
if max_queue_size != 0
&& tdf.file_tracker.get_inflight_size() + wr.wr_len as u64
> u64::from(max_queue_size)
{
state.set_event(SMBEvent::WriteQueueSizeExceeded);
state.set_skip(Direction::ToServer, nbss_remaining);
} else if max_queue_cnt != 0
&& tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize
{
state.set_event(SMBEvent::WriteQueueCntExceeded);
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
wr.wr_offset,
wr.wr_len as u64,
false,
) {
// tx just created: set the event on it directly.
tx.set_event(ev);
tx.tx_data.0.updated_ts = true;
tx.tx_data.0.updated_tc = 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.tx_data.0.files_opened = 0;
state.set_skip(Direction::ToServer, nbss_remaining);
// The trailing set_file_left also claims these bytes: safe -- records
// only reach here with no chunk armed, and handle_skip runs first.
} else {
filetracker_newchunk(
&mut tdf.file_tracker,

Loading…
Cancel
Save