
IPv4 SYN com opções de IP, um alvo de laboratório por execução, para pesquisa da CVE-2026-98432. Parser de deslocamento de cabeçalho, testes de regressão e uma postmortem de um auto-exploiter quebrado.
Uma sonda de alvo único para CVE-2026-98432, o use-after-free do TCP IPv4 do kernel Linux corrigido em outubro de 2026.
Ela constrói um SYN IPv4 que carrega opções IP (24 bytes NOP por padrão, o que eleva o IHL para 11), envia-o para um host e lê a resposta pelo deslocamento do cabeçalho em vez de correspondência de padrões. A lógica de pacotes também funciona offline: uma execução a seco imprime e reanalisa o pacote construído, o analisador hexadecimal analisa o tráfego capturado, e a suíte de testes não precisa de root nem de rede.
Não há tratamento de lista de alvos, nem integração com o Shodan, nem modo em lote. Um endereço por execução, e a CLI recusa qualquer coisa fora de faixas de laboratório, a menos que você passe --allow-public. A ferramenta pressupõe que você é dono do alvo ou tem permissão por escrito para testá-lo.
python3 -m pip install -e ".[test]"
Python 3.9 ou mais recente. Apenas biblioteca padrão.
Execução a seco, sem privilégios, sem pacote enviado:
synipopt --dry-run --target 192.0.2.10
Analise o pacote a partir do log do script original, aquele que ele reportou como SYN-ACK:
synipopt --analyze-hex "$(cat docs/fixtures/original-log-packet.hex)"
Sonde uma VM de laboratório e compare SYNs que carregam opções com SYNs simples:
sudo synipopt --target 192.168.56.101 --dport 443 --retries 3 --compare --log probe.jsonl
Construção padrão, 64 bytes no fio:
0x01Um SYN que carrega opções é o que coloca ireq->ireq_opt no socket de requisição. O kernel compila o bloco de opções, e o preenchimento puro de NOP compila para um bloco vazio, então um SYN-ACK para a construção padrão volta sem opções IP. Opções que carregam carga útil, como record route, são ecoadas. Medido durante o desenvolvimento contra um socket em escuta:
| Opções do SYN | Cabeçalho IP do SYN-ACK | opções ecoadas |
|---|---|---|
| 24 bytes NOP | 20 bytes (IHL 5) | nenhuma |
| Record Route, 12 bytes | 32 bytes (IHL 8) | 12 bytes |
| nenhuma | 20 bytes (IHL 5) | nenhuma |
Ambos os fatos importam para o trabalho de laboratório: o comprimento do cabeçalho da resposta indica se a pilha manteve o bloco de opções, e um analisador correto inicia o cabeçalho TCP em IHL * 4, não no deslocamento 20.
O script em circulação por trás deste repositório (cve_2026_98432_auto.zip) trazia seu próprio log de execução. Todo SYN-ACK "bem-sucedido" nesse log tem reply_sport=257 e tcp_seq=16843009. Esses são os bytes de preenchimento NOP: 0x0101 é 257 e 0x01010101 é 16843009. O analisador procurava bits de flag em qualquer lugar do pacote e parava no deslocamento 55, dentro do preenchimento de opções. Uma análise correta desse pacote de 84 bytes encontra IHL 5, protocolo TCP, e um segmento TCP cujo byte de flags é 0x00, sem SYN e sem ACK.
A carga útil é o próprio SYN construído pelo script (0x4b no deslocamento 20). O cabeçalho externo pertence ao kernel: o script nunca definiu IP_HDRINCL, então o Linux construiu seu próprio cabeçalho IPv4 e tomou o pacote construído como carga útil. Nada do que ele construiu foi jamais transmitido como pacote. Duas falhas se acumularam: o pacote nunca saiu como projetado, e o analisador leu bytes de preenchimento como a resposta.
O estágio de dork era inerte sem uma chave. Ele lia --shodan-key ou SHODAN_KEY; sem nenhuma das duas, gravava 192.168.56.101 (um padrão host-only do VirtualBox) em targets.txt e seguia em frente. Com uma chave, ele consultaria o Shodan por hosts públicos e dispararia contra eles, o que nenhuma ferramenta de laboratório precisa e que faz repositórios serem removidos.
O synipopt mantém a ideia de pacote único no fio, remove o estágio de direcionamento, substitui o analisador heurístico por análise por deslocamento de cabeçalho, e transforma os antigos valores de log em um teste de regressão em tests/test_packet.py.
CVE-2026-98432, rede IPv4 do kernel Linux, use-after-free, CVSS 3.1 9.8 pelo CNA do Linux. A Red Hat classifica como Crítica (RHSB-2026-016) e lista RHEL 8, 9, 10 e OpenShift entre os produtos afetados.
A corrida, conforme descrita no commit de correção: tcp_v4_syn_recv_sock() transfere ireq->ireq_opt para o socket filho (newinet->inet_opt) sem copiar. Outra CPU pode processar um SYN retransmitido para o mesmo request socket e enviar um SYN-ACK a partir de tcp_check_req(). tcp_v4_send_synack() e inet_csk_route_req() leem ireq->ireq_opt apenas sob rcu_read_lock(), e ip_build_and_send_pkt() e ip_options_build() então leem opt->optlen duas vezes. Como os request sockets são processados sem o lock do listener (commit 079096f103fa), o filho pode ser liberado enquanto o SYN-ACK ainda está sendo construído. Sockets filhos não possuem SOCK_RCU_FREE, e inet_sock_destruct() liberava inet_opt com um kfree() simples, de modo que ip_options_build() podia ler memória já liberada.
A correção no mainline é o commit e3f33b0a1d89d738e2b913dab519226eaf76e15b, "ipv4: free inet_opt and ireq_opt after an RCU grace period" (Eric Dumazet, mesclado em 2026-10-05). Ele troca kfree() por kfree_rcu() em inet_sock_destruct() e tcp_v4_reqsk_destructor(). O mesmo commit observa um segundo caminho através da migração de request sockets (net.ipv4.tcp_migrate_req=1 ou um programa BPF reuseport), em que inet_reqsk_clone() compartilha ireq_opt entre o request socket antigo e seu clone.
A mitigação temporária da Red Hat descarta pacotes TCP IPv4 de entrada que carregam opções 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 testa exatamente esse filtro: SYNs que carregam opções ficam sem resposta, enquanto SYNs simples recebem resposta.
Fontes:
Sondas de rede não conseguem provar corrupção de memória. O que elas podem mostrar: se um host responde a SYNs que carregam opções IP, se o SYN-ACK ecoa essas opções e se um filtro de hdrlength está no caminho.
uname -r em relação ao aviso da sua distribuição.tcpdump -ni eth0 'tcp and (ip[0] & 0x0f) > 5'sudo synipopt --target VM_IP --dport 22 --retries 5 --compare --log run.jsonlreply_kind classifica a resposta e o bloco tcp carrega o cabeçalho analisado. ip_options_len mostra o eco, que aparece para blocos de opções com payload (record route, timestamp); preenchimento puro de NOP é eliminado na compilação e retorna como um cabeçalho de 20 bytes.dmesg. A rede só mostra o tratamento de pacotes.Teste hosts que você possui ou está autorizado a testar. A proteção padrão recusa endereços fora de RFC 1918, loopback, link-local, CGNAT e as faixas de documentação. --allow-public existe para engajamentos autorizados em hosts que você controla. Disparar SYNs forjados contra terceiros não é o propósito desta ferramenta.
python3 -m pytest -q