Project

General

Profile

Actions

Security #9125

open
JI VJ

vlan: PacketReinit() leaves vlan_id[2] stale on packet-pool recycle, splitting flows and enabling multi-segment rule evasion

Security #9125: vlan: PacketReinit() leaves vlan_id[2] stale on packet-pool recycle, splitting flows and enabling multi-segment rule evasion

Added by Jason Ish 6 days ago. Updated about 21 hours ago.

Status:
In Review
Priority:
Normal
Assignee:
Target version:
Affected Versions:
Label:
CVE:
Git IDs:
Severity:
LOW
Disclosure Date:
12/16/2026
GHSA:

Description

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.

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.

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_id0, vlan_id1 and
vlan_idx, but NOT vlan_id2:

    /* 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 */

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.

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.

REPRODUCTION

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

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

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.

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:

  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

Verdict: VULNERABLE at HEAD. In control A the priming frame carries two VLAN tags, which
PacketReinit() DOES clear (vlan_id0, vlan_id1), 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_id2 -- 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_id2 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_id2 that PacketReinit()
fails to clear.

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.


Subtasks

JI Updated by Jason Ish 6 days ago ยท Edited Actions #1

  • Severity set to LOW

Possible impact: An attacker able to inject a triple-VLAN-tagged frame on a monitored segment may cause subsequent untagged packets to be assigned to a different flow. This can prevent Suricata from matching signatures that span TCP segments, resulting in missed alerts.

SV MR on GL.

VJ Updated by Victor Julien 6 days ago Actions #2

  • Subject changed from Suricata: PacketReinit() leaves vlan_id[2] stale on packet-pool recycle, splitting flows and enabling multi-segment rule evasion (ANT-2026-TN5YYMMT) to vlan: PacketReinit() leaves vlan_id[2] stale on packet-pool recycle, splitting flows and enabling multi-segment rule evasion (ANT-2026-TN5YYMMT)

VJ Updated by Victor Julien 6 days ago Actions #3

  • Subject changed from vlan: PacketReinit() leaves vlan_id[2] stale on packet-pool recycle, splitting flows and enabling multi-segment rule evasion (ANT-2026-TN5YYMMT) to vlan: PacketReinit() leaves vlan_id[2] stale on packet-pool recycle, splitting flows and enabling multi-segment rule evasion
  • Description updated (diff)

JI Updated by Jason Ish 5 days ago Actions #4

  • Target version changed from TBD to 9.0.0-beta1
  • Label Needs backport to 8.0 added

OT Updated by OISF Ticketbot 5 days ago Actions #5

  • Subtask #9131 added

OT Updated by OISF Ticketbot 5 days ago Actions #6

  • Label deleted (Needs backport to 8.0)

VJ Updated by Victor Julien about 22 hours ago Actions #7

  • Private changed from Yes to No

Agree with severity LOW, so making public.

VJ Updated by Victor Julien about 21 hours ago Actions #8

  • Status changed from New to In Review
  • Assignee changed from OISF Dev to Victor Julien
Actions

Also available in: PDF Atom