From e995aa355eae3d066d8c3d83febd236a2dc1bdd7 Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Thu, 23 Jul 2026 12:30:29 +0530 Subject: [PATCH 1/4] dcerpc: set PDU type from header in tx The constructor for creation of a new DCERPC Transaction unconditionally set the PDU type to REQUEST and RESPONSE in the respective direction. This means that if a transaction had only seen a request of type BIND, its response type would still be set to RESPONSE which is incorrect pairing and assumption. Fix the parser to only use PDU type reliably derived from the header. Do not return the corresponding PDU type for a request/response if it does not match the transaction's stated type for that respective direction. Bug 8759 --- rust/src/dcerpc/dcerpc.rs | 12 ++++++------ rust/src/dcerpc/dcerpc_udp.rs | 2 ++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 7ea109b69f..4efe040740 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -221,9 +221,9 @@ impl DCERPCTransaction { return Self { stub_data_buffer_ts: Vec::new(), stub_data_buffer_tc: Vec::new(), - req_cmd: DCERPC_TYPE_REQUEST, - resp_cmd: DCERPC_TYPE_RESPONSE, activityuuid: Vec::new(), + req_cmd: DCERPC_TYPE_UNKNOWN, + resp_cmd: DCERPC_TYPE_UNKNOWN, tx_data: AppLayerTxData::new(), ..Default::default() }; @@ -445,8 +445,8 @@ impl DCERPCState { if tx.req_done || tx.req_lost { continue; } - let resp_cmd = get_resp_type_for_req(cmd); - if resp_cmd != tx.resp_cmd { + /* do not match if the transaction's command does not match expected */ + if tx.req_cmd != cmd && get_req_type_for_resp(tx.resp_cmd) != cmd { continue; } } @@ -454,8 +454,8 @@ impl DCERPCState { if tx.resp_done || tx.resp_lost { continue; } - let req_cmd = get_req_type_for_resp(cmd); - if req_cmd != tx.req_cmd { + /* do not match if the transaction's command does not match expected */ + if tx.resp_cmd != cmd && get_resp_type_for_req(tx.req_cmd) != cmd { continue; } } diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs index 538ee3d3c6..0312c4187d 100644 --- a/rust/src/dcerpc/dcerpc_udp.rs +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -190,6 +190,7 @@ impl DCERPCUDPState { let max_size = cfg_max_stub_size() as usize; match hdr.pkt_type { DCERPC_TYPE_REQUEST => { + tx.req_cmd = hdr.pkt_type; tx.frag_cnt_ts = tx.frag_cnt_ts.saturating_add(1); if input.len() + tx.stub_data_buffer_ts.len() < max_size { tx.stub_data_buffer_ts.extend_from_slice(input); @@ -203,6 +204,7 @@ impl DCERPCUDPState { return true; } DCERPC_TYPE_RESPONSE => { + tx.resp_cmd = hdr.pkt_type; tx.frag_cnt_tc = tx.frag_cnt_tc.saturating_add(1); if input.len() + tx.stub_data_buffer_tc.len() < max_size { tx.stub_data_buffer_tc.extend_from_slice(input); From 42fb464b6c2cb7d6e69dada1ba22ded51a3f4f4d Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Thu, 23 Jul 2026 13:27:39 +0530 Subject: [PATCH 2/4] dcerpc: move drep alloc on stack DCERPC's drep field is always 4 bytes for connection oriented PDU types. Make it a byte array of exactly 4 bytes to store the entire header on stack. --- rust/src/dcerpc/dcerpc.rs | 4 ++-- rust/src/dcerpc/parser.rs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 4efe040740..81e7e8002f 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -278,13 +278,13 @@ pub struct Uuid { pub node: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct DCERPCHdr { pub rpc_vers: u8, pub rpc_vers_minor: u8, pub hdrtype: u8, pub pfc_flags: u8, - pub packed_drep: Vec, + pub packed_drep: [u8; 4], pub frag_length: u16, pub auth_length: u16, pub call_id: u32, diff --git a/rust/src/dcerpc/parser.rs b/rust/src/dcerpc/parser.rs index a80126b1c7..3fe2adea48 100644 --- a/rust/src/dcerpc/parser.rs +++ b/rust/src/dcerpc/parser.rs @@ -233,7 +233,12 @@ pub(super) fn parse_dcerpc_header(i: &[u8]) -> IResult<&[u8], DCERPCHdr> { rpc_vers_minor, hdrtype, pfc_flags, - packed_drep: packed_drep.to_vec(), + packed_drep: [ + packed_drep[0], + packed_drep[1], + packed_drep[2], + packed_drep[3], + ], frag_length, auth_length, call_id, From d3270643f3f20bdd6046eba5d0f527d35040255f Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Thu, 23 Jul 2026 18:05:18 +0530 Subject: [PATCH 3/4] dcerpc: mark request/response seen In order to avoid a blanket match on unknown PDUs with dcerpc.ptype keyword. --- rust/src/dcerpc/dcerpc.rs | 9 +++++++++ rust/src/dcerpc/dcerpc_udp.rs | 2 ++ 2 files changed, 11 insertions(+) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 81e7e8002f..2cd6cbb85c 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -205,6 +205,8 @@ pub struct DCERPCTransaction { pub resp_lost: bool, pub req_cmd: u8, pub resp_cmd: u8, + pub req_seen: bool, + pub resp_seen: bool, pub activityuuid: Vec, pub seqnum: u32, pub tx_data: AppLayerTxData, @@ -585,6 +587,7 @@ impl DCERPCState { } let mut tx = self.create_tx(hdr); tx.req_cmd = hdr.hdrtype; + tx.req_seen = true; tx.req_done = true; if let Some(flow) = self.flow { sc_app_layer_parser_trigger_raw_stream_inspection( @@ -762,6 +765,7 @@ impl DCERPCState { match transaction { Some(ref mut tx) => { tx.req_cmd = hdr_type; + tx.req_seen = true; tx.ctxid = request.ctxid; tx.opnum = request.opnum; tx.first_request_seen = request.first_request_seen; @@ -769,6 +773,7 @@ impl DCERPCState { None => { let mut tx = self.create_tx(hdr); tx.req_cmd = hdr_type; + tx.req_seen = true; tx.ctxid = request.ctxid; tx.opnum = request.opnum; tx.first_request_seen = request.first_request_seen; @@ -949,10 +954,12 @@ impl DCERPCState { self.get_tx_by_call_id(current_call_id, Direction::ToClient, hdrtype) { tx.resp_cmd = hdrtype; + tx.resp_seen = true; tx } else { let mut tx = self.create_tx(&hdr); tx.resp_cmd = hdrtype; + tx.resp_seen = true; self.transactions.push_back(tx); self.transactions.back_mut().unwrap() }; @@ -980,10 +987,12 @@ impl DCERPCState { match transaction { Some(tx) => { tx.resp_cmd = hdrtype; + tx.resp_seen = true; } None => { let mut tx = self.create_tx(&hdr); tx.resp_cmd = hdrtype; + tx.resp_seen = true; self.transactions.push_back(tx); } }; diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs index 0312c4187d..66b91b23d6 100644 --- a/rust/src/dcerpc/dcerpc_udp.rs +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -191,6 +191,7 @@ impl DCERPCUDPState { match hdr.pkt_type { DCERPC_TYPE_REQUEST => { tx.req_cmd = hdr.pkt_type; + tx.req_seen = true; tx.frag_cnt_ts = tx.frag_cnt_ts.saturating_add(1); if input.len() + tx.stub_data_buffer_ts.len() < max_size { tx.stub_data_buffer_ts.extend_from_slice(input); @@ -205,6 +206,7 @@ impl DCERPCUDPState { } DCERPC_TYPE_RESPONSE => { tx.resp_cmd = hdr.pkt_type; + tx.resp_seen = true; tx.frag_cnt_tc = tx.frag_cnt_tc.saturating_add(1); if input.len() + tx.stub_data_buffer_tc.len() < max_size { tx.stub_data_buffer_tc.extend_from_slice(input); From 489bd75ea10bb351dd13418549dafbf784266ffd Mon Sep 17 00:00:00 2001 From: Shivani Bhardwaj Date: Thu, 23 Jul 2026 16:38:35 +0530 Subject: [PATCH 4/4] dcerpc: store unhandled packet types This allows to at least log unhandled packet types even if the parser does not do anything extra with them. All this information was already in the header but since transactions were only created for the supported PDU types, there was no way to log or have a possibility to match on these. Store the packet type information for all unhandled types in a new transaction. Note that one transaction is created for each unhandled type and there is no request-response pair matching done. Feature 8762 --- rust/src/dcerpc/dcerpc.rs | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs index 2cd6cbb85c..7a6f206b49 100644 --- a/rust/src/dcerpc/dcerpc.rs +++ b/rust/src/dcerpc/dcerpc.rs @@ -801,6 +801,24 @@ impl DCERPCState { } } + fn save_unhandled_ptypes(&mut self, hdr: &DCERPCHdr, direction: Direction) { + /* Create tx even on unhandled PDU types to be able to + * at least issue a match on header only fields */ + let mut tx = self.create_tx(hdr); + if direction == Direction::ToServer { + tx.req_cmd = hdr.hdrtype; + tx.req_seen = true; + } else { + tx.resp_cmd = hdr.hdrtype; + tx.resp_seen = true; + } + tx.req_done = true; + tx.resp_done = true; + self.transactions.push_back(tx); + // recognized DCERPC PDU types that we do not process + SCLogDebug!("Unhandled packet type: {:?}", hdr.hdrtype); + } + pub fn handle_input_data( &mut self, stream_slice: StreamSlice, direction: Direction, ) -> AppLayerResult { @@ -893,6 +911,15 @@ impl DCERPCState { // input only consists of the header and that was consumed, so, return early cur_i = &cur_i[fraglen as usize..]; consumed += fraglen as u32; + // save header-only PDU types too + if matches!( + hdr.hdrtype, + DCERPC_TYPE_SHUTDOWN | DCERPC_TYPE_CO_CANCEL | DCERPC_TYPE_ORPHANED + ) { + self.save_unhandled_ptypes(&hdr, direction); + /* DCERPC_TYPE_CO_CANCEL and ORPHANED can optionally be more than header + * too so they're also handled later on the in the unrecognized branch */ + } continue; } cmp::Ordering::Greater => {} @@ -1006,9 +1033,24 @@ impl DCERPCState { return AppLayerResult::err(); } } + DCERPC_TYPE_PING + | DCERPC_TYPE_FAULT + | DCERPC_TYPE_WORKING + | DCERPC_TYPE_NOCALL + | DCERPC_TYPE_REJECT + | DCERPC_TYPE_ACK + | DCERPC_TYPE_CL_CANCEL + | DCERPC_TYPE_FACK + | DCERPC_TYPE_CANCEL_ACK + | DCERPC_TYPE_BINDNAK + | DCERPC_TYPE_AUTH3 + | DCERPC_TYPE_CO_CANCEL + | DCERPC_TYPE_ORPHANED + | DCERPC_TYPE_RTS => { + self.save_unhandled_ptypes(&hdr, direction); + } _ => { SCLogDebug!("Unrecognized packet type: {:?}", hdrtype); - // skip unrecognized packet types such as AUTH3 } } consumed += fraglen as u32;