
Unprivileged proof-of-concept for CVE-2026-74586, a Linux kernel SCTP ASCONF use-after-free. Provides a raw-packet trigger, reliability metrics, and detailed weaponization analysis including struct offsets and spray primitives.
A self-contained, unprivileged PoC for CVE-2026-74586, a use-after-free in
the Linux kernel's SCTP ASCONF (dynamic address reconfiguration) handling.
Credit for the bug itself goes to Qing Ming, who reported it upstream; the
fix is commit beb33f8ee1ca ("sctp: clear new_transport when removing a
peer"), merged 2026-08-12 with Cc: stable — Red Hat rates it important,
third-party trackers put it at CVSS 9.8.
What this repo adds on top of the advisory:
sctp_process_asconf_param() stores the transport created by an ADD-IP
parameter in asoc->new_transport. A DEL-IP for the same address in the
same ASCONF chunk frees that transport through sctp_assoc_rm_peer(),
which (before the fix) does not clear . When
returns, acts on the stale
pointer — on a stock kernel the visible effect is a HEARTBEAT emitted
toward the address of the just-freed transport; under KASAN it's a
slab-use-after-free report.
new_transportsctp_process_asconf()sctp_sf_do_asconf()One ASCONF chunk is enough:
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
No root. The PoC unshares a user + network namespace, sets the SCTP sysctls in it, brings up a multi-homed association over loopback, then injects the crafted ASCONF with a raw socket.
Expected output on a vulnerable kernel:
[+] Association established (no AUTH)
[+] server_vtag=0x... captured_tsn=0x...
[*] serial=0x... (= initial_tsn)
[+] Injected 60 bytes
[9222->9111 vt=...] ASCONF-ACK(0x80) len=8
[+] ASCONF-ACK -> server processed ADD-IP + DEL-IP
[9222->9111 vt=...] HB(0x04) len=60 <- kernel using freed transport
[9111->9222 vt=...] ABORT(0x06) len=8
[+] GhostTransport UAF TRIGGERED (ASCONF-ACK received)
[+] HEARTBEAT to ghost address observed (dangling transport USED)
The HEARTBEAT after the ACK is the interesting part: that packet only
exists because the kernel walked the dangling new_transport. The client
ABORTs right after because the heartbeat came back out-of-the-blue.
None of this is novel research, it's just undocumented enough to cost an afternoon, so here it is for the next person:
sctp_rcv() verifies the checksum and drops on
mismatch. Compute CRC32C (poly 0x82F63B78, reflected) over the whole
SCTP packet with the checksum field zeroed.sctp_auth_recv_cid() runs in the input
path before the state machine and drops unauthenticated ASCONF even
when addip_noauth_enable=1 — that sysctl only relaxes the check
inside sctp_sf_do_asconf(). The way out is to never negotiate AUTH
at the socket level in the first place (no SCTP_AUTH_SUPPORTED
setsockopt), so peer.auth_capable stays false and both checks pass.The Fixes: tag on the upstream commit is 6af29ccc223b ("sctp: Bundle
HEARTBEAT into ASCONF_ACK"), so the code is old. Fixed in mainline and
backported to the stable series (Debian tracker: fixed from 7.1.9-1 in
sid, 6.12.107 in trixie-security). Kali rolling as of 2026-09-09 ships
7.1.5-1kali1 in every suite, which predates all of it — that's the main
practical relevance of this PoC.
For anyone taking it further — measured against 7.1.5 in QEMU, offsets
from objdump -d of the shipped sctp.ko:
| field | offset |
|---|---|
| flowi (88 bytes) | 0x30 |
| ipaddr | 0x88 |
| af_specific | 0xa8 |
| asoc | 0xb0 |
| dst | 0xe0 |
| state | 0x15c |
| rcu | 0x2b0 |
sctp_association->new_transport sits at 0x6b0. The table comes from
pahole -C sctp_transport /sys/kernel/btf/vmlinux on the running kernel —
an earlier revision of this table carried a wrong ipaddr offset (0x68,
from miscounting flowi); BTF is the ground truth.
sctp_transport_destroy_rcu), there
is no double-free on this path. If you were hoping to close a socket
and get two frees — sctp_association_free() only walks the transport
list, and rm_peer() already unlinked the ghost. Don't spend an
evening on that.sctp_outq_select_transport() reads state at +0x15c off
chunk->transport at +0xe8 of the chunk. A reclaimed object sprayed
with state=1 (ACTIVE) changes kernel behavior after the free — the
object is being consulted.sctp_outq_select_transport() reading state=0xFFFF (slab garbage)
off a stale transport pointer right at inject time. Consequence: the
spray needs to pre-groom the cache before the trigger, not after —
the object is read before the grace period ever expires on this path.*(p+0x288) against p+0x288. That is a self-pointer. You
cannot spray your way past it without knowing the slab address, i.e.
this bug needs an info leak before af_specific control turns into
instruction-pointer control. af_specific itself is an indirect call
site (*(af_specific+0x18) with rdi = transport), so that's the prize
once a leak exists.setsockopt(SCTP_AUTH_KEY) with an 800-byte
key lands in the same kmalloc-1k cache. Remember the header is
struct sctp_authkey { assoc_id; keynumber; keylength; key[] } and
AUTH needs enabling per-socket first via SCTP_AUTH_SUPPORTED with an
sctp_assoc_value — a plain int gets EINVAL.This is a trigger/crash PoC. It is not a privilege escalation and this repo does not claim one. Run it against your own kernels in a VM.
Tested on: 7.1.5+kali-amd64 (Kali rolling) and 7.0.12+kali-amd64.
GPL-2.0 for the kernel-derived portions of the logic; do whatever you want with the rest. For authorized security testing and research only.