Project

General

Profile

Actions

Bug #9186

open
JI PA

http: gap before protocol detection permanently skips the HTTP request behind it

Bug #9186: http: gap before protocol detection permanently skips the HTTP request behind it

Added by Jason Ish 4 days ago. Updated about 4 hours ago.

Status:
Assigned
Priority:
Normal
Target version:
Affected Versions:
Effort:
Difficulty:
Label:

Description

Summary

If a TCP direction is still ALPROTO_UNKNOWN, one ACK'd sequence hole marks app-layer protocol detection complete and returns failure before any protocol bytes are parsed. STREAM_START is never attached again, so TCPProtoDetect does not run, and a negative gap result also skips the bytes that sit behind the hole in that same pass. A full HTTP request placed immediately after the hole produces no alert and no HTTP transaction, while the same request with no hole is alerted and logged. The engine keeps running. Unpack the attachment and run ./verify.sh.

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, Suricata 8.0.7 RELEASE, in default IDS mode. Stream reassembly and HTTP are left at their defaults. No extra app-layer protocol was enabled. -k none is only there because veth checksum offload marks ordinary frames invalid on the bridge. flow-timeouts.tcp.closed=1 and stats.interval=1 only make the flow record and the tcp.reassembly_gap counter show up before the test ends.

Preconditions

The sensor must see the handshake, then a toserver hole before any application payload, then a complete HTTP request, then an ACK that covers both the hole and that request. IDS mode only feeds ACK'd data to the app layer, so a real server stack that never received the missing bytes will not ACK over the hole. The lab injects that ACK on the monitored bridge. The same pattern is what the sensor sees when it misses the leading bytes and the peer still ACKs them. No account is required. A toclient HTTP response can still let the opposite direction identify the protocol, so the attack flow does not elicit one.

Security Impact

The toserver direction stops protocol detection for the rest of the flow. The HTTP rule does not fire and no http event is written. The flow record shows ts_gap and alerted: false, and app_proto stays unset. tcp.reassembly_gap increments, which is the counter next to the gap handoff. The process does not crash. This run does not show memory corruption, a second-direction bypass after the peer has already identified the protocol, or code execution.

Vulnerable Code

src/app-layer.c, gap branch in AppLayerHandleTCPData. A hole on an unknown direction sets the completed flag and fails out while f->alproto is still unknown, so progress is not advanced and the parser is not called.

if (flags & STREAM_GAP) {
if (alproto == ALPROTO_UNKNOWN) {
StreamTcpSetStreamFlagAppProtoDetectionCompleted(*stream);
if (f->alproto == ALPROTO_UNKNOWN) {
goto failure;
}
}
}

src/stream-tcp-reassemble.c then drops the rest of the pass. StreamGetAppLayerFlags adds STREAM_START only while the completed flag is clear, and TCPProtoDetect requires both ALPROTO_UNKNOWN and STREAM_START. Later bytes hit the generic branch and are parsed only when f->alproto is already known.

int r = AppLayerHandleTCPData(..., StreamGetAppLayerFlags(...) | STREAM_GAP, ...);
if (r < 0)
return 0;

Call Chain

HandleEstablishedPacketToClient (src/stream-tcp.c:3109) -> StreamTcpReassembleHandleSegment (src/stream-tcp-reassemble.c:2008) -> ReassembleUpdateAppLayer (src/stream-tcp-reassemble.c:1263) -> gap handoff (src/stream-tcp-reassemble.c:1292) -> AppLayerHandleTCPData (src/app-layer.c:742).

Payload

After the three-way handshake, the first toserver payload starts 100 bytes past the initial sequence, and the payload is the whole request:

GET /pregap-marker HTTP/1.1
Host: app
Connection: close

An ACK from the server side then covers ISN+1+100+len(request). The control flow sends that same request at ISN+1 with no hole.

Reproduction

jasonish/suricata:latest sniffs bridge br-pregap on 172.31.4.0/24. The HTTP sink is 172.31.4.20:18080, also published on the host as 127.0.0.1:18080. Host-network capture is required because the sensor has to see both directions on the bridge.

./verify.sh
control_alerts=1
control_http=1
attack_alerts=0
attack_http=0
nic_post_gap=True
reassembly_gap=1
suricata_running=True
VERDICT: PASS

The attack flow record has flow_ts_gap=true, flow_alerted=false, and no app_proto. The bridge capture shows the request in one segment at ISN+1+100.

Fix Recommendation

Do not set APPPROTO_DETECTION_COMPLETED for a gap that arrives while that direction's alproto is still ALPROTO_UNKNOWN. Advance app_progress over the hole without closing detection, and do not return 0 from ReassembleUpdateAppLayer before the bytes behind the hole are delivered. Those bytes still need STREAM_START so TCPProtoDetect runs. If a leading hole is treated as "detection impossible", emit the same explicit skipped-detection event the bail path already emits, and do not fail silently.


Files

JI Updated by Jason Ish 4 days ago Actions #1

Can likely make it a bug, gap recovery loss of visibility issue.

JI Updated by Jason Ish 4 days ago Actions #2

  • Target version changed from TBD to 9.0.0-beta1

PA Updated by Philippe Antoine about 4 hours ago Actions #3

  • Tracker changed from Security to Bug
  • Status changed from New to Assigned
  • Assignee changed from OISF Dev to Philippe Antoine
  • Private changed from Yes to No
  • Disclosure Date deleted (01/07/2027)

TCP gaps are not attacked controlled, so this is a bug

Actions

Also available in: PDF Atom