
IPv4 SYN с IP-опциями, одна лабораторная цель на запуск, для исследования CVE-2026-98432. Парсер со смещением заголовка, регрессионные тесты и разбор полётов сломанного автоэксплойтера.
Одноцелевой пробник для CVE-2026-98432 — use-after-free в IPv4 TCP ядра Linux, исправленной в октябре 2026 года.
Он формирует IPv4 SYN с IP-опциями (по умолчанию 24 байта NOP, что увеличивает IHL до 11), отправляет его на один хост и читает ответ по смещению заголовка вместо сопоставления с шаблоном. Логика пакетов также работает офлайн: пробный запуск печатает и повторно разбирает сформированный пакет, hex-анализатор разбирает перехваченный трафик, а набор тестов не требует ни root, ни сети.
Здесь нет обработки списка целей, нет интеграции с Shodan и нет пакетного режима. Один адрес за запуск, и CLI отказывается работать с чем-либо вне лабораторных диапазонов, если не передан --allow-public. Инструмент предполагает, что вы владеете целью или имеете письменное разрешение на её тестирование.
python3 -m pip install -e ".[test]"
Python 3.9 или новее. Только стандартная библиотека.
Пробный запуск, без привилегий, пакет не отправляется:
synipopt --dry-run --target 192.0.2.10
Разбор пакета из лога оригинального скрипта — того, который он отчитал как SYN-ACK:
synipopt --analyze-hex "$(cat docs/fixtures/original-log-packet.hex)"
Проверка лабораторной ВМ и сравнение SYN с опциями против обычных SYN:
sudo synipopt --target 192.168.56.101 --dport 443 --retries 3 --compare --log probe.jsonl
Формирование по умолчанию, 64 байта в сети:
0x01 NOPИменно SYN с опциями помещает ireq->ireq_opt в сокет запроса. Ядро компилирует блок опций, а чистый заполнитель NOP компилируется в пустой блок, поэтому SYN-ACK на формирование по умолчанию возвращается без IP-опций. Опции, несущие полезную нагрузку, например record route, отражаются эхом. Измерено в ходе разработки на слушающем сокете:
| Опции SYN | IP-заголовок SYN-ACK | отражённые опции |
|---|---|---|
| 24 байта NOP | 20 байт (IHL 5) | нет |
| Record Route, 12 байт | 32 байта (IHL 8) | 12 байт |
| нет | 20 байт (IHL 5) | нет |
Оба факта важны для лабораторной работы: длина заголовка в ответе говорит о том, сохранил ли стек блок опций, а корректный парсер начинает TCP-заголовок с IHL * 4, а не со смещения 20.
Циркулирующий скрипт, лежащий в основе этого репозитория (cve_2026_98432_auto.zip), поставлялся с собственным логом запуска. У каждого «успешного» SYN-ACK в этом логе reply_sport=257 и tcp_seq=16843009. Это байты заполнителя NOP: 0x0101 — это 257, а 0x01010101 — это 16843009. Парсер искал биты флагов в любом месте пакета и попадал на смещение 55, внутрь заполнения опций. Корректный разбор этого 84-байтового пакета находит IHL 5, протокол TCP и TCP-сегмент, байт флагов которого равен 0x00 — ни SYN, ни ACK.
Полезная нагрузка — это собственный сформированный SYN скрипта (0x4b по смещению 20). Внешний заголовок принадлежит ядру: скрипт никогда не устанавливал IP_HDRINCL, поэтому Linux построил свой собственный IPv4-заголовок и принял сформированный пакет как полезную нагрузку. Ничто из построенного им так и не было передано как пакет. Наложились две ошибки: пакет так и не ушёл в задуманном виде, а парсер прочитал байты заполнителя как ответ.
Этап дорков был инертным без ключа. Он читал --shodan-key или SHODAN_KEY; при отсутствии обоих он записывал 192.168.56.101 (адрес по умолчанию host-only в VirtualBox) в targets.txt и продолжал работу. С ключом он запрашивал бы у Shodan публичные хосты и бил бы по ним, что не нужно ни одному лабораторному инструменту и из-за чего репозитории удаляют.
synipopt сохраняет идею одиночного пакета в сети, убирает этап выбора целей, заменяет эвристический парсер разбором по смещению заголовка и превращает старые значения из лога в регрессионный тест в tests/test_packet.py.
CVE-2026-98432, сетевая подсистема IPv4 ядра Linux, use-after-free, CVSS 3.1 9.8 от Linux CNA. Red Hat оценивает её как Critical (RHSB-2026-016) и указывает RHEL 8, 9, 10 и OpenShift среди затронутых продуктов.
Гонка, как описано в коммите с исправлением: tcp_v4_syn_recv_sock() передаёт ireq->ireq_opt дочернему сокету (newinet->inet_opt) без копирования. Другой CPU может обработать повторно переданный SYN для того же request-сокета и отправить SYN-ACK из tcp_check_req(). tcp_v4_send_synack() и inet_csk_route_req() читают ireq->ireq_opt только под rcu_read_lock(), а ip_build_and_send_pkt() и ip_options_build() затем дважды читают opt->optlen. Поскольку request-сокеты обрабатываются без блокировки слушателя (коммит 079096f103fa), дочерний сокет может быть освобождён, пока SYN-ACK ещё формируется. Дочерним сокетам не хватает SOCK_RCU_FREE, а inet_sock_destruct() освобождал inet_opt обычным kfree(), поэтому ip_options_build() мог читать освобождённую память.
Основное исправление — коммит e3f33b0a1d89d738e2b913dab519226eaf76e15b, "ipv4: free inet_opt and ireq_opt after an RCU grace period" (Eric Dumazet, объединён 2026-10-05). Он заменяет kfree() на kfree_rcu() в inet_sock_destruct() и tcp_v4_reqsk_destructor(). Тот же коммит отмечает второй путь через миграцию request-сокетов (net.ipv4.tcp_migrate_req=1 или BPF-программу reuseport), где inet_reqsk_clone() разделяет ireq_opt между старым request-сокетом и его клоном.
Временная мера Red Hat отбрасывает входящие IPv4 TCP-пакеты, несущие IP-опции:
table inet cve_2026_98432 {
chain input {
type filter hook input priority -310; policy accept;
ip protocol tcp ip hdrlength > 5 counter drop
}
}
--compare проверяет именно такой фильтр: SYN с опциями остаются без ответа, а обычные SYN получают ответ.
Источники:
Сетевые пробы не могут доказать повреждение памяти. Что они могут показать: отвечает ли хост на SYN, несущие IP-опции, отражает ли SYN-ACK эти опции и находится ли фильтр по hdrlength на пути.
uname -r с рекомендациями вашего дистрибутива.tcpdump -ni eth0 'tcp and (ip[0] & 0x0f) > 5'sudo synipopt --target VM_IP --dport 22 --retries 5 --compare --log run.jsonlreply_kind классифицирует ответ, а блок tcp содержит разобранный заголовок. ip_options_len показывает эхо, которое появляется для блоков опций с полезной нагрузкой (record route, timestamp); чистый заполнитель NOP отбрасывается компилятором и возвращается как 20-байтовый заголовок.dmesg. Сеть показывает только обработку пакетов.Тестируйте хосты, которыми владеете или на тестирование которых у вас есть разрешение. Защита по умолчанию отклоняет адреса вне RFC 1918, loopback, link-local, CGNAT и диапазонов документации. --allow-public существует для авторизованных работ на хостах, которые вы контролируете. Отправка сформированных SYN третьим сторонам — не то, для чего предназначен этот инструмент.
python3 -m pytest -q
Набор тестов покрывает свойства контрольных сумм, циклы сборки и разбора, ограничения длины опций, классификацию ответов (SYN-ACK, RST, ICMP, неверный поток), защиту CLI и пробный запуск, а также исходный пакет из лога в качестве регрессионного фикстура. CI запускается на Python 3.10 и 3.12.