Bug #9184
opendcerpc: TCP gap returns a parser error and disables app-layer inspection for the rest of the flow
Description
Summary¶
The default DCERPC TCP parser registers APP_LAYER_PARSER_OPT_ACCEPT_GAPS, but parse_request and parse_response return AppLayerResult::err() as soon as the slice is a real STREAM_GAP. That error is fatal in AppLayerParserParse and calls StreamTcpDisableAppLayer, so both directions of the flow stop application-layer parsing. The resync path that searches for the next DCERPC record lives in handle_input_data and is not reached for a gap. After a normal request has already identified the protocol, one acknowledged sequence hole hides a later DCERPC request from dcerpc.opnum rules. The same request on a contiguous flow alerts. The process keeps running. Unpack the attachment and run ./verify.sh to reproduce.
CWE¶
- CWE-693 Protection Mechanism Failure
CVSS 3.1: 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)
Affected Versions¶
Confirmed on jasonish/suricata:latest, which prints Suricata 8.0.7 RELEASE, in default IDS mode with the built-in DCERPC TCP parser left enabled. No stream or app-layer policy was relaxed. Checksum validation stayed on. Line numbers below are from the 8.0.7 parser.
Preconditions¶
The flow must already be identified as DCERPC, then the sensor must observe a TCP sequence hole that the peer has acknowledged. IDS mode only feeds acknowledged data to the app layer, so an unacknowledged hole does not reach the parser. The attacker needs a position where the sensor misses bytes the endpoints have accepted, such as loss on a span port, or the ability to emit both directions of the TCP session. No account on the sensor is required.
Security Impact¶
For that one flow, DCERPC app-layer keywords stop matching after the gap, in both directions. A later request with opnum 4919 produces no alert, while the identical PDU without a hole does. The pre-gap request still alerts, which shows detection had started. The post-gap bytes are present in the IDS pcap and are covered by an ACK. tcp.reassembly_gap and app_layer.error.dcerpc_tcp.parser each increment by one. The Suricata process stays up. This run does not show a crash, and it does not claim the same failure for other protocols. Raw content matches were not used as the success signal.
Vulnerable Code¶
rust/src/dcerpc/dcerpc.rs registers the parser with flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS (line 1260) and then rejects the gap it just accepted:
if !stream_slice.is_gap() {
state.flow = Some(flow);
return state.handle_input_data(stream_slice, Direction::ToServer);
}
AppLayerResult::err()
parse_response does the same at lines 1089-1093. ts_gap / tc_gap are set only for a midstream start, not for STREAM_GAP, so the resync in handle_input_data (line 878) never sees the hole. AppLayerParserParse treats res.status < 0 as fatal (src/app-layer-parser.c line 1382) and StreamTcpDisableAppLayer (src/stream-tcp-reassemble.c line 447) sets STREAMTCP_FLAG_APP_LAYER_DISABLED for the whole TCP session.
Call Chain¶
CheckGap in src/stream-tcp-reassemble.c -> AppLayerHandleTCPData (src/app-layer.c:742, STREAM_GAP) -> AppLayerParserParse (src/app-layer-parser.c:1307, gap allowed because of ACCEPT_GAPS) -> parse_request (rust/src/dcerpc/dcerpc.rs:1067) returns AppLayerResult::err() at line 1071 -> src/app-layer-parser.c:1382 -> StreamTcpDisableAppLayer (src/stream-tcp-reassemble.c:447, called from src/app-layer-parser.c:1484) -> later chunks hit src/app-layer.c:727 and skip the parser.
Payload¶
Two little-endian DCERPC REQUEST PDUs (version 05 00, type 00, PFC first+last 03, drep 10 00 00 00). The first uses call id 1, opnum 1, stub DCESETUP1. The second uses call id 2, opnum 4919, stub DCEGAPMARK. On the attack flow the second PDU is placed 64 sequence numbers after the first, and the peer ACK covers the end of the second PDU. The control flow sends the same two PDUs with no hole. Client ports are 41010 (control) and 41011 (attack), server port 135.
Reproduction¶
Image jasonish/suricata:latest (8.0.7 RELEASE) sniffs bridge br-dce on docker network suri-dce, subnet 172.31.2.0/24. dce-app is python:3.11-slim at 172.31.2.10 and only emits the crafted frames. The sensor under test is the Suricata image. The peer address 172.31.2.20:135 is not a host service. This is passive bridge capture, so there is no published host port. The only detection change is a two-signature rules file for dcerpc.opnum:1 (sid 9001002) and dcerpc.opnum:4919 (sid 9001001). stats.interval is 1 and pcap-log is enabled so the run can record the gap counter and the bytes on the NIC. App-layer and stream defaults are otherwise untouched.
./verify.sh
fast.log from a passing run:
[1:9001002:1] DCERPC setup opnum 1 ... 172.31.2.20:135 -> 172.31.2.10:41010 [1:9001002:1] DCERPC setup opnum 1 ... 172.31.2.10:41010 -> 172.31.2.20:135 [1:9001001:1] DCERPC opnum 4919 ... 172.31.2.20:135 -> 172.31.2.10:41010 [1:9001001:1] DCERPC opnum 4919 ... 172.31.2.10:41010 -> 172.31.2.20:135 [1:9001002:1] DCERPC setup opnum 1 ... 172.31.2.20:135 -> 172.31.2.10:41011 [1:9001002:1] DCERPC setup opnum 1 ... 172.31.2.10:41011 -> 172.31.2.20:135
Sid 9001001 is absent on port 41011. Each matching signature is logged once per direction of the same flow, which is why the control port shows two lines. show_results.sh ends with:
RESULT: confirmed 64-byte TCP hole disables DCERPC parsing, so opnum 4919 raises no alert; the same PDU with no hole alerts control_hit_alerts=2 attack_hit_alerts=0 attack_setup_alerts=2 attack_seq_hole=64 attack_marker_on_ids_pcap=True attack_acked_past_marker=True attack_dcerpc_opnums=[1] tcp_reassembly_gap=1 dcerpc_parser_error=1 VERDICT: PASS
Fix Recommendation¶
On a real STREAM_GAP, set ts_gap or tc_gap and return AppLayerResult::ok() (or an incomplete result), and do not return AppLayerResult::err(). The next data slice already calls handle_input_data, which searches for the next DCERPC record when that flag is set. Returning an error is what makes AppLayerParserParse disable app-layer parsing for both directions. A gap the parser advertised as acceptable must not take that path.
Files
SB Updated by Shivani Bhardwaj 4 days ago
- Status changed from New to Assigned
- Assignee changed from OISF Dev to Shivani Bhardwaj
PA Updated by Philippe Antoine about 4 hours ago
- Tracker changed from Security to Bug
- Private changed from Yes to No
- Severity deleted (
LOW) - Disclosure Date deleted (
01/07/2027)
Requires packet loss at the sensor, which is not attacker-controlled.
Indeed so bug