

Executive Summary
On 18 September 2026 Asim Manizada published four Linux local-root bugs found with the same agentic graph/geometry harness behind CIFSwitch, OVSwrap, and the “drunk LLM” kernel work. DirtyAH6 (CVE-2026-80844), TUNderflow (CVE-2026-81000), PPPoEject (CVE-2026-68121), and DiagSpill (CVE-2026-74469). The underlying mistakes are 10–21 years old. The first three LPEs want unprivileged user namespaces (or equivalent CAP_NET_ADMIN in a userns-owned netns). DiagSpill does not. DirtyAH6’s and DiagSpill’s corruption can be remote in very specific configs; DirtyAH6 was remote-rooted in lab with on-target grooming; fully remote grooming looks extremely hard. DiagSpill is a remote crash/DoS if SCTP ADD-IP is on, not a remote-root path he can see.
He reported the set to security@kernel.org in mid-July 2026. The coordinated embargo with linux-distros@ lifted at 06:00 UTC on 18 September. The first stable kernels that contain all four fixes are 5.10.270, 5.15.221, 6.1.188, 6.6.157, 6.12.109, 6.18.50, and 7.2.4. AppArmor and SELinux did not block the LPEs in his testing, except Ubuntu cases that already block unprivileged userns. This page keeps every original table, fix snippet, and changelog list, then adds kitchen-table walkthroughs, operator hunting notes, CWE/ATT&CK mapping, and patch-math so a desktop owner and a distro kernel person can both finish the article.
TLDR: Four more Linux LPEs; two of the corruption bugs are reachable remotely under very specific circumstances, with one theoretically remote-groomable to remote root.
Asim Manizada, 18 September 2026
How to read this page
The original write-up is short on purpose: four bugs, one embargo morning, a time crunch. That is a gift if you already live in AH6 and skb headroom, and a brick wall if you do not. Green boxes translate the same facts into a house, a mailman, a cardboard box, and a guest list. Blue boxes keep the operator verbs: which kmod, which cap, which sysctl, which stable. Original tables and C snippets sit at the same reading positions as on heyitsas.im. Exploit repositories are linked, not copied.
- If you run a laptop or a homelab: jump to Are you affected?, then Mitigation. You want the all-four floor, not a single CVE blog post.
- If you run a fleet: inventory AH6/XFRM, TUN/TAP, PPPoE, SCTP/
sctp_diag, unprivileged userns, and SCTP ADD-IP. Then patch math. - If you hunt kernel bugs: the interesting claim is not “LLM found four CVEs.” It is that graph tracking of security-relevant objects plus geometric memory-state tooling hit four old, independently exploitable primitives in four subsystems.
- If you write detections: do not grep for GitHub repo names. Hunt patch level, kconfig, and the rare remote preconditions (transport-mode IPv6 AH; SCTP ADD-IP).

Background
Shorter than his usual posts: time crunch, four bugs at once. Discovery stacked graph tracking of security-relevant objects (CIFSwitch) with geometric memory-state tooling for agents (OVSwrap). Harness DNA is in Getting LLMs Drunk to Find Remote Linux Kernel OOB Writes, evolved since. This is the public end of that experiment for a while — see the outro.
The age of the bugs matters more than the tooling slogan. DirtyAH6’s affected range starts at 2.6.12. PPPoEject starts at 2.6.12. TUNderflow starts at 4.6. DiagSpill starts at 4.7. Those are not “new attack surfaces from 2025.” They are decades of IPv6 IPsec, virtual NICs, DSL-era PPPoE, and SCTP diagnostics that nobody asked a fuzzer to think about geometrically. When a 16-bit counter and a Netlink dump have lived next to each other since 4.7, the interesting question is not “why now,” it is “why did every other harness miss the wrap.”
The embargo shape is the usual kernel.org one: report to security@kernel.org, fixes land in mainline and stables over weeks, linux-distros@ gets a coordinated date, public write-up and PoCs drop together. Maintainers called out in the original: Stefan Klassert, Xin Long, Paolo Abeni, Willem de Bruijn, Greg KH, and others. If you ship a distro kernel, those names are your changelog trail, not a press list.
The four bugs
| Name | CVE | Class | Userns for LPE? | Remote? |
|---|---|---|---|---|
| DirtyAH6 | CVE-2026-80844 | AH6 rthdr memmove OOB | Yes (CAP_NET_ADMIN+RAW in userns) | Crash/DoS if AH transport on IPv6 router; lab remote-root with on-target groom |
| TUNderflow | CVE-2026-81000 | TUN align underflow → skb OOB | Yes (CAP_NET_ADMIN) | Local (needs a headroom-propagating device path) |
| PPPoEject | CVE-2026-68121 | UAF after pskb_expand_head | Yes (CAP_NET_ADMIN) | Local (FUSE + GRE/team race) |
| DiagSpill | CVE-2026-74469 | u16 transport_count wrap, ~8 MiB Netlink spill | No | DoS if ADD-IP + AUTH/noauth; no remote-root path seen |
Two facts are easy to mash together and should stay apart. The bug is the memory mistake: an OOB memmove, an underflowed skb head, a stale pointer, a wrapped counter. The LPE is a groomed, target-specific chain that turns that mistake into root. The tables later in this page are about the bugs. The GitHub READMEs are about the LPEs. A kernel can be vulnerable to the bug without matching the PoC’s RAM map.
The basics
DirtyAH6: IPsec Authentication Header proves packet bytes did not change. Linux IPv6 AH lives in AH6 on XFRM. Before computing or checking auth data it rewrites IPv6 fields into the expected form, including addresses in a routing header. That rearrange is the whole plot: the kernel is supposed to shuffle labels and then weigh the wax seal. If the shuffle walks off the packet, the seal is being computed on the wrong memory.
TUNderflow: TUN/TAP move packets between kernel and userspace via /dev/net/tun. Devices stacked on others pass receive headroom through ndo_set_rx_headroom(); Open vSwitch can carry that value onto a TUN/TAP port. Headroom is “how much empty cardboard to leave in front of the packet so later headers fit.” If the asked-for empty space is larger than the box, the allocator’s math wraps.
PPPoEject: PPPoE carries PPP in Ethernet. pppoe_sendmsg() builds an skb, copies payload, asks the lower device to create its hardware header, then fills the PPPoE header. The bug is a classic “I kept a sticky note on a box that the warehouse already recycled.”
DiagSpill: An SCTP association can have many peer transports (one per peer address). sctp_diag reports sockets and peers through sock_diag, stuffing a sockaddr_storage per transport into a Netlink reply. The guest list is 16 bits. The banquet hall is not.


