|
|
Message-ID: <UKEwxUv6L_SOQ3dxJhtoFDD4N5ji694-vMGviN_pwRaOi04cOpAk7ZoiQGsV0px_xpG0wgwnyNZoKg1yMeKTn1y9m2gTwS1_sWxLHnSsn2E=@pm.me>
Date: Fri, 18 Sep 2026 06:15:07 +0000
From: manizada <manizada@...me>
To: "oss-security@...ts.openwall.com" <oss-security@...ts.openwall.com>
Subject: A quartet of Linux local root vulns: DirtyAH6, PPPoEject, TUNderflow, and DiagSpill
Hi folks,
Emailing here now that the embargo agreed upon with linux-distros@ has
expired.
Flagging four local root vulnerabilities in the Linux kernel, originally
reported to security@...nel.org and the relevant maintainers in
mid-July:
DirtyAH6 (CVE-2026-80844), TUNderflow (CVE-2026-81000), PPPoEject
(CVE-2026-68121), and DiagSpill (CVE-2026-74469).
The underlying bugs have been around for 10-21 years. The first three
LPEs require either unprivileged user namespaces or specific CAPs
(see below); DiagSpill does not.
The corruption bugs in DirtyAH6 and DiagSpill are remotely reachable
under _very specific circumstances_, with DirtyAH6 theoretically
(unlikely/with difficulty) remote-groomable to remote root.
The fixes are now public and have been landing in the relevant stable
trees over the past several weeks:
DirtyAH6:
7bad4bda74dc4713f398d3b7624ff05478e3a568
("xfrm: ah6: validate routing header segments_left")
TUNderflow:
447c9303942c439a117d9b76ce6d6e2116b38ee7
("net: tun: bound receive headroom")
PPPoEject:
e9c238f6fe42fb1b4dba3a578277de32cb487937
("pppoe: reload header pointer after dev_hard_header()")
DiagSpill:
bd0e9289e2642f6a5c54faad304ce0f41e926d22
("sctp: prevent peer transport count overflow")
The first upstream stable releases containing 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.
LPE impact:
All four PoCs provide unprivileged local user -> root code execution
on their targets.
The pre-reqs listed below are for the vulns' _corruption bugs
specifically_; the full LPE-specific requirements, including
additional kmods and various userspace tooling, are captured in the
PoCs' READMEs.
DirtyAH6 pre-reqs:
- AH6/XFRM support
- unprivileged user/network namespaces, or CAP_NET_ADMIN and
CAP_NET_RAW over an attacker-controlled network namespace
TUNderflow pre-reqs:
- TUN support and some network-device path that can propagate
oversized receive headroom to it
- unprivileged user/network namespaces, or CAP_NET_ADMIN over an
attacker-controlled network namespace
PPPoEject pre-reqs:
- PPPoE support and a lower-device header callback that can
reallocate the skb head during dev_hard_header()
- unprivileged user/network namespaces, or CAP_NET_ADMIN over an
attacker-controlled network namespace
DiagSpill pre-reqs:
- SCTP and sctp_diag support (no unprivileged user namespace or CAP
requirements)
These pre-reqs are not exotic -- they cover standard modules/paths
commonly available on regular distro kernels.
AppArmor (except for the Ubuntu cases of blocking unprivileged user
namespaces themselves)/SELinux do not block the exploits in my testing.
All four bugs can also corrupt the host kernel from a container; the
first three would require the appropriate CAPs (without creating new
user namespaces), while DiagSpill would work without any special CAPs
so long as SCTP and sctp_diag are available. This could theoretically
enable a container escape, though I didn't pursue this angle with the
PoCs.
Remote impact:
DirtyAH6:
If the target is acting as an IPv6 router/gateway and adds AH in
transport mode, the bug can be turned into a remote crash/DoS.
With on-target memory grooming, I was able to turn it into remote
root in a lab environment.
Remote-only grooming to root is theoretically possible, but looks
extremely difficult.
DiagSpill:
If ASCONF/ADD-IP are enabled with either SCTP-AUTH or
net.sctp.addip_noauth_enable=1 (all disabled by default), a
malicious peer can add enough transports to wrap transport_count.
Something on the target (e.g., ss) must still issue the sock_diag
request that triggers the overwrite. This can be turned into a
remote crash/DoS; I do not see a path to full remote root, even
assuming perfect remote memory grooming.
Bug:
DirtyAH6:
ipv6_rearrange_rthdr() would get the number of addresses from
hdrlen, then use segments - segments_left to move an address
pointer without checking that segments_left <= segments. A raw
IPv6 HDRINCL packet with hdrlen=2 and segments_left=255 moved the
pointer back 4,064 bytes and passed a 4,064-byte length to
memmove(), causing an out-of-bounds access.
TUNderflow:
tun_set_headroom() stored receive headroom directly in tun->align,
while tun_get_user() also used it to choose how much packet data to
keep in the head. A netkit device with 4,096 bytes of configured
headroom, under VXLAN and Open vSwitch, could pass 4,160 bytes to
a raw TUN port. SKB_MAX_HEAD(4160) would underflow; prepad + linear
and len - linear then wrapped, leaving skb->data 64 bytes beyond
its 4,096-byte allocation.
PPPoEject:
pppoe_sendmsg() kept a pointer into the skb head across
dev_hard_header(), even though a device callback could call
pskb_expand_head() and free that head. Blocking the payload copy on
FUSE while adding the first GRE/IP6GRE port to an empty team or
bonding device triggered the reallocation, effectively ejecting the
old skb head while PPPoE still held a pointer into it. The
subsequent header and length writes used that stale pointer.
DiagSpill:
An SCTP association can have 65,536 peer transports, but
transport_count is 16 bits and the 65,536th transport wrapped it
to zero. sctp_diag then reserved no peer payload but copied the full
list, spilling about 8 MiB past the end of the Netlink response.
Affected upstream kernels:
The versions below are, again, for the underlying bugs, not the
full-root PoCs, which have some additional dependencies:
DirtyAH6:
2.6.12–5.9: all releases
5.10.0–5.10.268; 5.11–5.14: all releases
5.15.0–5.15.219; 5.16–6.0: all releases
6.1.0–6.1.186; 6.2–6.5: all releases
6.6.0–6.6.155; 6.7–6.11: all releases
6.12.0–6.12.107; 6.13–6.17: all releases
6.18.0–6.18.48; 6.19–7.0: all releases
7.1.0–7.1.12; 7.2.0–7.2.2
TUNderflow:
4.6–5.9: all releases
5.10.0–5.10.269; 5.11–5.14: all releases
5.15.0–5.15.220; 5.16–6.0: all releases
6.1.0–6.1.187; 6.2–6.5: all releases
6.6.0–6.6.156; 6.7–6.11: all releases
6.12.0–6.12.108; 6.13–6.17: all releases
6.18.0–6.18.49; 6.19–7.0: all releases
7.1.0–7.1.13; 7.2.0–7.2.3
PPPoEject:
2.6.12–5.9: all releases
5.10.0–5.10.264; 5.11–5.14: all releases
5.15.0–5.15.215; 5.16–6.0: all releases
6.1.0–6.1.182; 6.2–6.5: all releases
6.6.0–6.6.147; 6.7–6.11: all releases
6.12.0–6.12.100; 6.13–6.17: all releases
6.18.0–6.18.41; 6.19–7.0: all releases
7.1.0–7.1.5
DiagSpill:
4.7–5.9: all releases
5.10.0–5.10.264; 5.11–5.14: all releases
5.15.0–5.15.215; 5.16–6.0: all releases
6.1.0–6.1.182; 6.2–6.5: all releases
6.6.0–6.6.150; 6.7–6.11: all releases
6.12.0–6.12.102; 6.13–6.17: all releases
6.18.0–6.18.43; 6.19–7.0: all releases
7.1.0–7.1.7
Immediate-term mitigations (aside from using updated kernels):
- Disabling unprivileged user namespaces removes the standard
ordinary-user path to the first three vulns, though it doesn't
protect against appropriately-CAP'd containers/other processes;
DiagSpill would still remain reachable too.
- Disable AH6, TUN, PPPoE, and/or SCTP/sctp_diag if unused.
Full writeup:
https://heyitsas.im/posts/lpe-quartet/
PoCs for validation:
https://github.com/manizada/DirtyAH6
https://github.com/manizada/TUNderflow
https://github.com/manizada/PPPoEject
https://github.com/manizada/DiagSpill
Note that while the LPEs reproduce across a range of distros/kernels,
the PoCs are targeted at just a handful, as each target often requires
custom derivation/grooming.
Thanks,
-Asim Manizada
Powered by blists - more mailing lists
Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.
Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.