
Preuve de concept sans privilèges pour CVE-2026-74586, une use-after-free SCTP ASCONF dans le noyau Linux. Fournit un déclencheur par paquets bruts, des métriques de fiabilité et une analyse détaillée de l'armement, y compris les décalages de structures et les primitives de spray.
Un PoC autonome et non privilégié pour CVE-2026-74586, un use-after-free
dans la gestion ASCONF (reconfiguration dynamique d'adresses) SCTP du
noyau Linux. Le crédit du bug lui-même revient à Qing Ming, qui l'a
signalé en amont ; le correctif est le commit beb33f8ee1ca (« sctp: clear
new_transport when removing a peer »), fusionné le 2026-08-12 avec
Cc: stable — Red Hat le juge important, les trackers tiers le placent
à CVSS 9.8.
Ce que ce dépôt ajoute par-dessus l'avis de sécurité :
sctp_process_asconf_param() stocke le transport créé par un paramètre
ADD-IP dans asoc->new_transport. Un DEL-IP pour la même adresse dans le
même chunk ASCONF libère ce transport via sctp_assoc_rm_peer(), qui
(avant le correctif) n'efface pas . Quand
retourne, agit sur le
pointeur obsolète — sur un noyau de stock, l'effet visible est un
HEARTBEAT émis vers l'adresse du transport tout juste libéré ; sous KASAN,
c'est un rapport slab-use-after-free.
new_transportsctp_process_asconf()sctp_sf_do_asconf()Un seul chunk ASCONF suffit :
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
Pas besoin de root. Le PoC désolidarise un espace de noms utilisateur + réseau, y configure les sysctls SCTP, monte une association multi-homée sur loopback, puis injecte l'ASCONF forgé avec une socket raw.
Sortie attendue sur un noyau vulnérable :
[+] 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)
Le HEARTBEAT après l'ACK est la partie intéressante : ce paquet n'existe
que parce que le noyau a parcouru le new_transport pendant. Le client
ABORT juste après parce que le heartbeat est revenu out-of-the-blue.
Rien de tout cela n'est une recherche inédite, c'est juste assez peu documenté pour coûter un après-midi, donc le voici pour la personne suivante :
sctp_rcv() vérifie la somme de contrôle
et abandonne en cas de discordance. Calculez le CRC32C (poly
0x82F63B78, réfléchi) sur tout le paquet SCTP avec le champ de somme
de contrôle mis à zéro.sctp_auth_recv_cid() s'exécute
dans le chemin d'entrée avant la machine à états et abandonne
l'ASCONF non authentifié même quand addip_noauth_enable=1 — ce
sysctl ne relâche que la vérification à l'intérieur de
sctp_sf_do_asconf(). La solution est de ne jamais négocier AUTH au
niveau de la socket en premier lieu (pas de setsockopt
SCTP_AUTH_SUPPORTED), pour que peer.auth_capable reste faux et que
les deux vérifications passent.Le tag Fixes: sur le commit amont est 6af29ccc223b (« sctp: Bundle
HEARTBEAT into ASCONF_ACK »), donc le code est ancien. Corrigé dans
mainline et rétroporté aux séries stables (tracker Debian : corrigé à
partir de 7.1.9-1 dans sid, 6.12.107 dans trixie-security). Kali rolling
au 2026-09-09 fournit 7.1.5-1kali1 dans chaque suite, ce qui précède tout
cela — c'est la principale pertinence pratique de ce PoC.
Pour quiconque irait plus loin — mesuré contre 7.1.5 dans QEMU, offsets
issus de objdump -d du sctp.ko fourni :
| champ | offset |
|---|---|
| flowi (88 octets) | 0x30 |
| ipaddr | 0x88 |
| af_specific | 0xa8 |
| asoc | 0xb0 |
| dst | 0xe0 |
| state | 0x15c |
| rcu | 0x2b0 |
sctp_association->new_transport se trouve à 0x6b0. Le tableau provient
de pahole -C sctp_transport /sys/kernel/btf/vmlinux sur le noyau en
cours d'exécution — une révision antérieure de ce tableau portait un
offset ipaddr erroné (0x68, dû à un mauvais comptage de flowi) ; BTF est
la vérité de référence.
sctp_transport_destroy_rcu), il n'y a pas de double-free sur ce
chemin. Si vous espériez fermer une socket et obtenir deux frees —
sctp_association_free() ne parcourt que la liste des transports, et
rm_peer() a déjà délié le ghost. Ne perdez pas une soirée là-dessus.sctp_outq_select_transport() lit state à +0x15c depuis
chunk->transport à +0xe8 du chunk. Un objet récupéré arrosé avec
state=1 (ACTIVE) modifie le comportement du noyau après la
libération — l'objet est consulté.sctp_outq_select_transport() lisant
state=0xFFFF (déchet de slab) depuis un pointeur de transport obsolète
juste au moment de l'injection. Conséquence : le spray doit pré-préparer
le cache avant le déclencheur, pas après — l'objet est lu avant même
que la période de grâce n'expire sur ce chemin.*(p+0x288) à p+0x288. C'est un auto-pointeur. Vous ne
pouvez pas passer outre par spray sans connaître l'adresse de slab,
c'est-à-dire que ce bug nécessite une fuite d'informations avant que le
contrôle de af_specific ne se transforme en contrôle du pointeur
d'instruction. af_specific lui-même est un site d'appel indirect
(*(af_specific+0x18) avec rdi = transport), donc c'est le prix une
fois qu'une fuite existe.setsockopt(SCTP_AUTH_KEY) avec
une clé de 800 octets atterrit dans le même cache kmalloc-1k. Rappelez-
vous que l'en-tête est struct sctp_authkey { assoc_id; keynumber; keylength; key[] } et que AUTH doit d'abord être activé par socket via
SCTP_AUTH_SUPPORTED avec un sctp_assoc_value — un simple int renvoie
EINVAL.C'est un PoC de déclenchement/crash. Ce n'est pas une élévation de privilèges et ce dépôt n'en revendique pas une. Exécutez-le contre vos propres noyaux dans une VM.
Testé sur : 7.1.5+kali-amd64 (Kali rolling) et 7.0.12+kali-amd64.
GPL-2.0 pour les portions dérivées du noyau de la logique ; faites ce que vous voulez du reste. Réservé aux tests de sécurité autorisés et à la recherche uniquement.