Vulnerability details
DirtyAH6 — CVE-2026-80844
ipv6_rearrange_rthdr() took the address count from hdrlen, then used segments - segments_left to move an address pointer without checking segments_left <= segments. A raw IPv6 HDRINCL packet with hdrlen=2 and segments_left=255 walked the pointer back 4,064 bytes and passed 4,064 to memmove() — out of bounds. If the target is an IPv6 router or gateway adding AH in transport mode, the same bug is a remote crash/DoS. With memory grooming on the target he got remote root in lab. Achieving root with remote-only grooming looks extremely difficult, not a priori impossible.
memmove length and destination derived from an unchecked routing-header field. Local trigger wants CAP_NET_ADMIN and CAP_NET_RAW in the userns that owns the attacker netns, plus AH6/XFRM. Remote crash clause: IPv6 gateway, AH in transport mode. The DirtyFrag ESP fix does not close this path — different OOB, same family of IPsec packet munging. Commit 7bad4bda74dc.The 4,064-byte figure is not a magic constant from a fuzzer log. hdrlen=2 means two 32-bit units after the first 8 bytes of the extension header, so the legitimate address count is small. segments_left=255 is a one-byte field at its maximum. The pointer walks (segments - 255) addresses backwards, which under unsigned or poorly-checked arithmetic becomes a large positive byte count into memmove. The original post’s number is the one to cite; the lesson for audit is “any length derived from a - b on packet fields needs b <= a before the subtract.”
TUNderflow — CVE-2026-81000
tun_set_headroom() stored receive headroom in tun->align; tun_get_user() used it to decide how much packet to keep in the head. A netkit device with 4,096 bytes of headroom, under VXLAN and an OVS datapath, could pass 4,160 to a raw TUN port. SKB_MAX_HEAD(4160) underflowed. Negative good_linear became a huge size_t; prepad+linear and len-linear wrapped; tun_alloc_skb() left skb->data 64 bytes past a 4,096-byte allocation. Later processing read and wrote outside the skb head.


CAP_NET_ADMIN. Commit 447c9303942c.Why 4,160 versus 4,096? Headroom is additive along a stack. A netkit port already configured for a page of headroom, plus VXLAN’s extra header budget, plus whatever OVS copies through ndo_set_rx_headroom(), can exceed the one-page skb-head budget that SKB_MAX_HEAD() assumes. The macro does not clamp; it subtracts. Underflow of a signed “how much linear room is left” value, then a cast to size_t, is an old Linux skb class. The fix clamps stored TUN headroom to min(SKB_MAX_HEAD(0), U16_MAX-1) minus TAP Ethernet overhead or the raw-TUN protocol byte, and pskb_may_pulls before reading the version nibble or Ethernet header.
PPPoEject — CVE-2026-68121
pppoe_sendmsg() kept a pointer into the skb head across dev_hard_header(), even though a device callback can pskb_expand_head() and free that head. Blocking the payload copy on FUSE while adding the first GRE or IP6GRE port to an empty team or bonding device triggered the realloc — ejecting the old head while PPPoE still pointed into it. Header and length writes used the stale pointer.

