
Verifica se un host scarta SYN TCP IPv4 con IP options (mitigazione della CVE-2026-98432)
Verifica se un host scarta SYN TCP IPv4 con IP options, la mitigazione di rete consigliata per CVE-2026-98432 (use-after-free nell'handshake TCP IPv4 del kernel Linux, CVSS 9.8, fino all'uscita del kernel corretto).
Per ogni porta, invia un SYN normale e un SYN con IPv4 Record Route e confronta le risposte. È innocuo: non sfrutta nulla, apre (e non completa) solo un handshake.
Cosa misura il test: se il SYN con IP options arriva ed è accettato dal target. Non legge la versione del kernel. Un kernel già corretto continua ad accettare IP options e restituisce
ESPOSTO— in quel casoESPOSTOsignifica solo "senza filtro IP options", e la correzione si verifica conuname -r.
./verifica-cve-2026-98432.sh <host-ou-ip> [porta ...] # porta padrão: 443
./verifica-cve-2026-98432.sh exemplo.com 443 80 22
Senza lo script, direttamente tramite l'immagine (il target deve essere IPv4):
docker run --rm --network host --cap-add NET_RAW \
ghcr.io/marcelofmatos/cve-2026-98432-check:latest 203.0.113.10 443 22
| Output | Exit | Significato |
|---|---|---|
MITIGATO | 0 | SYN normale accettato, SYN con IP options scartato — e nessun salto del percorso filtra le IP options |
ESPOSTO | 1 | Anche il SYN con IP options è stato accettato: nulla nel percorso né nel target filtra |
INCONCLUSIVO | 2 | Porta chiusa (RST) o filtrata — usa una porta aperta |
SENZA HOST | 2 | Target nella rete locale e nessuno risponde all'ARP per esso: IP libero o macchina spenta. Nessuna sonda TCP viene inviata |
NON AFFIDABILE | 3 | Qualche salto del percorso (VPN, operatore) scarta già le IP options. Il "scartato" non prova nulla sul target: testa da un altro punto della rete |
| — | 64 | Uso errato, o nome senza IPv4 |
Con più porte, l'exit è il caso peggiore: qualsiasi ESPOSTO → 1.
Dettagli:
getent), quindi i nomi di VPN/DNS interno funzionano.prerouting come se venisse dall'esterno.CHECK_IMAGE (predefinito ghcr.io/marcelofmatos/cve-2026-98432-check:latest). La 1ª riga
del log è la versione: cve-2026-98432-check <versione> in avvio.Molte VPN e operatori scartano pacchetti con IP options. Testando da dietro una di esse,
ogni target sembra mitigato. Per questo il verificatore percorre il percorso salto per salto (TTL
crescente) prima di testare e, se qualche salto scarta le IP options, restituisce NON AFFIDABILE.
Limite: un filtro su un salto che non risponde all'ICMP passa inosservato. Valida il punto di test
su un target notoriamente senza mitigazione: deve risultare ESPOSTO.
Un Linux vulnerabile restituisce le IP options nel SYN-ACK (il SYN con Record Route torna con
ihl=6 e l'opzione RR). In un punto di test con la regola di mitigacao/, questo SYN-ACK
viene scartato nel prerouting. Anche così il risultato è corretto: scapy cattura la risposta nel
socket di pacchetto, prima di netfilter. Misurato: testando, da un host con la regola, un target senza la
regola, il risultato è stato ESPOSTO e il contatore della regola è aumentato di 1 (il SYN-ACK scartato dopo essere
stato catturato). Il test target senza la regola (container) -> ESPOSTO copre questo caso nella CI, che esegue i
test con il runner senza e con la regola.
Eccezione: target dietro una bridge dello stesso punto di test con br_netfilter. Con il modulo
caricato (net.bridge.bridge-nf-call-iptables = 1, predefinito su host Docker Swarm e su molti
Proxmox), il prerouting IPv4 viene eseguito ancora dentro la bridge, prima della cattura. In questo caso, la regola
del punto di test scarta il SYN-ACK di un container o di una VM di quella bridge, e il risultato è un
MITIGATO falso. I target raggiunti tramite una scheda comune (un'altra macchina della rete) non sono interessati.
Per testare container o VM di un host con br_netfilter, usa un punto di test esterno ad esso, o uno
senza la regola. I test saltano questo caso (SKIP) quando rilevano questa combinazione.
Se la porta del target è pubblicata da Docker (DNAT verso un container), il target stesso risponde all'ICMP
"TTL exceeded" alle sonde con TTL basso, perché inoltra il pacchetto. Il verificatore riconosce questo ICMP
dall'IP di origine (è l'IP del target) e non lo tratta come un salto del percorso. Fino alla versione 1.1.0, esso
dava un NON AFFIDABILE falso su un target mitigato quando la 1ª porta della lista era inoltrata. Il
test porta inoltrata sul target mitigato -> MITIGATO copre questo caso.
In mitigacao/:
cve-2026-98432.nft: tabella ip cve_2026_98432 che scarta TCP IPv4 con ip hdrlength > 5
nel prerouting (priority -300). Idempotente.cve-2026-98432-mitigation.service: unit systemd che carica la regola prima che la rete si avvii.sudo install -m 644 mitigacao/cve-2026-98432.nft /etc/cve-2026-98432.nft
sudo install -m 644 mitigacao/cve-2026-98432-mitigation.service /etc/systemd/system/
echo 'net.mptcp.enabled = 0' | sudo tee /etc/sysctl.d/90-cve-2026-98432-mptcp.conf
sudo sysctl -w net.mptcp.enabled=0
sudo systemctl daemon-reload && sudo systemctl enable --now cve-2026-98432-mitigation
La mitigazione non sostituisce il kernel corretto: installalo e riavvia non appena la tua distro lo pubblica.
flowchart LR
U["Operador"] --> L["verifica-cve-2026-98432.sh<br/>resolve o nome com getent<br/>escolhe a rede do container"]
L -->|"docker run --cap-add NET_RAW"| C["Container cve-2026-98432-check<br/>Alpine + Python + scapy"]
C -->|"rede host: alvo remoto"| N["Caminho de rede<br/>VPN, operadora, internet"]
C -->|"bridge: alvo é a própria máquina"| H["prerouting do host"]
N --> A["Alvo IPv4<br/>porta TCP"]
REG["GHCR<br/>ghcr.io/marcelofmatos/cve-2026-98432-check"] -.->|"docker pull"| C
flowchart TD
S["Início: alvo e portas"] --> AQ{"Alvo na rede local<br/>e sem resposta ARP?"}
AQ -->|"sim"| SH["SEM HOST — exit 2"]
AQ -->|"não"| H["TTL = 1, 2, 3 ... até o alvo<br/>SYN normal e SYN com RR"]
H --> HQ{"Algum salto responde ICMP ao normal<br/>e some com o RR?"}
HQ -->|"sim"| FL["caminho FILTRA no salto N"]
HQ -->|"não, chegou ao alvo, ICMP veio do próprio alvo<br/>ou 4 saltos mudos seguidos"| OK["nenhum filtro visto"]
FL --> P
OK --> P
P["Por porta: SYN normal"] --> PQ{"SYN-ACK?"}
PQ -->|"não"| INC["INCONCLUSIVO — exit 2"]
PQ -->|"sim"| R["SYN com RR"]
R --> RQ{"Alguma resposta?"}
RQ -->|"sim"| EXP["EXPOSTO — exit 1"]
RQ -->|"não, caminho filtra"| NC["NÃO CONFIÁVEL — exit 3"]
RQ -->|"não, caminho limpo"| MIT["MITIGADO — exit 0"]
docker build --build-arg APP_VERSION=dev -t cve-2026-98432-check:dev .
# ESPERADO_LOCAL=1 num host sem a regra nft; 0 num host com a regra aplicada
CHECK_IMAGE=cve-2026-98432-check:dev ESPERADO_LOCAL=1 VERSAO_ESPERADA=dev ./tests/testes.sh
Os testes sobem um listener temporário no host (porta 18098) e o testam a partir de um container
na bridge do Docker. No CI (test.yml) o runner roda duas vezes:
sem a regra (precisa sair EXPOSTO) e com a regra aplicada (precisa sair MITIGADO).
Isso valida o método nos dois sentidos.