
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 SCTP ASCONF (reconfiguration dynamique d'adresses) du noyau
Linux. Le crédit pour le 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 classe important,
des trackers tiers le situent à CVSS 9.8.
Ce que ce dépôt ajoute par rapport à l'avis :
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) ne vide pas new_transport. Lorsque
sctp_process_asconf() retourne, sctp_sf_do_asconf() agit sur le
pointeur obsolète — sur un noyau standard, l'effet visible est un HEARTBEAT émis
vers l'adresse du transport tout juste libéré ; sous KASAN, c'est un
rapport de slab-use-after-free.
Un seul chunk ASCONF suffit :
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
Pas de root. Le PoC désassocie un namespace utilisateur + réseau, y définit les sysctls SCTP, établit une association multi-homed via loopback, puis injecte l'ASCONF forgé à l'aide d'un socket brut.
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
envoie un ABORT juste après parce que le heartbeat est revenu de manière inattendue.
Rien de tout cela n'est une recherche novatrice, c'est juste suffisamment non documenté pour coûter un après-midi, alors voici pour la personne suivante :
sctp_rcv() vérifie la somme de contrôle et abandonne en cas de
non-concordance. Calculez CRC32C (poly 0x82F63B78, réfléchi) sur l'ensemble du
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 les ASCONF non authentifiés même
lorsque addip_noauth_enable=1 — ce sysctl ne relâche la vérification qu'à
l'intérieur de sctp_sf_do_asconf(). La solution consiste à ne jamais négocier AUTH
au niveau du socket dès le départ (pas de setsockopt SCTP_AUTH_SUPPORTED), ainsi
peer.auth_capable reste faux et 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 le mainline et
rétroporté vers la série stable (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 toutes les suites, ce qui précède tout cela — c'est la principale
pertinence pratique de ce PoC.
Pour quiconque va plus loin — mesuré par rapport à 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 comportait un offset ipaddr erroné (0x68,
dû à un mauvais comptage de flowi) ; BTF est la source de vérité.
sctp_transport_destroy_rcu), il n'y a
pas de double-free sur ce chemin. Si vous espériez fermer un socket
et obtenir deux frees — sctp_association_free() ne parcourt que la liste
des transports, et rm_peer() a déjà délié le fantôme. Ne passez pas
une soirée là-dessus.sctp_outq_select_transport() lit state à +0x15c depuis
chunk->transport à +0xe8 du chunk. Un objet récupéré et pulvérisé
avec state=1 (ACTIVE) modifie le comportement du noyau après le free — l'
objet est consulté.sctp_outq_select_transport() lisant state=0xFFFF (déchets de slab)
depuis un pointeur de transport obsolète pile au moment de l'injection. Conséquence : le
spray doit pré-nettoyer 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 pulvérisation sans connaître l'adresse du slab, c'est-à-dire
que ce bug nécessite une fuite d'information avant que le contrôle af_specific ne devienne un
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 butin
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
qu'AUTH doit d'abord être activé par socket via SCTP_AUTH_SUPPORTED avec une
sctp_assoc_value — un simple int renvoie EINVAL.Ceci est un PoC de déclenchement/crash. Ce n'est pas une escalade de privilèges et ce dépôt n'en revendique aucune. 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 de la logique dérivées du noyau ; faites ce que vous voulez du reste. Pour les tests et la recherche en sécurité autorisés uniquement.