skb_network_header), let pskb_expand_head() update offsets, reload with pppoe_hdr(skb) after dev_hard_header(). Local LPE wants userns + CAP_NET_ADMIN + PPPoE + a lower-device callback that reallocates. Commit e9c238f6fe42.Use-after-free on an skb head is a nicer primitive than a random 4k OOB, because the freed slab is a known size and a known cache. The published PoC races populated file-descriptor tables into that hole and redirects a live fd to a fake struct file. That is a target-specific groom, not a generic “UAF equals root” law. The bug still exists on any affected kernel that can hit the realloc path, even if your fd-table spray looks nothing like his.
DiagSpill — CVE-2026-74469
An association can have 65,536 peer transports, but transport_count is 16 bits, so the 65,536th wraps to 0. sctp_diag reserved no peer payload then copied the full list — about 8 MiB past the Netlink response. Remote crash/DoS if ASCONF/ADD-IP are enabled with SCTP-AUTH or net.sctp.addip_noauth_enable=1 (all off by default): a malicious peer adds transports; something on the target (for example ss) issues sock_diag. He does not see a path to full remote root even with perfect remote grooming.

sctp and sctp_diag are loaded, a local unprivileged process can grow an association and dump it. Remote DoS clause is picky: addip_enable plus either AUTH or addip_noauth_enable, then a local sock_diag consumer. ~8 MiB is “a sockaddr_storage times 65,536, written into a Netlink skb that reserved nothing.” Commit bd0e9289e264.DiagSpill is the one that should move locked-down hosts up the patch queue. ChromeOS-style images, hardened containers that already killed unprivileged userns, and “we do not give developers CAP_NET_ADMIN” fleets still load SCTP because a telephony or WebRTC dependency pulled it in. If sctp_diag is there, the local corruption path does not care about your LSM. His tests: AppArmor (except Ubuntu’s userns block) and SELinux did not stop the LPEs — and DiagSpill never needed the userns block in the first place.
The fixes
DirtyAH6 — check segments_left, return -EINVAL on the existing AH6 paths. Original snippet:
segments = rthdr->hdrlen >> 1;
if (segments_left > segments)
return -EINVAL;
rthdr->segments_left = 0;
TUNderflow — clamp TUN headroom to the one-page skb-head budget and 16-bit header offset; pull before use. Original snippet:
max_headroom = min_t(size_t, SKB_MAX_HEAD(0), U16_MAX - 1);
if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP)
max_headroom -= ETH_HLEN + NET_IP_ALIGN;
else
max_headroom -= 1;
tun->align = clamp_t(int, new_hr, NET_SKB_PAD, max_headroom);
...
case IFF_TUN:
if (tun->flags & IFF_NO_PI) {
u8 ip_version;
if (!pskb_may_pull(skb, 1)) {
err = -EINVAL;
goto drop;
}
ip_version = skb->data[0] >> 4;
...
}
...
break;
case IFF_TAP:
if (!pskb_may_pull(skb, ETH_HLEN)) {
err = -ENOMEM;
drop_reason = SKB_DROP_REASON_HDR_TRUNC;
goto drop;
}
PPPoEject — reload the PPPoE header via the skb network-header offset after device header creation; pskb_expand_head() updates that offset when it moves the head. Original snippet:
dev_hard_header(skb, dev, ETH_P_PPP_SES,
po->pppoe_pa.remote, NULL, total_len);
ph = pppoe_hdr(skb);
memcpy(ph, &hdr, sizeof(struct pppoe_hdr));
DiagSpill — refuse a new unique peer once transport_count == U16_MAX (existing peer still returns). Original snippet:
if (asoc->peer.transport_count == U16_MAX)
return NULL;
peer = sctp_transport_new(asoc->base.net, addr, gfp);
The four commits, in the same order: 7bad4bda74dc (DirtyAH6), 447c9303942c (TUNderflow), e9c238f6fe42 (PPPoEject), bd0e9289e264 (DiagSpill). Maintainers called out: Stefan Klassert, Xin Long, Paolo Abeni, Willem de Bruijn, Greg KH, and others.
Corresponding changelogs for the first upstream-fixed releases, reproduced from the original (these are the per-bug floors, not the all-four floor):
DirtyAH6 first-fixed changelogs
- Linux 5.10.269
- Linux 5.15.220
- Linux 6.1.187
- Linux 6.6.156
- Linux 6.12.108
- Linux 6.18.49
- Linux 7.1.13
- Linux 7.2.3
TUNderflow first-fixed changelogs
PPPoEject first-fixed changelogs
- Linux 5.10.265
- Linux 5.15.216
- Linux 6.1.183
- Linux 6.6.148
- Linux 6.12.101
- Linux 6.18.42
- Linux 7.1.6
- Linux 7.2
DiagSpill first-fixed changelogs
- Linux 5.10.265
- Linux 5.15.216
- Linux 6.1.183
- Linux 6.6.151
- Linux 6.12.103
- Linux 6.18.44
- Linux 7.1.8
- Linux 7.2

