From f82a388d0283725cb76782cf64e8341cab370830 Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Tue, 6 Jan 2026 16:44:52 +0530 Subject: [PATCH] dcerpc: add upper limit on stub data DCERPC parsers had no upper bounds when it came to extending the stub data buffer. Traffic can be crafted to bypass some internal parser conditions to create an indefinite buffering in the stub_data array that can make Suricata crash. Add a default limit of 1MiB and make it configurable for the user. Security 8182 Co-authored-by: Philippe Antoine (cherry picked from commit e412215af990feeffbb66c7dd9f392813a20ae50) --- rust/src/dcerpc/dcerpc.rs | 31 +++++++++++++++++++++++++++++-- rust/src/dcerpc/dcerpc_udp.rs | 18 +++++++++++++----- rust/src/smb/dcerpc.rs | 31 +++++++++++++++++++++++-------- rust/src/smb/smb.rs | 23 +++++++++++++++++++++++ suricata.yaml.in | 4 ++++ 5 files changed, 92 insertions(+), 15 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 5469b736b0..7297ff82ed 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -25,7 +25,9 @@ use std; use std::cmp; use std::ffi::CString; use std::collections::VecDeque; -use crate::conf::conf_get; +use crate::conf::{conf_get, get_memval}; + +pub static mut DCERPC_MAX_STUB_SIZE: u32 = 1048576; // Constant DCERPC UDP Header length pub const DCERPC_HDR_LEN: u16 = 16; @@ -163,6 +165,11 @@ pub fn get_req_type_for_resp(t: u8) -> u8 { _ => DCERPC_TYPE_UNKNOWN, } } +#[inline(always)] +pub fn cfg_max_stub_size() -> u32 { + unsafe { DCERPC_MAX_STUB_SIZE } +} + #[derive(Default, Debug)] pub struct DCERPCTransaction { @@ -1096,7 +1103,12 @@ fn evaluate_stub_params( } let input_slice = &input[..stub_len as usize]; - stub_data_buffer.extend_from_slice(input_slice); + let max_size = cfg_max_stub_size() as usize; + if (stub_data_buffer.len() + input_slice.len()) < max_size { + stub_data_buffer.extend_from_slice(input_slice); + } else if stub_data_buffer.len() < max_size { + stub_data_buffer.extend_from_slice(&input_slice[..max_size - stub_data_buffer.len()]); + } stub_len } @@ -1396,6 +1408,21 @@ pub unsafe extern "C" fn rs_dcerpc_register_parser() { } } SCLogDebug!("Rust DCERPC parser registered."); + let retval = conf_get("app-layer.protocols.dcerpc.max-stub-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { + if retval > 0 { + DCERPC_MAX_STUB_SIZE = retval as u32; + } else { + SCLogError!("Invalid max-stub-size value"); + } + } + Err(_) => { + SCLogError!("Invalid max-stub-size value"); + } + } + } } else { SCLogDebug!("Protocol detector and parser disabled for DCERPC."); } diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs index d70ca1b531..0a6213a870 100644 --- a/rust/src/dcerpc/dcerpc_udp.rs +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Open Information Security Foundation +/* Copyright (C) 2020-2026 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 @@ -19,7 +19,7 @@ use crate::applayer::{self, *}; use crate::core::{self, Direction, DIR_BOTH}; use crate::dcerpc::dcerpc::{ DCERPCTransaction, DCERPC_MAX_TX, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, PFCL1_FRAG, PFCL1_LASTFRAG, - rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME, + rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME, cfg_max_stub_size, }; use nom7::Err; use std; @@ -169,18 +169,27 @@ impl DCERPCUDPState { tx.tx_data.updated_ts = true; let done = (hdr.flags1 & PFCL1_FRAG) == 0 || (hdr.flags1 & PFCL1_LASTFRAG) != 0; + let max_size = cfg_max_stub_size() as usize; match hdr.pkt_type { DCERPC_TYPE_REQUEST => { - tx.stub_data_buffer_ts.extend_from_slice(input); tx.frag_cnt_ts += 1; + if input.len() + tx.stub_data_buffer_ts.len() < max_size { + tx.stub_data_buffer_ts.extend_from_slice(input); + } else if tx.stub_data_buffer_ts.len() < max_size { + tx.stub_data_buffer_ts.extend_from_slice(&input[..max_size - tx.stub_data_buffer_ts.len()]); + } if done { tx.req_done = true; } return true; } DCERPC_TYPE_RESPONSE => { - tx.stub_data_buffer_tc.extend_from_slice(input); tx.frag_cnt_tc += 1; + if input.len() + tx.stub_data_buffer_tc.len() < max_size { + tx.stub_data_buffer_tc.extend_from_slice(input); + } else if tx.stub_data_buffer_tc.len() < max_size { + tx.stub_data_buffer_tc.extend_from_slice(&input[..max_size - tx.stub_data_buffer_tc.len()]); + } if done { tx.resp_done = true; } @@ -397,7 +406,6 @@ pub unsafe extern "C" fn rs_dcerpc_udp_register_parser() { } } - #[cfg(test)] mod tests { use crate::applayer::AppLayerResult; diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs index 6c2a2f9345..1e62241bb2 100644 --- a/rust/src/smb/dcerpc.rs +++ b/rust/src/smb/dcerpc.rs @@ -18,7 +18,7 @@ // written by Victor Julien use uuid; -use crate::smb::smb::*; +use crate::smb::smb::{cfg_max_stub_size, *}; use crate::smb::smb2::*; use crate::smb::dcerpc_records::*; use crate::smb::events::*; @@ -205,10 +205,15 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState, SCLogDebug!("previous CMD {} found at tx {} => {:?}", dcer.packet_type, tx.id, tx); if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { - SCLogDebug!("additional frag of size {}", recr.data.len()); - tdn.stub_data_ts.extend_from_slice(recr.data); tdn.frag_cnt_ts += 1; - SCLogDebug!("stub_data now {}", tdn.stub_data_ts.len()); + let max_size = cfg_max_stub_size() as usize; + if recr.data.len() + tdn.stub_data_ts.len() < max_size { + SCLogDebug!("additional frag of size {}", recr.data.len()); + tdn.stub_data_ts.extend_from_slice(recr.data); + SCLogDebug!("stub_data now {}", tdn.stub_data_ts.len()); + } else if tdn.stub_data_ts.len() < max_size { + tdn.stub_data_ts.extend_from_slice(&recr.data[..max_size - tdn.stub_data_ts.len()]); + } } if dcer.last_frag { SCLogDebug!("last frag set, so request side of DCERPC closed"); @@ -240,12 +245,17 @@ pub fn smb_write_dcerpc_record(state: &mut SMBState, SCLogDebug!("DCERPC: REQUEST {:?}", recr); if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { SCLogDebug!("first frag size {}", recr.data.len()); - tdn.stub_data_ts.extend_from_slice(recr.data); tdn.opnum = recr.opnum; tdn.context_id = recr.context_id; tdn.frag_cnt_ts += 1; - SCLogDebug!("DCERPC: REQUEST opnum {} stub data len {}", - tdn.opnum, tdn.stub_data_ts.len()); + let max_size = cfg_max_stub_size() as usize; + if tdn.stub_data_ts.len() + recr.data.len() < max_size { + tdn.stub_data_ts.extend_from_slice(recr.data); + SCLogDebug!("DCERPC: REQUEST opnum {} stub data len {}", + tdn.opnum, tdn.stub_data_ts.len()); + } else if tdn.stub_data_ts.len() < max_size { + tdn.stub_data_ts.extend_from_slice(&recr.data[..max_size - tdn.stub_data_ts.len()]); + } } if dcer.last_frag { tx.request_done = true; @@ -407,8 +417,13 @@ fn dcerpc_response_handle(tx: &mut SMBTransaction, if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { SCLogDebug!("CMD 11 found at tx {}", tx.id); tdn.set_result(DCERPC_TYPE_RESPONSE); - tdn.stub_data_tc.extend_from_slice(respr.data); + let max_size = cfg_max_stub_size() as usize; tdn.frag_cnt_tc += 1; + if tdn.stub_data_tc.len() + respr.data.len() < max_size { + tdn.stub_data_tc.extend_from_slice(respr.data); + } else if tdn.stub_data_tc.len() < max_size { + tdn.stub_data_tc.extend_from_slice(&respr.data[..max_size - tdn.stub_data_tc.len()]); + } } tx.vercmd.set_ntstatus(ntstatus); tx.response_done = dcer.last_frag; diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs index c22bc9fc97..0a0fc10824 100644 --- a/rust/src/smb/smb.rs +++ b/rust/src/smb/smb.rs @@ -81,6 +81,8 @@ pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 16777216; pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 67108864; pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 64; +pub static mut SMB_DCERPC_MAX_STUB_SIZE: u32 = 1048576; + static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN; static mut SMB_MAX_TX: usize = 1024; @@ -2438,6 +2440,21 @@ pub unsafe extern "C" fn rs_smb_register_parser() { SCLogError!("Invalid value for smb.max-tx"); } } + let retval = conf_get("app-layer.protocols.smb.dcerpc.max-stub-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { + if retval > 0 { + SMB_DCERPC_MAX_STUB_SIZE = retval as u32; + } else { + SCLogError!("Invalid max-stub-size value"); + } + } + Err(_) => { + SCLogError!("Invalid max-stub-size value"); + } + } + } SCLogConfig!("read: max record size: {}, max queued chunks {}, max queued size {}", SMB_CFG_MAX_READ_SIZE, SMB_CFG_MAX_READ_QUEUE_CNT, SMB_CFG_MAX_READ_QUEUE_SIZE); SCLogConfig!("write: max record size: {}, max queued chunks {}, max queued size {}", @@ -2446,3 +2463,9 @@ pub unsafe extern "C" fn rs_smb_register_parser() { SCLogDebug!("Protocol detector and parser disabled for SMB."); } } + +#[inline(always)] +pub fn cfg_max_stub_size() -> u32 { + unsafe { SMB_DCERPC_MAX_STUB_SIZE } +} + diff --git a/suricata.yaml.in b/suricata.yaml.in index 7640f2b62f..eab6ca5005 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -951,6 +951,8 @@ app-layer: enabled: yes # Maximum number of live DCERPC transactions per flow # max-tx: 1024 + #max-stub-size: 1MiB + ftp: enabled: yes # memcap: 64mb @@ -1015,6 +1017,8 @@ app-layer: # Stream reassembly size for SMB streams. By default track it completely. #stream-depth: 0 + #dcerpc: + # max-stub-size: 1MiB nfs: enabled: yes