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.
(cherry picked from commit b39984c87e)
pull/16211/head
Victor Julien 2 months ago committed by Jason Ish
parent 7ab8ba7b97
commit ca0d780fd5

@ -20,6 +20,7 @@ use crate::core::*;
use crate::direction::Direction;
use crate::filetracker::*;
use crate::filecontainer::*;
use crate::smb::events::SMBEvent;
use crate::smb::smb::*;
@ -232,6 +233,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(tx: *mut std::ffi::c_void, direction: u8) -> AppLayerGetFileState {

@ -977,22 +977,42 @@ pub fn smb1_write_request_record(state: &mut SMBState, r: &SmbRecord, andx_offse
None => b"<unknown>".to_vec(),
};
let mut set_event_fileoverlap = false;
let found = match state.get_file_tx_by_fuid_with_open_file(&file_fid, Direction::ToServer) {
Some(tx) => {
let file_id : u32 = tx.id as u32;
if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
if rd.offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
let found =
match state.get_file_tx_by_fuid_with_open_file(&file_fid, Direction::ToServer) {
Some(tx) => {
let file_id: u32 = tx.id as u32;
if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
if rd.offset < tdf.file_tracker.tracked {
set_event_fileoverlap = true;
}
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
rd.offset,
rd.len as u64,
false,
) {
// Attach to the affected file tx (not the newest tx).
tx.set_event(ev);
tx.tx_data.updated_ts = true;
tx.tx_data.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);
}
filetracker_newchunk(&mut tdf.file_tracker,
&file_name, rd.data, rd.offset,
rd.len, false, &file_id);
SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx);
true
}
true
},
None => { false },
};
None => false,
};
if !found {
let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE);
let (share_name, is_pipe) = match state.ssn2tree_cache.get(&tree_key) {
@ -1009,19 +1029,44 @@ pub fn smb1_write_request_record(state: &mut SMBState, r: &SmbRecord, andx_offse
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, rd.offset,
rd.len, false, &file_id);
tdf.share_name = share_name;
SCLogDebug!("tdf {:?}", tdf);
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
rd.offset,
rd.len as u64,
false,
) {
// tx just created: set the event on it directly.
tx.set_event(ev);
tx.tx_data.updated_ts = true;
tx.tx_data.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.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 {
@ -1089,9 +1134,28 @@ pub fn smb1_read_response_record(state: &mut SMBState, r: &SmbRecord, andx_offse
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);
if let Some(ev) = smb_queue_limit_event(
&tdf.file_tracker,
offset,
rd.len as u64,
true,
) {
// Attach to the affected file tx (not the newest tx).
tx.set_event(ev);
tx.tx_data.updated_ts = true;
tx.tx_data.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
},
@ -1103,18 +1167,44 @@ pub fn smb1_read_response_record(state: &mut SMBState, r: &SmbRecord, andx_offse
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);
let file_id: u32 = tx.id as u32;
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.updated_ts = true;
tx.tx_data.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.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);

@ -112,11 +112,7 @@ 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 };
pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) {
smb2_read_response_record_generic(state, r);
match parse_smb2_response_read(r.data) {
@ -169,11 +165,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 {
state.set_event(SMBEvent::ReadQueueCntExceeded);
if let Some(ev) =
smb_queue_limit_event(&tdf.file_tracker, offset, rd.len as u64, true)
{
// Attach to the affected file tx, not the newest tx.
tx.set_event(ev);
tx.tx_data.updated_ts = true;
tx.tx_data.updated_tc = true;
state.set_skip(Direction::ToClient, nbss_remaining);
} else {
filetracker_newchunk(&mut tdf.file_tracker,
@ -245,12 +243,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 {
state.set_event(SMBEvent::ReadQueueCntExceeded);
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.updated_ts = true;
tx.tx_data.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.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,
&file_name, rd.data, offset,
@ -272,11 +279,7 @@ 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 };
pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) {
SCLogDebug!("SMBv2/WRITE: request record");
if smb2_create_new_tx(r.command) {
let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
@ -318,11 +321,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.updated_ts = true;
tx.tx_data.updated_tc = true;
state.set_skip(Direction::ToServer, nbss_remaining);
} else {
filetracker_newchunk(&mut tdf.file_tracker,
@ -389,12 +397,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.updated_ts = true;
tx.tx_data.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.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, wr.data, wr.wr_offset,

Loading…
Cancel
Save