The exploits (links, not copies)
All four are memory bugs that need per-target grooming. Linked PoCs name distro, kernel, CPU, and memory in the READMEs. He reproduced across distros with tuning; he ships only a couple of target-specific PoCs. They can still corrupt the wrong memory — dedicated VMs only.
- DirtyAH6 — github.com/manizada/DirtyAH6: cousin of DirtyFrag’s ESP variant; corrupts
skb_shared_infovia a separate AH6 rthdrmemmoveOOB (not closed by the DirtyFrag fix); later ESP decrypt writes a file-backed fragment;pam_rootok.so→pam_permit.so;su. - TUNderflow — github.com/manizada/TUNderflow: file-backed pipe buffers next to the bad TUN packet; OVS OOB sets
PIPE_BUF_FLAG_CAN_MERGE; pipe write does the same PAM swap. - PPPoEject — github.com/manizada/PPPoEject: race populated fd tables into the freed skb head; PPPoE writes redirect a live fd to a fake
struct file; close → controlled kernel callback → root creds. - DiagSpill — github.com/manizada/DiagSpill: groom
sock_diagoverwrite into page tables; map host memory; rewrite a cred; sudoers; root shell.
Are you affected?
The tables below are pre-reqs for the bugs, not the full LPE chains. Treat the kernel as vulnerable if it is in range and the subsystem is available.
DirtyAH6
AH6/XFRM present. Unprivileged userns for the specific LPE PoC. CAP_NET_ADMIN + CAP_NET_RAW in the userns that owns the attacker netns.
| Kernel series | Affected releases | First fixed release |
|---|---|---|
| 2.6.12–5.9 | All releases | EOL; no upstream stable fix |
| 5.10.y | 5.10.0–5.10.268 | 5.10.269 |
| 5.11–5.14 | All releases | EOL; no upstream stable fix |
| 5.15.y | 5.15.0–5.15.219 | 5.15.220 |
| 5.16–6.0 | All releases | EOL; no upstream stable fix |
| 6.1.y | 6.1.0–6.1.186 | 6.1.187 |
| 6.2–6.5 | All releases | EOL; no upstream stable fix |
| 6.6.y | 6.6.0–6.6.155 | 6.6.156 |
| 6.7–6.11 | All releases | EOL; no upstream stable fix |
| 6.12.y | 6.12.0–6.12.107 | 6.12.108 |
| 6.13–6.17 | All releases | EOL; no upstream stable fix |
| 6.18.y | 6.18.0–6.18.48 | 6.18.49 |
| 6.19–7.0 | All releases | EOL; no upstream stable fix |
| 7.1.y | 7.1.0–7.1.12 | 7.1.13 |
| 7.2.y | 7.2.0–7.2.2 | 7.2.3 |
TUNderflow
Unprivileged userns + TUN/TAP, plus some device path that can propagate oversized receive headroom (the PoC uses netkit/VXLAN/OVS). CAP_NET_ADMIN in that userns.
| Kernel series | Affected releases | First fixed release |
|---|---|---|
| 4.6–5.9 | All releases | EOL; no upstream stable fix |
| 5.10.y | 5.10.0–5.10.269 | 5.10.270 |
| 5.11–5.14 | All releases | EOL; no upstream stable fix |
| 5.15.y | 5.15.0–5.15.220 | 5.15.221 |
| 5.16–6.0 | All releases | EOL; no upstream stable fix |
| 6.1.y | 6.1.0–6.1.187 | 6.1.188 |
| 6.2–6.5 | All releases | EOL; no upstream stable fix |
| 6.6.y | 6.6.0–6.6.156 | 6.6.157 |
| 6.7–6.11 | All releases | EOL; no upstream stable fix |
| 6.12.y | 6.12.0–6.12.108 | 6.12.109 |
| 6.13–6.17 | All releases | EOL; no upstream stable fix |
| 6.18.y | 6.18.0–6.18.49 | 6.18.50 |
| 6.19–7.0 | All releases | EOL; no upstream stable fix |
| 7.1.y | 7.1.0–7.1.13 | EOL; no upstream stable fix |
| 7.2.y | 7.2.0–7.2.3 | 7.2.4 |
PPPoEject
Unprivileged userns + PPPoE, plus a lower-device header callback that can realloc the skb head during dev_hard_header(). CAP_NET_ADMIN.
| Kernel series | Affected releases | First fixed release |
|---|---|---|
| 2.6.12–5.9 | All releases | EOL; no upstream stable fix |
| 5.10.y | 5.10.0–5.10.264 | 5.10.265 |
| 5.11–5.14 | All releases | EOL; no upstream stable fix |
| 5.15.y | 5.15.0–5.15.215 | 5.15.216 |
| 5.16–6.0 | All releases | EOL; no upstream stable fix |
| 6.1.y | 6.1.0–6.1.182 | 6.1.183 |
| 6.2–6.5 | All releases | EOL; no upstream stable fix |
| 6.6.y | 6.6.0–6.6.147 | 6.6.148 |
| 6.7–6.11 | All releases | EOL; no upstream stable fix |
| 6.12.y | 6.12.0–6.12.100 | 6.12.101 |
| 6.13–6.17 | All releases | EOL; no upstream stable fix |
| 6.18.y | 6.18.0–6.18.41 | 6.18.42 |
| 6.19–7.0 | All releases | EOL; no upstream stable fix |
| 7.1.y | 7.1.0–7.1.5 | 7.1.6 |
DiagSpill
SCTP + sctp_diag. No userns, no special CAPs. That is the scary one for locked-down hosts that still load SCTP.
| Kernel series | Affected releases | First fixed release |
|---|---|---|
| 4.7–5.9 | All releases | EOL; no upstream stable fix |
| 5.10.y | 5.10.0–5.10.264 | 5.10.265 |
| 5.11–5.14 | All releases | EOL; no upstream stable fix |
| 5.15.y | 5.15.0–5.15.215 | 5.15.216 |
| 5.16–6.0 | All releases | EOL; no upstream stable fix |
| 6.1.y | 6.1.0–6.1.182 | 6.1.183 |
| 6.2–6.5 | All releases | EOL; no upstream stable fix |
| 6.6.y | 6.6.0–6.6.150 | 6.6.151 |
| 6.7–6.11 | All releases | EOL; no upstream stable fix |
| 6.12.y | 6.12.0–6.12.102 | 6.12.103 |
| 6.13–6.17 | All releases | EOL; no upstream stable fix |
| 6.18.y | 6.18.0–6.18.43 | 6.18.44 |
| 6.19–7.0 | All releases | EOL; no upstream stable fix |
| 7.1.y | 7.1.0–7.1.7 | 7.1.8 |
Mouthful: DirtyAH6 trigger wants CAP_NET_ADMIN and CAP_NET_RAW in the userns owning the netns. TUNderflow and PPPoEject want CAP_NET_ADMIN there. In an already-CAP’d container those paths can corrupt the host kernel without creating new userns (container escape theoretically possible; he did not PoC it, same as CIFSwitch/OVSwrap). Unprivileged userns is just the common path. DiagSpill: no CAPs, no userns, as long as SCTP and sctp_diag exist.

