
Непривилегированный 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, который сообщил о ней
в upstream; исправление — коммит beb33f8ee1ca ("sctp: clear new_transport when removing a
peer"), влитый 2026-08-12 с Cc: stable — Red Hat оценивает его как important,
сторонние трекеры присваивают ему CVSS 9.8.
Что этот репозиторий добавляет к рекомендациям:
sctp_process_asconf_param() сохраняет транспорт, созданный параметром ADD-IP,
в asoc->new_transport. DEL-IP для того же адреса в том же ASCONF-чанке
освобождает этот транспорт через sctp_assoc_rm_peer(),
который (до исправления) не очищает new_transport. Когда
sctp_process_asconf() возвращается, sctp_sf_do_asconf() работает с устаревшим
указателем — на штатном ядре видимый эффект — HEARTBEAT, отправленный
по адресу только что освобождённого транспорта; под KASAN это
отчёт slab-use-after-free.
Достаточно одного ASCONF-чанка:
[Address Parameter L] [ADD-IP G] [DEL-IP G]
gcc -O2 -Wall -o poc poc.c
./poc
Без root. PoC выполняет unshare для пользовательского и сетевого пространства имён, устанавливает в нём sysctl-параметры SCTP, поднимает многоадресную ассоциацию через loopback, а затем внедряет сформированный ASCONF с помощью raw-сокета.
Ожидаемый вывод на уязвимом ядре:
[+] 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 пришёл совершенно неожиданно.
Ничто из этого не является новым исследованием, просто это недостаточно документировано, чтобы потратить полдня, так что вот для следующего человека:
sctp_rcv() проверяет контрольную сумму и отбрасывает пакет при
несовпадении. Вычислите CRC32C (полином 0x82F63B78, отражённый) по всему
пакету SCTP с обнулённым полем контрольной суммы.sctp_auth_recv_cid() выполняется на входном
пути до конечного автомата и отбрасывает неаутентифицированный ASCONF даже
при addip_noauth_enable=1 — этот sysctl лишь ослабляет проверку
внутри sctp_sf_do_asconf(). Выход — вообще не согласовывать AUTH
на уровне сокета (без SCTP_AUTH_SUPPORTED
setsockopt), тогда peer.auth_capable остаётся false и обе проверки проходят.Тег Fixes: в upstream-коммите — 6af29ccc223b ("sctp: Bundle
HEARTBEAT into ASCONF_ACK"), так что код старый. Исправлено в mainline и
бэкпортировано в стабильную серию (Debian tracker: исправлено начиная с 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 для производных от ядра частей логики; с остальным делайте что хотите. Только для авторизованного тестирования безопасности и исследований.
acklock.c — soft-lockup на ASCONF-ACK (не исправлен во ВСЕХ 7.1.x)