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 <pantoine@oisf.net>
pull/14601/head
Shivani Bhardwaj 4 months ago committed by Victor Julien
parent 2c95f1ff44
commit e412215af9

@ -32,7 +32,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;
@ -177,6 +179,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 {
@ -1019,7 +1026,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
}
@ -1268,6 +1280,21 @@ pub unsafe extern "C" fn SCRegisterDcerpcParser() {
}
}
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.");
}

@ -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::core;
use crate::applayer::{self, *};
use crate::dcerpc::dcerpc::{
DCERPCTransaction, DCERPC_MAX_TX, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, PFCL1_FRAG, PFCL1_LASTFRAG,
get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME,
get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME, cfg_max_stub_size,
};
use crate::direction::{Direction, DIR_BOTH};
use crate::flow::Flow;
@ -175,18 +175,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;
}
@ -399,7 +408,6 @@ pub unsafe extern "C" fn SCRegisterDcerpcUdpParser() {
}
}
#[cfg(test)]
mod tests {
use crate::applayer::AppLayerResult;

@ -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;

@ -101,6 +101,8 @@ pub static mut SMB_CFG_MAX_FRAG_CACHE_SIZE: usize = 128;
/// For SMBState::ssn2vec_cache
pub static mut SMB_CFG_MAX_SSN2VEC_CACHE_SIZE: usize = 512;
pub static mut SMB_DCERPC_MAX_STUB_SIZE: u32 = 1048576;
static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN;
static mut SMB_MAX_TX: usize = 1024;
@ -2513,6 +2515,21 @@ pub unsafe extern "C" fn SCRegisterSmbParser() {
SCLogError!("Invalid max-dcerpc-frag-cache-size value");
}
}
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");
}
}
}
let retval = conf_get("app-layer.protocols.smb.max-session-cache-size");
if let Some(val) = retval {
if let Ok(v) = val.parse::<usize>() {
@ -2583,3 +2600,9 @@ fn cfg_max_write_queue_size() -> u32 {
fn cfg_max_guid_cache_size() -> usize {
unsafe { SMB_CFG_MAX_GUID_CACHE_SIZE }
}
#[inline(always)]
pub fn cfg_max_stub_size() -> u32 {
unsafe { SMB_DCERPC_MAX_STUB_SIZE }
}

@ -979,6 +979,8 @@ app-layer:
enabled: yes
# Maximum number of live DCERPC transactions per flow
# max-tx: 1024
#max-stub-size: 1MiB
ftp:
enabled: yes
# memcap: 64 MiB
@ -1069,6 +1071,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

Loading…
Cancel
Save