Mitigation
Upgrade to a kernel with all four. First stables that contain the set: 5.10.270, 5.15.221, 6.1.188, 6.6.157, 6.12.109, 6.18.50, 7.2.4.
If you cannot patch: disabling unprivileged userns removes the ordinary-user path to the first three (not CAP’d containers). DiagSpill remains. You can also disable unused AH6, TUN, PPPoE, SCTP/sctp_diag. Do not treat “disable the PoC’s extra kmods” as a real mitigation — other paths to root may exist. Patch.
Outro
Personal/professional developments: this likely ends the public AI-assisted vuln-hunting experiment for a while. Thanks for riding along.
That sentence is doing more work than a blog farewell. The last two years of public Linux kernel LPEs from this author were a running argument that agentic hunting with graph and geometry tools can find old, exploitable memory bugs in boring subsystems — not just “LLM wrote a syzkaller prog.” The experiment going quiet does not make 5.10.268 safe. The stables still have to move.
A glossary for both sides of the table
| Term | Kitchen | Operator |
|---|---|---|
| AH6 / XFRM | A wax seal on the package after the clerk rewrote the address labels. | IPv6 IPsec Authentication Header; ipv6_rearrange_rthdr(). |
| segments_left | How many stickers still to shuffle. Unchecked, the clerk walks off the table. | rthdr field; 255 with hdrlen=2 → 4064-byte memmove. |
| TUN align | How much empty cardboard to leave in front of the packet. | tun->align from ndo_set_rx_headroom; SKB_MAX_HEAD underflow. |
| pskb_expand_head | The box is too small, so they pour it into a new box and throw the old one. | PPPoE still held a pointer into the old skb head. |
| transport_count u16 | A 16-bit guest list for 65,536 chairs. | 65,536th peer wraps to 0; diag copies ~8 MiB past the skb. |
| unprivileged userns | A hall pass that lets you pretend to be the network janitor. | Common LPE on-ramp for the first three; not required for DiagSpill. |
| CAP_NET_ADMIN | Permission to rearrange the building’s cables. | Enough, in a userns-owned netns, to hit TUN/PPPoE; plus CAP_NET_RAW for DirtyAH6. |
| sock_diag | Asking the front desk to print every guest’s name and phone number. | Netlink dump; sctp_diag builds one sockaddr_storage per transport. |
| ADD-IP / ASCONF | Letting a member add extra phone numbers from outside. | Off by default; required for DiagSpill’s remote DoS clause. |
| skb_shared_info | The packing slip glued to the bottom of the crate. | Trailer of an skb; DirtyAH6 PoC corrupts it, then ESP writes a file-backed frag. |
Why two of these scare routers

