Project

General

Profile

Security #9125

Updated by Victor Julien 6 days ago

Reported as ANT-2026-TN5YYMMT. 

 We would like to report a detection-evasion issue in the packet-pool recycle path that 
 is present in the shipped default configuration of current main and the 7.0.x / 8.0.x 
 release branches. A self-contained Docker reproducer that builds Suricata from source and 
 runs three differential pcaps through the shipped binary was provided as 
 ANT-2026-TN5YYMMT-e2e.zip. 

 This issue was found by Anthropic using Claude to study the security of open-source software 
 and validated manually by me from Ada Logics. 


 h2. AFFECTED COMPONENT 

   src/packet.c    PacketReinit()    -- the VLAN clear block at src/packet.c:121-123 
   current main HEAD d996d85dcfbf1349acf54d942abc86a13f9e699f (re-gated 2026-09-17) 

   Present unchanged on main-8.0.x (src/packet.c:107) and main-7.0.x (src/packet.c:104). 
   We confirmed src/packet.c is byte-unchanged on main between our first analysis at 
   928ac012 (2026-09-08) and this HEAD, so the line numbers below are identical there. 

 h2. ROOT CAUSE 

 Packet.vlan_id is uint16_t[VLAN_MAX_LAYERS] with VLAN_MAX_LAYERS == 3, and DecodeVLAN() 
 populates all three slots for a QinQinQ (triple-802.1Q-tagged) frame. PacketReinit(), 
 which runs on every packet-pool recycle, explicitly zeroes vlan_id[0], vlan_id[1] and 
 vlan_idx, but NOT vlan_id[2]: 

 <pre> 
     /* src/packet.c:121-123 -- PacketReinit() */ 
     p->vlan_id[0] = 0; 
     p->vlan_id[1] = 0; 
     p->vlan_idx = 0; 
     /* vlan_id[2] is left as whatever the previous occupant of this pool slot set */ 
 </pre> 

 The flow engine (FlowGetHash() / CmpVlanIds()) and the defrag tracker 
 (CMP_DEFRAGTRACKER) fold all three vlan_id slots into their keys unconditionally and 
 never consult vlan_idx. Consequently, once a pooled Packet has carried a triple-tagged 
 frame, every later packet served from that slot is keyed with the attacker's third tag, 
 even though that later packet carries no VLAN tags at all. 

 When the priming (triple-tagged) frame arrives in the middle of a monitored stream, the 
 packets before and after it are keyed differently and land in DIFFERENT Flow objects. 
 Stream reassembly is split across the two flows, so a signature whose content spans two 
 TCP segments never matches -- while the target host, which ignores the stray VLAN tag on 
 a frame that is not part of the attacked flow, reassembles the stream normally. 

 The priming frame need not even belong to the attacked 5-tuple, and need not be a frame 
 Suricata can otherwise parse; it only has to occupy and release a pool slot that the 
 victim flow's packets later reuse. 

 h2. IMPACT / SEVERITY 

 Detection evasion (CWE-226 / CWE-459 stale object state -> CWE-693 protection-mechanism 
 failure). An unauthenticated remote party (or on-path injector) that can place one 
 triple-tagged Ethernet frame on the monitored segment silently splits subsequent flows, 
 causing multi-segment signatures to miss. No crash, no memory-safety violation; this is 
 an inspection-integrity issue. 

 We read this as HIGH under your SECURITY.md rubric (a traffic-triggered evasion in a 
 Tier 1, default-enabled feature -- VLAN decode and flow keying are always on), but VLAN 
 QinQinQ is a somewhat unusual on-wire condition, so we would equally understand a MODERATE 
 rating. 

 h2. REPRODUCTION 

 ANT-2026-TN5YYMMT-e2e.zip is self-contained (one build, one run, no prebuilt 
 base image): 

 <pre> 
     unzip ANT-2026-TN5YYMMT-e2e.zip && cd ANT-2026-TN5YYMMT 
     docker build -t ant-tn5yymmt docker-e2e/ 
     docker run --rm ant-tn5yymmt 
 </pre> 

 It clones OISF/suricata, checks out ${TARGET_COMMIT} (default: main HEAD d996d85d), builds 
 it (clang, plain -O1 -g, no sanitizer), and runs three pcaps that differ ONLY in the VLAN 
 tagging of one priming frame that is not part of the attacked flow, with one signature 
 @content:"FOOBAR"@ that spans two TCP segments. Build at another commit with 
 @--build-arg TARGET_COMMIT=<sha>@. A suricata-verify-style test can be provided on request. 

 h2. VALIDATED EVIDENCE (re-run at HEAD d996d85d, 2026-09-17) 

 Suricata built from OISF/suricata at HEAD d996d85d (clang, -O1 -g, no sanitizer). Three 
 pcaps carry the identical two-segment "FOOBAR" stream and one priming frame that is not 
 part of the attacked 5-tuple; they differ ONLY in that priming frame's VLAN tagging and 
 position: 

 <pre> 
   case                                          sid:1 alerts     Flow objects for the 5-tuple 
   control A    (2 VLAN tags, mid-stream)                1                1 
   control B    (3 VLAN tags, priming frame FIRST)       1                1 
   POC          (3 VLAN tags, priming frame MID)         0                2 
 </pre> 

 Verdict: VULNERABLE at HEAD. In control A the priming frame carries two VLAN tags, which 
 PacketReinit() DOES clear (vlan_id[0], vlan_id[1]), so every packet keys into one Flow and 
 the multi-segment @content:"FOOBAR"@ signature fires. In control B the frame carries three 
 tags but arrives first, so the whole stream inherits the same stale vlan_id[2] -- a wrong 
 but consistent key -- and again lands in one Flow, so the rule still fires. In the POC the 
 three-tag frame is inserted MID-stream: PacketReinit() leaves vlan_id[2] stale on the pool 
 slots reused after it, so the segments before and after the priming frame hash into TWO 
 different Flow objects (pkts_toserver 3 + 3 instead of 6), reassembly is split, and the 
 signature is evaded (0 alerts). The only variable across the three runs is the priming 
 frame's tagging/position, which pins the evasion to the vlan_id[2] that PacketReinit() 
 fails to clear. 

 h2. DISCLOSURE 

 Found by Anthropic using Claude to study the security of open-source software, validated 
 manually by Ada Logics. Handled under Anthropic's coordinated vulnerability disclosure 
 policy (90 days): https://www.anthropic.com/coordinated-vulnerability-disclosure. Please 
 keep this private per your usual timeline; we will follow your lead on publication. If you 
 publish a GHSA, please credit Claude (Anthropic) and Ada Logics. 

Back