Skip to content

Commit c8f68b0

Browse files
jlucovskyvictorjulien
authored andcommitted
smb: fail transaction creation once the limit is reached
new_tx() now refuses to create a transaction when the list is already at SMB_MAX_TX, returning None instead of a transaction. Every creation path -- the new_*_tx helpers and their callers across smb1/smb2/dcerpc/session/ files/ioctl -- propagates that, so no single input can create more than the limit, including a compound SMB2 request that chains many PDUs in one record. When the list is full the parser puts the flow into an error state and stops processing it. This replaces the previous force-completion of old transactions, which did not reliably bound the list and could leave transactions unreclaimable on asymmetric flows. The now-unused tx_index_completed bookkeeping is removed. Issue: 8629
1 parent 392b6ae commit c8f68b0

9 files changed

Lines changed: 173 additions & 109 deletions

File tree

rust/src/smb/dcerpc.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (C) 2017 Open Information Security Foundation
1+
/* Copyright (C) 2017-2026 Open Information Security Foundation
22
*
33
* You can copy, redistribute or modify this Program under the terms of
44
* the GNU General Public License version 2 as published by the Free
@@ -126,8 +126,8 @@ impl SMBTransactionDCERPC {
126126
impl SMBState {
127127
fn new_dcerpc_tx(
128128
&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32,
129-
) -> &mut SMBTransaction {
130-
let mut tx = self.new_tx();
129+
) -> Option<&mut SMBTransaction> {
130+
let mut tx = self.new_tx()?;
131131
tx.hdr = hdr;
132132
tx.vercmd = vercmd;
133133
tx.type_data = Some(SMBTransactionTypeData::DCERPC(
@@ -136,14 +136,13 @@ impl SMBState {
136136

137137
SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr);
138138
self.transactions.push_back(tx);
139-
let tx_ref = self.transactions.back_mut();
140-
return tx_ref.unwrap();
139+
self.transactions.back_mut()
141140
}
142141

143142
fn new_dcerpc_tx_for_response(
144143
&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, call_id: u32,
145-
) -> &mut SMBTransaction {
146-
let mut tx = self.new_tx();
144+
) -> Option<&mut SMBTransaction> {
145+
let mut tx = self.new_tx()?;
147146
tx.hdr = hdr;
148147
tx.vercmd = vercmd;
149148
tx.type_data = Some(SMBTransactionTypeData::DCERPC(
@@ -152,8 +151,7 @@ impl SMBState {
152151

153152
SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr);
154153
self.transactions.push_back(tx);
155-
let tx_ref = self.transactions.back_mut();
156-
return tx_ref.unwrap();
154+
self.transactions.back_mut()
157155
}
158156

159157
fn get_dcerpc_tx(
@@ -251,7 +249,9 @@ pub fn smb_write_dcerpc_record(
251249
}
252250
}
253251

254-
let tx = state.new_dcerpc_tx(hdr, vercmd, dcer.packet_type, dcer.call_id);
252+
let Some(tx) = state.new_dcerpc_tx(hdr, vercmd, dcer.packet_type, dcer.call_id) else {
253+
return false;
254+
};
255255
match dcer.packet_type {
256256
DCERPC_TYPE_REQUEST => {
257257
match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian)
@@ -556,7 +556,11 @@ pub fn smb_read_dcerpc_record(
556556
};
557557
if !found {
558558
// pick up DCERPC tx even if we missed the request
559-
let tx = state.new_dcerpc_tx_for_response(hdr, vercmd.clone(), dcer.call_id);
559+
let Some(tx) =
560+
state.new_dcerpc_tx_for_response(hdr, vercmd.clone(), dcer.call_id)
561+
else {
562+
return false;
563+
};
560564
dcerpc_response_handle(tx, vercmd, &dcer);
561565
}
562566
}

rust/src/smb/files.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (C) 2018-2022 Open Information Security Foundation
1+
/* Copyright (C) 2018-2026 Open Information Security Foundation
22
*
33
* You can copy, redistribute or modify this Program under the terms of
44
* the GNU General Public License version 2 as published by the Free
@@ -89,8 +89,8 @@ fn filetracker_update(ft: &mut FileTransferTracker, data: &[u8], gap_size: u32)
8989
impl SMBState {
9090
pub fn new_file_tx(
9191
&mut self, fuid: &[u8], file_name: &[u8], direction: Direction,
92-
) -> &mut SMBTransaction {
93-
let mut tx = self.new_tx();
92+
) -> Option<&mut SMBTransaction> {
93+
let mut tx = self.new_tx()?;
9494
tx.type_data = Some(SMBTransactionTypeData::FILE(SMBTransactionFile::new()));
9595
if let Some(SMBTransactionTypeData::FILE(ref mut d)) = tx.type_data {
9696
d.direction = direction;
@@ -112,8 +112,7 @@ impl SMBState {
112112
String::from_utf8_lossy(file_name)
113113
);
114114
self.transactions.push_back(tx);
115-
let tx_ref = self.transactions.back_mut();
116-
return tx_ref.unwrap();
115+
self.transactions.back_mut()
117116
}
118117

119118
/// get file tx for a open file. Returns None if a file for the fuid exists,

rust/src/smb/session.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (C) 2018 Open Information Security Foundation
1+
/* Copyright (C) 2018-2026 Open Information Security Foundation
22
*
33
* You can copy, redistribute or modify this Program under the terms of
44
* the GNU General Public License version 2 as published by the Free
@@ -36,8 +36,8 @@ impl SMBTransactionSessionSetup {
3636
}
3737

3838
impl SMBState {
39-
pub fn new_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) -> &mut SMBTransaction {
40-
let mut tx = self.new_tx();
39+
pub fn new_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) -> Option<&mut SMBTransaction> {
40+
let mut tx = self.new_tx()?;
4141

4242
tx.hdr = hdr;
4343
tx.type_data = Some(SMBTransactionTypeData::SESSIONSETUP(
@@ -48,8 +48,7 @@ impl SMBState {
4848

4949
SCLogDebug!("SMB: TX SESSIONSETUP created: ID {}", tx.id);
5050
self.transactions.push_back(tx);
51-
let tx_ref = self.transactions.back_mut();
52-
return tx_ref.unwrap();
51+
self.transactions.back_mut()
5352
}
5453

5554
pub fn get_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) -> Option<&mut SMBTransaction> {

rust/src/smb/smb.rs

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* Copyright (C) 2017-2022 Open Information Security Foundation
1+
/* Copyright (C) 2017-2026 Open Information Security Foundation
22
*
33
* You can copy, redistribute or modify this Program under the terms of
44
* the GNU General Public License version 2 as published by the Free
@@ -358,8 +358,8 @@ impl SMBTransactionSetFilePathInfo {
358358
impl SMBState {
359359
pub fn new_setfileinfo_tx(
360360
&mut self, filename: Vec<u8>, fid: Vec<u8>, subcmd: u16, loi: u16, delete_on_close: bool,
361-
) -> &mut SMBTransaction {
362-
let mut tx = self.new_tx();
361+
) -> Option<&mut SMBTransaction> {
362+
let mut tx = self.new_tx()?;
363363

364364
tx.type_data = Some(SMBTransactionTypeData::SETFILEPATHINFO(
365365
SMBTransactionSetFilePathInfo::new(filename, fid, subcmd, loi, delete_on_close),
@@ -369,14 +369,13 @@ impl SMBState {
369369

370370
SCLogDebug!("SMB: TX SETFILEPATHINFO created: ID {}", tx.id);
371371
self.transactions.push_back(tx);
372-
let tx_ref = self.transactions.back_mut();
373-
return tx_ref.unwrap();
372+
self.transactions.back_mut()
374373
}
375374

376375
pub fn new_setpathinfo_tx(
377376
&mut self, filename: Vec<u8>, subcmd: u16, loi: u16, delete_on_close: bool,
378-
) -> &mut SMBTransaction {
379-
let mut tx = self.new_tx();
377+
) -> Option<&mut SMBTransaction> {
378+
let mut tx = self.new_tx()?;
380379

381380
let fid: Vec<u8> = Vec::new();
382381
tx.type_data = Some(SMBTransactionTypeData::SETFILEPATHINFO(
@@ -387,8 +386,7 @@ impl SMBState {
387386

388387
SCLogDebug!("SMB: TX SETFILEPATHINFO created: ID {}", tx.id);
389388
self.transactions.push_back(tx);
390-
let tx_ref = self.transactions.back_mut();
391-
return tx_ref.unwrap();
389+
self.transactions.back_mut()
392390
}
393391
}
394392

@@ -412,8 +410,8 @@ impl SMBTransactionRename {
412410
impl SMBState {
413411
pub fn new_rename_tx(
414412
&mut self, fuid: Vec<u8>, oldname: Vec<u8>, newname: Vec<u8>,
415-
) -> &mut SMBTransaction {
416-
let mut tx = self.new_tx();
413+
) -> Option<&mut SMBTransaction> {
414+
let mut tx = self.new_tx()?;
417415

418416
tx.type_data = Some(SMBTransactionTypeData::RENAME(SMBTransactionRename::new(
419417
fuid, oldname, newname,
@@ -423,8 +421,7 @@ impl SMBState {
423421

424422
SCLogDebug!("SMB: TX RENAME created: ID {}", tx.id);
425423
self.transactions.push_back(tx);
426-
let tx_ref = self.transactions.back_mut();
427-
return tx_ref.unwrap();
424+
self.transactions.back_mut()
428425
}
429426
}
430427

@@ -745,7 +742,6 @@ pub struct SMBState {
745742

746743
/// transactions list
747744
pub transactions: VecDeque<SMBTransaction>,
748-
tx_index_completed: usize,
749745

750746
/// tx counter for assigning incrementing id's to tx's
751747
tx_id: u64,
@@ -818,7 +814,6 @@ impl SMBState {
818814
check_post_gap_file_txs: false,
819815
post_gap_files_checked: false,
820816
transactions: VecDeque::new(),
821-
tx_index_completed: 0,
822817
tx_id: 0,
823818
dialect: 0,
824819
dialect_vec: None,
@@ -834,27 +829,21 @@ impl SMBState {
834829
self._debug_tx_stats();
835830
}
836831

837-
pub fn new_tx(&mut self) -> SMBTransaction {
832+
pub fn new_tx(&mut self) -> Option<SMBTransaction> {
833+
if self.transactions.len() >= unsafe { SMB_MAX_TX } {
834+
// Refuse to create a transaction once the list is at the limit. This
835+
// bounds the number of transactions a single input can create, no
836+
// matter the path (compound records, dcerpc, ...). Callers stop
837+
// using the transaction and the parser puts the flow into an error
838+
// state (see issue 8629).
839+
self.set_event(SMBEvent::TooManyTransactions);
840+
return None;
841+
}
838842
let mut tx = SMBTransaction::new();
839843
self.tx_id += 1;
840844
tx.id = self.tx_id;
841845
SCLogDebug!("TX {} created", tx.id);
842-
if self.transactions.len() > unsafe { SMB_MAX_TX } {
843-
let mut index = self.tx_index_completed;
844-
for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) {
845-
index += 1;
846-
if !tx_old.request_done || !tx_old.response_done {
847-
tx_old.tx_data.0.updated_tc = true;
848-
tx_old.tx_data.0.updated_ts = true;
849-
tx_old.request_done = true;
850-
tx_old.response_done = true;
851-
tx_old.set_event(SMBEvent::TooManyTransactions);
852-
break;
853-
}
854-
}
855-
self.tx_index_completed = index;
856-
}
857-
return tx;
846+
Some(tx)
858847
}
859848

860849
pub fn free_tx(&mut self, tx_id: u64) {
@@ -885,7 +874,6 @@ impl SMBState {
885874
self.transactions.len(),
886875
self.tx_id
887876
);
888-
self.tx_index_completed = 0;
889877
self.transactions.remove(index);
890878
}
891879
}
@@ -942,8 +930,8 @@ impl SMBState {
942930

943931
pub fn new_generic_tx(
944932
&mut self, smb_ver: u8, smb_cmd: u16, key: SMBCommonHdr,
945-
) -> &mut SMBTransaction {
946-
let mut tx = self.new_tx();
933+
) -> Option<&mut SMBTransaction> {
934+
let mut tx = self.new_tx()?;
947935
if smb_ver == 1 && smb_cmd <= 255 {
948936
tx.vercmd.set_smb1_cmd(smb_cmd as u8);
949937
} else if smb_ver == 2 {
@@ -962,8 +950,7 @@ impl SMBState {
962950
&tx
963951
);
964952
self.transactions.push_back(tx);
965-
let tx_ref = self.transactions.back_mut();
966-
return tx_ref.unwrap();
953+
self.transactions.back_mut()
967954
}
968955

969956
pub fn get_last_tx(&mut self, smb_ver: u8, smb_cmd: u16) -> Option<&mut SMBTransaction> {
@@ -1017,8 +1004,8 @@ impl SMBState {
10171004
return None;
10181005
}
10191006

1020-
pub fn new_negotiate_tx(&mut self, smb_ver: u8) -> &mut SMBTransaction {
1021-
let mut tx = self.new_tx();
1007+
pub fn new_negotiate_tx(&mut self, smb_ver: u8) -> Option<&mut SMBTransaction> {
1008+
let mut tx = self.new_tx()?;
10221009
if smb_ver == 1 {
10231010
tx.vercmd.set_smb1_cmd(SMB1_COMMAND_NEGOTIATE_PROTOCOL);
10241011
} else if smb_ver == 2 {
@@ -1037,8 +1024,7 @@ impl SMBState {
10371024
smb_ver
10381025
);
10391026
self.transactions.push_back(tx);
1040-
let tx_ref = self.transactions.back_mut();
1041-
return tx_ref.unwrap();
1027+
self.transactions.back_mut()
10421028
}
10431029

10441030
pub fn get_negotiate_tx(&mut self, smb_ver: u8) -> Option<&mut SMBTransaction> {
@@ -1056,8 +1042,10 @@ impl SMBState {
10561042
return None;
10571043
}
10581044

1059-
pub fn new_treeconnect_tx(&mut self, hdr: SMBCommonHdr, name: Vec<u8>) -> &mut SMBTransaction {
1060-
let mut tx = self.new_tx();
1045+
pub fn new_treeconnect_tx(
1046+
&mut self, hdr: SMBCommonHdr, name: Vec<u8>,
1047+
) -> Option<&mut SMBTransaction> {
1048+
let mut tx = self.new_tx()?;
10611049

10621050
tx.hdr = hdr;
10631051
tx.type_data = Some(SMBTransactionTypeData::TREECONNECT(
@@ -1072,8 +1060,7 @@ impl SMBState {
10721060
String::from_utf8_lossy(&name)
10731061
);
10741062
self.transactions.push_back(tx);
1075-
let tx_ref = self.transactions.back_mut();
1076-
return tx_ref.unwrap();
1063+
self.transactions.back_mut()
10771064
}
10781065

10791066
pub fn get_treeconnect_tx(&mut self, hdr: SMBCommonHdr) -> Option<&mut SMBTransaction> {
@@ -1094,8 +1081,8 @@ impl SMBState {
10941081

10951082
pub fn new_create_tx(
10961083
&mut self, file_name: &[u8], disposition: u32, del: bool, dir: bool, hdr: SMBCommonHdr,
1097-
) -> &mut SMBTransaction {
1098-
let mut tx = self.new_tx();
1084+
) -> Option<&mut SMBTransaction> {
1085+
let mut tx = self.new_tx()?;
10991086
tx.hdr = hdr;
11001087
tx.type_data = Some(SMBTransactionTypeData::CREATE(SMBTransactionCreate::new(
11011088
file_name.to_vec(),
@@ -1107,8 +1094,7 @@ impl SMBState {
11071094
tx.response_done = self.tc_trunc; // no response expected if tc is truncated
11081095

11091096
self.transactions.push_back(tx);
1110-
let tx_ref = self.transactions.back_mut();
1111-
return tx_ref.unwrap();
1097+
self.transactions.back_mut()
11121098
}
11131099

11141100
pub fn get_service_for_guid(&mut self, guid: &[u8]) -> (&'static str, bool) {
@@ -1551,6 +1537,13 @@ impl SMBState {
15511537
pub fn parse_tcp_data_ts(
15521538
&mut self, flow: *mut Flow, stream_slice: &StreamSlice,
15531539
) -> AppLayerResult {
1540+
// The transaction list is full: new_tx() is refusing to create more, so
1541+
// put the flow into an error state and stop processing it (see issue
1542+
// 8629).
1543+
if self.transactions.len() >= unsafe { SMB_MAX_TX } {
1544+
self.set_event(SMBEvent::TooManyTransactions);
1545+
return AppLayerResult::err();
1546+
}
15541547
let mut cur_i = stream_slice.as_slice();
15551548
let consumed = self.handle_skip(Direction::ToServer, cur_i.len() as u32);
15561549
if consumed > 0 {
@@ -2105,6 +2098,13 @@ impl SMBState {
21052098
pub fn parse_tcp_data_tc(
21062099
&mut self, flow: *mut Flow, stream_slice: &StreamSlice,
21072100
) -> AppLayerResult {
2101+
// The transaction list is full: new_tx() is refusing to create more, so
2102+
// put the flow into an error state and stop processing it (see issue
2103+
// 8629).
2104+
if self.transactions.len() >= unsafe { SMB_MAX_TX } {
2105+
self.set_event(SMBEvent::TooManyTransactions);
2106+
return AppLayerResult::err();
2107+
}
21082108
let mut cur_i = stream_slice.as_slice();
21092109
let consumed = self.handle_skip(Direction::ToClient, cur_i.len() as u32);
21102110
if consumed > 0 {

0 commit comments

Comments
 (0)