Local root on a laptop with userns is a Tuesday. DirtyAH6 on an IPv6 gateway that encapsulates AH in transport mode is a remote memmove into packet metadata. That is why he bothers to say “router/gateway.” The LPE PoC still uses userns; the crash does not need a local account. A border box that terminates or encapsulates IPsec AH for IPv6 should be treated as a remote-DoS (and, in his lab, a remote-root-with-local-groom) until it is on the DirtyAH6 first-fixed release or newer.
DiagSpill’s remote path is pickier: ADD-IP off by default, AUTH or addip_noauth required, then someone local must run sock_diag (ss is the example). That is a DoS gadget for a mis-set SCTP anycast box, not a worm. Do not write “remotely exploitable SCTP root” into a ticket. Do write “if we turned on ADD-IP, a peer can inflate transports until the next diagnostic dump crashes the box.”
net.sctp.addip_enable=1? If both answers are no, the remote clauses are academic and you still patch because of local LPE and DiagSpill-without-caps.Patch math for distro owners
Four different first-fixed versions per series. A host on 6.6.148 has PPPoEject closed if it took 6.6.148, DiagSpill closed at 6.6.151, DirtyAH6 at 6.6.156, TUNderflow at 6.6.157. “We applied last week’s stable” is not “all four.” The all-four floor is the list in Mitigation. EOL series (everything not .y in the tables) get no upstream backport — you live on the distro’s fork or you disable the kmod.
| Series | PPPoEject | DiagSpill | DirtyAH6 | TUNderflow (all-four) |
|---|---|---|---|---|
| 5.10.y | 5.10.265 | 5.10.265 | 5.10.269 | 5.10.270 |
| 5.15.y | 5.15.216 | 5.15.216 | 5.15.220 | 5.15.221 |
| 6.1.y | 6.1.183 | 6.1.183 | 6.1.187 | 6.1.188 |
| 6.6.y | 6.6.148 | 6.6.151 | 6.6.156 | 6.6.157 |
| 6.12.y | 6.12.101 | 6.12.103 | 6.12.108 | 6.12.109 |
| 6.18.y | 6.18.42 | 6.18.44 | 6.18.49 | 6.18.50 |
| 7.1.y | 7.1.6 | 7.1.8 | 7.1.13 | EOL; no TUNderflow stable |
| 7.2.y | 7.2 | 7.2 | 7.2.3 | 7.2.4 |
7.1.y is the trap in that table. DirtyAH6 is fixed in 7.1.13; TUNderflow’s 7.1 row is “EOL; no upstream stable fix.” If you are on 7.1, you either move to 7.2.4 or you carry a distro backport of 447c9303942c. Do not read “7.1.13 has DirtyAH6” as “7.1 is done.”
Distro kernels (Ubuntu HWE, RHEL, Android GKI, ChromeOS, custom Yocto) will not match these numbers. Map the four git IDs into your tree. If your vendor says “we shipped CVE-2026-68121,” ask which of the four IDs are present. One CVE blog post is not a quartet.
Hunting without the PoCs
You do not need to run a GitHub LPE to know whether a fleet is in the blast radius. The original already gave you the pre-reqs. Here is that list as a hunt.
uname -rversus the all-four floors above. If you are below the floor, you are not done.lsmodand boot config: AH6/XFRM, tun, pppoe, sctp, sctp_diag, openvswitch, vxlan, netkit, team, bonding, fuse, gre.- sysctl
kernel.unprivileged_userns_clone(and distro equivalents: Debian/Ubuntu userns sysctls, Fedora, container-runtime defaults). net.sctp.addip_enable/addip_noauth_enable/auth_enable— if any of those are on, DiagSpill’s remote DoS clause applies.- IPv6 routers with xfrm transport AH: DirtyAH6 remote crash clause.
- Containers with
CAP_NET_ADMIN(andCAP_NET_RAWfor DirtyAH6): treat as host-kernel risk even when the node disabled unprivileged userns for ordinary users. - Do not grep for the GitHub repo names as your only signal. The bugs are old; the PoCs are new. Patch level is the hunt.
CWE, ATT&CK, and what “exploitable” means here
| Bug | CWE | ATT&CK | What “exploitable” meant in the original |
|---|---|---|---|
| DirtyAH6 | CWE-787, CWE-20 | T1068; T1499; T1190 on AH-transport routers | Local LPE with userns; remote crash; lab remote-root with on-target groom |
| TUNderflow | CWE-191, CWE-787 | T1068; T1611 theoretical in CAP’d containers | Local LPE; needs headroom-propagating device path |
| PPPoEject | CWE-416 | T1068; T1611 theoretical | Local LPE; FUSE + GRE/team race in the published trigger |
| DiagSpill | CWE-190, CWE-787 | T1068; T1499 if ADD-IP | Local LPE with no caps; remote DoS only; no remote-root path seen |
T1611 is marked theoretical because the author said so: same shape as CIFSwitch/OVSwrap, community example exists for that class, he did not pursue a container-escape PoC here. Do not file “confirmed container escape” from this page. Do file “CAP_NET_ADMIN in a userns-owned netns can hit host-kernel memory bugs 1–3.”
T1190 for DirtyAH6 is the AH-transport IPv6 gateway clause, not “every Linux box is a public-facing AH server.” Most desktops never add AH in transport mode. Most core routers that still speak IPsec AH should be on the DirtyAH6 floor this week, not next quarter.
Detection ideas that do not need the exploit repos
Pre-patch, you will not get a reliable “DirtyAH6 fired” EDR signature from a public blog. Post-patch, the detection that matters is inventory. A few behavioral edges are still worth a SIEM rule if you already collect them:
- Unprivileged processes creating user namespaces and then opening
/dev/net/tun, PPPoE sockets, or raw IPv6 HDRINCL — noisy on developer workstations, interesting on locked-down gold images. - Sudden growth of SCTP peer addresses on an association (thousands of transports) followed by
ss/sock_diag— DiagSpill’s remote clause, and also a local prep step. - Kernel oops/panic with AH6, TUN, PPPoE, or sctp_diag on the stack during the unpatched window. That is a crash, not a proven LPE, but it is a reason to isolate the box.
- Writes to
/etc/pam.d/suor unexpectedsudoersdrops from a non-config-management user — those are the published LPE finishes, not the bugs. Hunt them as integrity events regardless.
None of those replace patching. They exist so a SOC that cannot reboot tonight has something to watch until the kernel package lands.
Containers, hall passes, and the CAP footnote
Unprivileged user namespaces are the common on-ramp because they let an ordinary user mint a namespace in which they hold CAP_NET_ADMIN (and CAP_NET_RAW) relative to that namespace’s network stack. The kernel objects those caps touch — TUN, PPPoE, AH6 — are still the host’s. That is the entire Linux userns LPE genre of the last decade. Ubuntu’s choice to block unprivileged userns is why AppArmor “helped” in those cases: it removed the hall pass, not because AppArmor understood memmove.
The footnote in the original is the one operators skip: if a container runtime already hands out CAP_NET_ADMIN (and RAW for DirtyAH6) in a userns-owned netns, the process does not need to create a new user namespace. Kubernetes pods with NET_ADMIN, Docker --cap-add=NET_ADMIN, and “network troubleshooting” sidecars are in that bucket. Host-kernel memory corruption from inside that pod is a container-escape primitive in theory. He did not write that PoC. The community already did analogous work on CIFSwitch.
DiagSpill ignores this entire paragraph. No caps. No userns. SCTP + sctp_diag. If your hardened image still has those two, you are not hardened against this quartet.
What the agentic hunter was actually doing
The original is brief because the method was already written up. A one-paragraph restatement for people who will not click the three older posts: CIFSwitch-style graph tracking keeps security-relevant objects (creds, skbs, netns, xfrm states, SCTP transports) as first-class nodes so an agent can ask “what can this cap touch.” OVSwrap-style geometry gives the agent a spatial model of kernel memory — slabs, skb heads, page tails — instead of a token soup of C. The drunk-LLM post is the harness: how to keep a model exploring without collapsing into syzkaller karaoke. This quartet is four successful trips through that loop, not four lucky prompts.
Why that matters for defenders: the next bug in AH6, TUN, PPPoE, or SCTP will not necessarily come from the same author, and the public experiment is pausing. The class is “old packet-munging code with a length derived from a wire field, a stored headroom, a pointer held across realloc, or a 16-bit counter next to a dump.” Those sentences are audit tickets you can file without an LLM.
- Any
memmove/memcpywhose length isa - bfrom a packet field, withoutb <= a. - Any stored headroom/alignment that can be set by a stacked device and is later subtracted from a page-sized budget without a clamp.
- Any protocol that caches
skb->dataor a header pointer acrossdev_hard_header()/pskb_expand_head(). - Any 8- or 16-bit “how many peers/transports/addresses” counter that a dump path later uses as a reservation size.
A slower walk through the four kitchens
The mailman and the wax seal
IPsec AH is a promise: these bytes did not change in flight. IPv6 makes that promise annoying because extension headers and routing headers are allowed to look different at each hop while still naming the same journey. Linux therefore canonicalizes some fields before it computes or checks the ICV. Canonicalize-then-authenticate is correct. Canonicalize with an unchecked segments_left is “walk off the packet.” The local attacker who can inject a raw IPv6 packet with a nonsense routing header is usually a userns tenant with RAW. The remote attacker is whoever can get an IPv6 packet to a box that will run AH6 on it in transport mode — a much smaller set, and a much more important one.
The box that was too small
TUN is how VPNs, Kubernetes CNIs, and a lot of lab tooling shove packets into the kernel. Headroom exists so the kernel can prepend headers without copying the whole packet. Stacked devices (VXLAN on netkit, OVS copying the max) honestly advertise “I need more headroom than a page.” TUN stored that number and later asked SKB_MAX_HEAD to subtract it from a page. Subtracting a larger number from a smaller one, then using the result as a size, is how you place skb->data in the next object. The OVS OOB in the published LPE is a consequence, not a second bug.
The recycled cardboard
PPPoE is still in trees because ISPs and a surprising number of nested-virt labs still speak it. pppoe_sendmsg is a send path, so it feels user-triggered and boring. The boring part is the bug: a pointer that should have been an offset. FUSE appears in the PoC because a slow userspace filesystem is a convenient way to hold the payload copy while another thread mutates the lower device. Team/bonding plus the first GRE port is a convenient way to make dev_hard_header actually realloc. Other lower devices that call pskb_expand_head during header creation are in the same CWE bucket even if they are not in the README.
The clicker that rolled over
SCTP associations with tens of thousands of peer addresses are not a desktop thing. They are a “we built a pathological association on purpose” thing, which is exactly what a local attacker does, and what a remote ADD-IP peer can do if you enabled the feature. The diagnostic path (ss, other sock_diag consumers) is the detonator: it trusts transport_count to size a buffer, then walks the real list. When the counter is 0 and the list is 65,536, you get an 8 MiB spill. Locally that is an LPE primitive. Remotely, without a local dumper, it is a stored bomb. His remote-root skepticism is about grooming page tables from the wrong side of a network without a local helper. Believe him until someone publishes otherwise.
What this is not
- Not a worm. Three of four want local caps or userns. DiagSpill’s remote clause is a default-off feature plus a local diagnostic.
- Not “LLMs replaced kernel auditors.” The harness found old mistakes; humans reported, patched, and wrote PoCs.
- Not a reason to disable SCTP on a host that actually needs SCTP — it is a reason to patch and leave ADD-IP off.
- Not a DirtyFrag duplicate. DirtyAH6 is a different OOB in AH6’s routing-header shuffle. The ESP-side DirtyFrag fix does not close it.
- Not permission to paste the GitHub trees into a ticket tracker. Link them. Run them in VMs you intend to delete.
Key Takeaways
- Four LPEs: DirtyAH6 (CVE-2026-80844), TUNderflow (CVE-2026-81000), PPPoEject (CVE-2026-68121), DiagSpill (CVE-2026-74469). 10–21 year-old bugs. Agent+graph hunting.
- First three: userns (or
CAP_NET_ADMINin a userns netns; plus RAW for DirtyAH6). DiagSpill: SCTP+sctp_diag, no extra caps. - DirtyAH6 remote crash on AH-transport IPv6 routers; lab remote-root with on-target groom. DiagSpill remote DoS if ADD-IP. No remote-root path seen for DiagSpill.
- Patch to 5.10.270 / 5.15.221 / 6.1.188 / 6.6.157 / 6.12.109 / 6.18.50 / 7.2.4 (all four). Per-bug floors are earlier; 7.1.y never got TUNderflow upstream.
- PoCs on GitHub, target-specific, VM-only. This article does not re-host them.
- AppArmor/SELinux were not a save in his tests except Ubuntu’s userns block (which does not help DiagSpill).
- Public hunting experiment paused. Thanks, maintainers.
Defensive Recommendations
- Upgrade to an all-four stable. Do not stop at the first of the four changelogs. Map the four git IDs into vendor kernels.
- If stuck: disable unprivileged userns (helps 1–3 only) and unused AH6/TUN/PPPoE/SCTP. Do not confuse “removed OVS” with “removed the bug class.”
- IPv6 edge: inventory transport-mode AH. Treat those boxes as remote-DoS until DirtyAH6 is in.
- SCTP: leave ADD-IP off unless a named product requires it. If it is on, you have accepted DiagSpill’s remote crash clause until patched.
- Containers with
NET_ADMIN: treat as host-kernel risk for bugs 1–3 even without new userns. DiagSpill ignores that discussion. - SELinux/AppArmor were not a save in his tests. Patch anyway. Integrity-monitor
/etc/pam.d/suand sudoers as a backstop, not a fix. - PoCs: throwaway VMs matching the README. Not staging. Not the laptop. Not a shared CI runner.
- File audit tickets on the four C patterns in “What the agentic hunter was actually doing,” even after this quartet is closed.
Conclusion
Four old keys, one geometric fuzzer, one embargo morning. Two of them can be shaken from the network if you left a rare lock on the street. The rest need a hall pass most desktops still print. DiagSpill does not even need the hall pass — only an intercom a surprising number of “locked down” images still ship. The experiment goes quiet. The stables do not. Take 6.6.157 or your series’ all-four floor, map the four commits into the vendor tree, leave ADD-IP off, and leave the GitHub tabs for a VM you intend to delete.
Original text: “A quartet of Linux local root vulns: DirtyAH6, PPPoEject, TUNderflow, and DiagSpill” by Asim Viladi Oglu Manizada at heyitsas.im.


