
Непривилегированный proof-of-concept для CVE-2026-74586, use-after-free в SCTP ASCONF ядра Linux. Предоставляет триггер на основе raw-пакетов, метрики надёжности и подробный анализ вектора эксплуатации, включая смещения структур и примитивы для spray.
Автономный PoC без привилегий для CVE-2026-74586, use-after-free в
обработке SCTP ASCONF (динамическая перенастройка адресов) в ядре Linux.
Авторство самой ошибки принадлежит Qing Ming, который сообщил о ней
вышестоящим разработчикам; исправление — коммит beb33f8ee1ca ("sctp: clear new_transport when removing a
peer"), объединённый 2026-08-12 с пометкой Cc: stable — Red Hat оценивает
её как важную, сторонние трекеры присваивают ей CVSS 9.8.
Что этот репозиторий добавляет поверх уведомления:
sctp_process_asconf_param() сохраняет транспорт, созданный параметром ADD-IP,
в asoc->new_transport. DEL-IP для того же адреса в том же
ASCONF-чанке освобождает этот транспорт через sctp_assoc_rm_peer(),
который (до исправления) не очищает . Когда
возвращает управление, действует на основе устаревшего
указателя — на стандартном ядре видимый эффект — HEARTBEAT, отправленный
на адрес только что освобождённого транспорта; под KASAN это
отчёт о slab-use-after-free.
new_transportsctp_process_asconf()sctp_sf_do_asconf()Достаточно одного ASCONF-чанка:
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
Root не требуется. PoC разворачивает пространство имён пользователя и сети, устанавливает в нём sysctl для SCTP, поднимает многоадресную ассоциацию через loopback, затем внедряет сформированный ASCONF через сырой сокет.
Ожидаемый вывод на уязвимом ядре:
[+] 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)
HEARTBEAT после ACK — самая интересная часть: этот пакет существует
только потому, что ядро прошло по висячему new_transport. Клиент
сразу после этого отправляет ABORT, поскольку heartbeat пришёл неожиданно (out-of-the-blue).
Ничего из этого не является новым исследованием, это просто недостаточно задокументировано, чтобы стоить полдня работы, так что вот это для следующего человека:
sctp_rcv() проверяет контрольную сумму и отбрасывает пакет при
несовпадении. Вычислите CRC32C (полином 0x82F63B78, отражённый) по всему
SCTP-пакету с обнулённым полем контрольной суммы.sctp_auth_recv_cid() выполняется на входном
пути до машины состояний и отбрасывает неаутентифицированный ASCONF, даже
когда addip_noauth_enable=1 — этот sysctl лишь ослабляет проверку
внутри sctp_sf_do_asconf(). Выход — вообще не согласовывать AUTH
на уровне сокета (без setsockopt SCTP_AUTH_SUPPORTED), чтобы peer.auth_capable оставался false, и обе проверки проходили.Тег Fixes: в вышестоящем коммите — 6af29ccc223b ("sctp: Bundle
HEARTBEAT into ASCONF_ACK"), так что код старый. Исправлено в mainline и
перенесено в стабильные серии (трекер Debian: исправлено с 7.1.9-1 в
sid, 6.12.107 в trixie-security). Kali rolling по состоянию на 2026-09-09 поставляет
7.1.5-1kali1 во всех наборах, что предшествует всему этому — в этом и состоит
основная практическая значимость данного PoC.
Для тех, кто пойдёт дальше — измерено против 7.1.5 в QEMU, смещения
из objdump -d поставляемого sctp.ko:
| поле | смещение |
|---|---|
| flowi (88 байт) | 0x30 |
| ipaddr | 0x88 |
| af_specific | 0xa8 |
| asoc | 0xb0 |
| dst | 0xe0 |
| state | 0x15c |
| rcu | 0x2b0 |
sctp_association->new_transport находится по смещению 0x6b0. Таблица взята из
pahole -C sctp_transport /sys/kernel/btf/vmlinux на работающем ядре —
более ранняя редакция этой таблицы содержала неверное смещение ipaddr (0x68,
из-за неверного подсчёта flowi); BTF — источник истины.
sctp_transport_destroy_rcu), двойного
освобождения на этом пути нет. Если вы надеялись закрыть сокет
и получить два освобождения — sctp_association_free() обходит только список
транспортов, а rm_peer() уже отцепил призрака. Не тратьте вечер
на это.sctp_outq_select_transport() читает state по смещению +0x15c от
chunk->transport по +0xe8 от чанка. Переиспользованный объект, залитый
значением state=1 (ACTIVE), меняет поведение ядра после освобождения —
объект запрашивается.sctp_outq_select_transport(), читающий state=0xFFFF (мусор из slab)
по устаревшему указателю транспорта прямо в момент инъекции. Следствие:
заливке необходимо предварительно подготовить кэш до триггера, а не после —
объект читается до истечения периода ожидания (grace period) на этом пути.*(p+0x288) с p+0x288. Это самоссылающийся указатель. Вы не можете
обойти его заливкой, не зная адреса slab, т.е.
этой ошибке нужна утечка информации до того, как контроль над af_specific превратится
в контроль над указателем инструкций. Сам af_specific — это место косвенного вызова
(*(af_specific+0x18) с rdi = transport), так что это приз
после появления утечки.setsockopt(SCTP_AUTH_KEY) с ключом 800 байт
попадает в тот же кэш kmalloc-1k. Помните, что заголовок —
struct sctp_authkey { assoc_id; keynumber; keylength; key[] }, и AUTH
сначала нужно включить для сокета через SCTP_AUTH_SUPPORTED со
структурой sctp_assoc_value — обычный int даст EINVAL.Это PoC для триггера/краха. Это не повышение привилегий, и этот репозиторий не заявляет о таковом. Запускайте его против собственных ядер в виртуальной машине.
Протестировано на: 7.1.5+kali-amd64 (Kali rolling) и 7.0.12+kali-amd64.
GPL-2.0 для частей логики, производных от ядра; с остальным делайте что хотите. Только для авторизованного тестирования безопасности и исследований.