JohannesLks/CVE-2026-23398: The ICMP Packet Linux Forgot to Ignore
A tiny Scapy proof of concept, a forged inner header, and a kernel validation path that turns a routine network error into a crash.
- The crash path is interesting because a routine ICMP error is shaped to satisfy validation, not because it brute-forces the kernel.
- Protocol 253 matters because the strict PMTU branch asks for a handler that the kernel never registered.
- The repo reads like an executable bug report, with one Python file translating kernel behavior into a short Scapy script.
- The broader lesson is that mature protocol stacks still fail when an "impossible" slot turns out to be reachable.
One packet, one missing check
The repo's whole trick fits in one sentence: a crafted ICMP Destination Unreachable, Fragmentation Needed message can drive Linux into a null pointer dereference if the inner packet is shaped to pass validation first. That is what makes this PoC interesting. The trigger is tiny, the code is readable, and the failure mode is dramatic.
This is not a noisy spray of malformed traffic. It is a standards-adjacent packet aimed at a trust boundary. The bug lives in the assumption that a protocol handler will always exist when the kernel asks for one.
‼️ CVE-2026-23398: Linux Kernel ICMP DoS Vulnerability PoC: https://t.co/qD3Vo8jHAF
The forged header that gets the packet inside
The clever part is not the outer ICMP shell. It is the inner IP header. The PoC forges that nested header so the kernel treats the error as if it belongs to traffic the target itself sent, which helps it survive the first validation gates.
UNREGISTERED_PROTO = 253 # RFC 3692 experimental protocol
def build_frag_needed(target):
inner = IP(src=target, proto=UNREGISTERED_PROTO)
return IP(dst=target) / ICMP(type=3, code=4) / inner
Why protocol 253 is the empty slot
Protocol 253 is not a random number. It is an experimental protocol slot from RFC 3692, which means it can exist in the registry without having a normal handler behind it. That matters because the strict PMTU path eventually reaches into the protocol table and expects a live function pointer.
The sysctl setting net.ipv4.ip_no_pmtu_disc = 3 nudges Linux onto that stricter path. Without it, the packet does not reach the same validation logic. With it, the empty slot becomes a crash surface.
icmp_rcv()
-> icmp_unreach()
-> icmp_tag_validation()
-> inet_protos[253]
-> null dereference
The path from ICMP error to kernel panic
The flow is short, which is part of the point. icmp_rcv() accepts the packet, icmp_unreach() handles the destination-unreachable case, and icmp_tag_validation() inspects the embedded header. Under the right sysctl setting, the kernel takes the strict branch and tries to validate the inner protocol against inet_protos[253].
icmp_rcv()receives the outer packet.icmp_unreach()routes the fragmentation-needed case.icmp_tag_validation()inspects the inner header.inet_protos[253]is empty, so the dereference falls through.- The kernel crashes in softirq context.
The important thing is not the exact line number. It is the shape of the assumption. The kernel treats a reserved protocol slot like a safe lookup, then discovers that the lookup itself is the bug.
Why this PoC is unusually clean
The repository is almost aggressively small. One Python file, Scapy, a README, and enough comments to map the packet structure back to the kernel path. That minimalism makes it better than many bigger PoCs. You can inspect the whole idea without guessing where the clever part is hidden.
Built to explain, not to spread
JohannesLks presents the bug as a focused proof of concept, not a toolkit. That keeps the artifact legible, and it makes the README feel like a lab note rather than a product page.
| Dimension | Typical kernel DoS PoC | JohannesLks/CVE-2026-23398 |
|---|---|---|
| Trigger size | Often a chain of packets, state, or timing | One crafted ICMP error |
| Prerequisites | Complex setup or unstable conditions | Root privileges and a specific sysctl setting |
| Bug class | Memory corruption or state machine abuse | Null pointer dereference in validation |
| Code complexity | Exploit framework with many moving parts | A short Scapy script |
| Observability | Hard to trace by inspection | Easy to map back to the kernel path |
| Failure mode | Crash, hang, or attempted code execution | Kernel panic or denial of service |
| What the reader learns | Exploit mechanics | Protocol trust boundaries |
What it says about kernel error handling
This is not a memory safety puzzle. It is an assumption puzzle. The bug shows what happens when a mature networking stack assumes that every protocol lookup will land on a real handler, even inside a validation path that was supposed to make the system safer.
That is why the PoC matters. It turns a narrow edge case into something you can reason about, test, and patch against. Small protocol corners are where large systems still get embarrassed.





