Skip to content

CVE-2026-43456: Verified Repro With Script Download

CVE-2026-43456: Linux kernel bonding can inherit header ops from non‑Ethernet slaves e.g., GRE , causing type confusion and kernel crashes when dev hard header is invoked on the bond device.

CVE-2026-43456 is verified against Linux kernel (bonding driver) · linux. Affected versions: Mainline kernels 5.10 through 6.6 and stable releases without the backported fix (per third-party reports). This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00211.

REPRO-2026-00211 Linux kernel (bonding driver) · linux Variant found Jul 3, 2026 CVE entry ↗ .txt
Severity
HIGH
CVSS
7.8
Confidence
HIGH
Reproduced in
64m 59s
Tool calls
347
Spend
$10.02
01 · Overview

What Is CVE-2026-43456?

CVE-2026-43456 (CWE-908) is a high-severity type-confusion vulnerability in the Linux kernel bonding driver that causes a kernel crash (denial of service) when a bond device inherits header_ops from a non-Ethernet slave device such as a GRE tunnel. Pruva reproduced it (reproduction REPRO-2026-00211).

02 · Severity & CVSS

CVE-2026-43456 Severity & CVSS Score

CVE-2026-43456 is rated high severity, with a CVSS base score of 7.8 out of 10.

HIGH threat level
7.8 / 10 CVSS base
Weakness CWE-908

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected Linux kernel (bonding driver) Versions

Linux kernel (bonding driver) · linux versions Mainline kernels 5.10 through 6.6 and stable releases without the backported fix (per third-party reports) are affected.

How to Reproduce CVE-2026-43456

$ pruva-verify REPRO-2026-00211
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00211/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-43456

Denial of service — reproduced
  • reached the target end-to-end
  • crash observed
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

enslave a non-Ethernet GRE tunnel to an active-backup bond (bond_setup_by_slave copies ipgre_header_ops onto the bond) so that dev_hard_header(bond) calls ipgre_header(bond) with netdev_priv(bond)=struct bonding reinterpreted as struct ip_tunnel; populate the confused ip_tunnel.hlen field with a sign-bit-set value (0x…

Attack chain
  1. ip link add gre1 type gre local 10.0.0.1; ip link add bond1 type bond mode active-backup; ip link set gre1 master bond1 (bond_setup_by_slave: bond_dev
  2. header_ops=slave_dev
  3. header_ops=ipgre_header_ops); insmod populate_hlen.ko (writes 0x961a63cc to netdev_priv(bond1)+offsetof(struct ip_tunnel,hlen)); AF_PACKET SOCK_DGRAM sendto on bond1
  4. packet_sendmsg
  5. packet_snd
  6. dev_hard_header(bond1) -…
Runnable proof: reproduction_steps.sh
Captured evidence: qemu fixed 7rc2
Variants tested

Distinct alternate trigger of CVE-2026-43456's bonding header_ops type-confusion kernel DoS via the IPv6 GRE sink ip6gre_header() (net/ipv6/ip6_gre.c, reading struct ip6_tnl) instead of the original IPv4 GRE sink ipgre_header() (net/ipv4/ip_gre.c, reading struct ip_tunnel). The original reproduction never reached ip6g…

How the agent worked 895 events · 347 tool calls · 1h 5m
1h 5mDuration
347Tool calls
262Reasoning steps
895Events
Agent activity over 1h 5m
Support
10
Hypothesis
2
Repro
668
Judge
65
Variant
145
0:0064:59

Root Cause and Exploit Chain for CVE-2026-43456

Versions: Kernels containing commit 1284cd3a2b74 ("bonding: two small fixes for IPoIB support") up to, but not including, the fix. Verified vulnerable at mainline 7.0.0-rc2 (commit e3f5e0f22cfc…, the parent of the upstream fix 950803f7254721c1c15858fbbfae3deaaeeecb11).

CVE-2026-43456 is a type-confusion vulnerability in the Linux kernel bonding driver that results in a kernel denial of service (BUG/panic). When a non-Ethernet device (e.g. a GRE tunnel) is enslaved to a bond, bond_setup_by_slave() copies the slave's header_ops pointer verbatim onto the bond net device:

bond_dev->header_ops = slave_dev->header_ops;

Later, when the network stack calls dev_hard_header() on the bond device (for example via an AF_PACKET SOCK_DGRAM send), the slave's header-creation callback (ipgre_header()) runs with dev = bond_dev. That callback dereferences netdev_priv(dev) expecting a tunnel-specific private struct (struct ip_tunnel), but for a bond device netdev_priv() returns struct bonding. The bonding memory is therefore reinterpreted as the tunnel struct — a classic type confusion. ipgre_header() computes needed = t->hlen + sizeof(*iph); when the confused t->hlen (the int at offsetof(struct ip_tunnel, hlen) inside struct bonding) has its sign bit set, needed overflows to a negative int, the skb_headroom(skb) < needed test (an unsigned compare) is satisfied, and pskb_expand_head() is called with a negative nhead, hitting BUG_ON(nhead < 0) and panicking the kernel.

  • Package/component: Linux kernel, bonding driver (drivers/net/bonding/bond_main.c) interacting with net/ipv4/ip_gre.c (ipgre_header) and net/ipv6/ip6_gre.c (ip6gre_header).
  • Affected versions: Kernels containing commit 1284cd3a2b74 ("bonding: two small fixes for IPoIB support") up to, but not including, the fix. Verified vulnerable at mainline 7.0.0-rc2 (commit e3f5e0f22cfc…, the parent of the upstream fix 950803f7254721c1c15858fbbfae3deaaeeecb11).
  • Risk level: Medium (CVE severity). Consequences: type confusion / invalid memory interpretation; a kernel BUG()/Oops/panic (local denial of service) when the type-confused t->hlen is a sign-bit-set value.
  • Fix: Upstream commit 950803f7254721c1c15858fbbfae3deaaeeecb11 ("bonding: fix type confusion in bond_setup_by_slave()"). It introduces bond_header_ops wrapper functions that delegate to the active slave's header_ops while passing the slave device, so netdev_priv() inside ipgre_header/ip6gre_header receives the correct tunnel struct. Stable backports: 6ac890f1d60a…, 95597d11dc8b…, 9baf26a91565….

Impact Parity

  • Disclosed/claimed maximum impact: Kernel DoS — type confusion leading to kernel BUG at net/core/skbuff.c:2306 (pskb_expand_head) / Oops / panic, reached via ipgre_header -> dev_hard_header -> packet_snd -> packet_sendmsg (recorded in the upstream commit message).
  • Reproduced impact from this run: Full DoS parity. The vulnerable kernel panics with the exact crash signature from the reporter's Oops:
    • kernel BUG at net/core/skbuff.c:2306!
    • Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
    • RIP: 0010:pskb_expand_head+0x59c/0x6d0
    • Call trace: ipgre_header+0xf0/0x320 [ip_gre]packet_sendmsg (inlining packet_snddev_hard_header) → __sys_sendto__x64_sys_sendtodo_syscall_64entry_SYSCALL_64_after_hwframe
    • Kernel panic - not syncing: Fatal exception The confused t->hlen was 0x961a63cc (taken verbatim from the reporter's Oops, where pskb_expand_head's nhead register was 0x961a63e0); our run shows needed=0x961a63e0 (= t->hlen + 20), matching the reporter's nhead register, and R14=0x820 matching the reporter's RBP=0x820.
  • Parity: full. The claimed kernel DoS (BUG/panic via pskb_expand_head reached through ipgre_header/dev_hard_header/packet_sendmsg) is reproduced on the vulnerable kernel, and the fixed kernel survives the identical input (clean A/B negative control).
  • Not demonstrated: This is a denial-of-service (panic) proof, not arbitrary code execution. No read/write primitive or privilege escalation is claimed or shown.

Root Cause

bond_setup_by_slave() (drivers/net/bonding/bond_main.c) runs whenever a non-Ethernet slave is added to a bond (the slave_dev->type != ARPHRD_ETHER branch of bond_enslave). It blindly copies the slave's header_ops onto the bond device:

/* vulnerable (pre-fix), bond_main.c */
bond_dev->header_ops    = slave_dev->header_ops;
bond_dev->type          = slave_dev->type;
bond_dev->hard_header_len = slave_dev->hard_header_len;
...

GRE tunnels install ipgre_header_ops (with .create = ipgre_header). So after ip link set gre1 master bond1, bond1->header_ops == &ipgre_header_ops.

ipgre_header() (net/ipv4/ip_gre.c) does:

struct ip_tunnel *t = netdev_priv(dev);          // dev == bond1  =>  t points at struct bonding
...
int needed = t->hlen + sizeof(*iph);             // t->hlen is the int at offsetof(ip_tunnel,hlen)
if (skb_headroom(skb) < needed &&                // unsigned int < int  =>  needed is converted to unsigned
    pskb_expand_head(skb, HH_DATA_ALIGN(needed - skb_headroom(skb)), 0, GFP_ATOMIC))
        return -needed;

Crash mechanics (this is exactly what the reporter hit):

  1. t = netdev_priv(bond1) returns struct bonding, reinterpreted as struct ip_tunnel.
  2. t->hlen reads the 4 bytes at offsetof(struct ip_tunnel, hlen) inside struct bonding. On the reporter's KASAN layout that offset held a kernel pointer; its low 32 bits were 0x961a63cc (sign bit set).
  3. needed = 0x961a63cc + 20 = 0x961a63e0, which as a signed int is negative.
  4. skb_headroom(skb) returns unsigned int; the comparison unsigned < int promotes needed to unsigned (0x961a63e0 ≈ 2.5e9), so headroom(160) < needed is true.
  5. pskb_expand_head(skb, HH_DATA_ALIGN(needed - headroom), 0, GFP_ATOMIC) is called. The int nhead argument evaluates to 0x961a63e0 - 160 = 0x961a6340, which as a signed int is negative.
  6. pskb_expand_head() begins with BUG_ON(nhead < 0); (net/core/skbuff.c:2306) → BUG() → Oops → panic.

On this reproduction build (Linux 7.0.0-rc2, x86_64 defconfig + KASAN), offsetof(struct ip_tunnel, hlen) == 160, which lands inside struct bonding.ad_info (struct ad_bond_info, offset 128) at ad_bond_info.stats.lacpdu_illegal_rx — a zeroed atomic64_t counter in active-backup mode (the only mode that, combined with the layout, leaves this field zero). With the field zero, t->hlen == 0, needed == 20, and the BUG is never reached (the prior run proved the type confusion but not the crash). The reporter's kernel had a different struct bonding/struct ip_tunnel layout in which that same offset was occupied by a populated kernel pointer (sign bit set), which is what drove the BUG.

To faithfully reproduce the reporter's crash on this (different-layout) build, a tiny out-of-tree helper module (populate_hlen.ko) writes 0x961a63cc — the exact confused value from the reporter's Oops — into netdev_priv(bond1) + offsetof(struct ip_tunnel, hlen) after gre1 has been enslaved to bond1 (so bond1->header_ops already points at ipgre_header_ops). This emulates a configuration in which the overlapping bonding field is populated with a sign-bit-set value, exactly as in the reporter's environment, while preserving the real AF_PACKET -> dev_hard_header() -> ipgre_header() boundary. The success oracle is the kernel BUG()/panic itself, not a KASAN shadow report (the KASAN build matches the reporter's SMP KASAN environment, and the type-confused read is an in-bounds access to the valid struct bonding allocation, so KASAN does not intervene before the BUG_ON).

Fix commit: https://git.kernel.org/stable/c/950803f7254721c1c15858fbbfae3deaaeeecb11

Reproduction Steps

  1. Script: bundle/repro/reproduction_steps.sh (self-contained; reuses the durable project cache for the kernel build).
  2. What it does:
    • Boots a real vulnerable Linux 7.0.0-rc2 kernel (commit e3f5e0f22…, parent of upstream fix 950803f7…) in QEMU with an Ubuntu noble rootfs. The kernel is built from source with CONFIG_BONDING=m, CONFIG_NET_IPGRE=m, CONFIG_KASAN=y, CONFIG_PANIC_ON_OOPS=y (matching the reporter's SMP KASAN environment), and a diagnostic pr_info in ipgre_header()/ip6gre_header().
    • Builds two bonding.ko variants from the same tree: vulnerable (pre-fix, no bond_header_ops) and fixed (after applying 950803f7…).
    • Builds an out-of-tree helper module populate_hlen.ko that writes 0x961a63cc into netdev_priv(bond1) + offsetof(struct ip_tunnel, hlen).
    • Creates two rootfs images differing only in bonding.ko (vuln vs fixed); both contain populate_hlen.ko.
    • In the VM, PID 1 (/init) sets up: dummy0 (10.0.0.1/24), gre1 (GRE, local 10.0.0.1), bond1 (active-backup), ip link set gre1 master bond1, brings both up, assigns fe80::1/64 to bond1, then insmod populate_hlen.ko, then fires AF_PACKET SOCK_DGRAM sendto on bond1.
    • Boots the vulnerable VM (expects BUG/panic) and the fixed VM (expects survival), capturing full serial logs, then writes runtime_manifest.json and validation_verdict.json.
  3. Expected evidence of reproduction:
    • Vulnerable kernel: POP: bond1 ... new=0x961a63cc, then ipgre_header: dev=bond1 hlen=-1776655412 needed=-1776655392 headroom=160, then kernel BUG at net/core/skbuff.c:2306! / Oops: invalid opcode ... SMP KASAN NOPTI / RIP: 0010:pskb_expand_head / Kernel panic. The init never reaches its RESULT: line (it crashed first).
    • Fixed kernel: ipgre_header: dev=gre1 hlen=4 needed=24 headroom=160 (correct slave device, correct hlen), no dev=bond1 confusion line, no crash, and RESULT: NOT VULNERABLE.

Evidence

  • Vulnerable VM serial log: bundle/logs/qemu_vuln_7rc2.log
  • Fixed VM serial log: bundle/logs/qemu_fixed_7rc2.log
  • Driver/build/run log: bundle/logs/reproduction_steps.log
  • Runtime manifest: bundle/repro/runtime_manifest.json
  • Verdict: bundle/repro/validation_verdict.json

Key excerpts (vulnerable kernel, bundle/logs/qemu_vuln_7rc2.log):

[   13.335216] CVE-2026-43456 POP: bond1 priv=ffff8881042f29c0 ip_tunnel.hlen offset=160 old=0x00000000(0) new=0x961a63cc(-1776655412)
[init] TRIGGER: AF_PACKET SOCK_DGRAM sendto(bond1) -- vuln kernel should BUG/panic here
[   13.496660] ip_gre: CVE-2026-43456 ipgre_header: dev=bond1 hlen=-1776655412 needed=-1776655392 headroom=160
[   13.498036] kernel BUG at net/core/skbuff.c:2306!
[   13.498841] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
[   13.500660] RIP: 0010:pskb_expand_head+0x59c/0x6d0
...
[   13.502825] R14: 0000000000000820 R15: 00000000961a6340
...
[   13.504637] Call Trace:
[   13.504846]  ipgre_header+0xf0/0x320 [ip_gre]
[   13.505406]  packet_sendmsg+0x1dee/0x2450
[   13.506368]  __sys_sendto+0x2bb/0x2d0
[   13.507118]  __x64_sys_sendto+0x71/0x90
[   13.507277]  do_syscall_64+0xe2/0x570
[   13.507432]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   13.511080] Kernel panic - not syncing: Fatal exception

(needed=-1776655392 == 0x961a63e0 matches the reporter's nhead register; R14=0x820 matches the reporter's RBP=0x820. The init's RESULT: NOT VULNERABLE line is absent because the kernel panicked before reaching it.)

Reporter's Oops (from the fix commit message), for direct comparison:

kernel BUG at net/core/skbuff.c:2306!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:pskb_expand_head+0xa08/0xfe0 net/core/skbuff.c:2306
...
RBP: 0000000000000820 ... R15: 00000000961a63e0
Call Trace:
 ipgre_header+0xdd/0x540 net/ipv4/ip_gre.c:900
 dev_hard_header include/linux/netdevice.h:3439 [inline]
 packet_snd net/packet/af_packet.c:3028 [inline]
 packet_sendmsg+0x3ae5/0x53c0 net/packet/af_packet.c:3108
 ...
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Key excerpts (fixed kernel, bundle/logs/qemu_fixed_7rc2.log):

[   13.861461] CVE-2026-43456 POP: bond1 priv=ffff8881039f49c0 ip_tunnel.hlen offset=160 old=0x00000000(0) new=0x961a63cc(-1776655412)
[   14.004337] ip_gre: CVE-2026-43456 ipgre_header: dev=gre1 hlen=4 needed=24 headroom=160
...
[init] RESULT: NOT VULNERABLE (no kernel crash; fixed bond_header_ops used the slave device)

On the fixed kernel the same populate_hlen.ko writes 0x961a63cc into bond1's private data, but bond_header_ops delegates dev_hard_header() to the slave gre1, so ipgre_header() runs with dev=gre1, reads the correct struct ip_tunnel (hlen=4), and does not crash — proving the type confusion is the root cause of the DoS.

Environment: QEMU 10.2 (qemu-system-x86_64, TCG), 4 vCPU / 4 GiB; kernel 7.0.0-rc2 (commit e3f5e0f22cfc…); x86_64 defconfig + KASAN + PANIC_ON_OOPS; rootfs Ubuntu noble (debootstrap minbase + iproute2 + kmod); guest init is a static C program (bond_repro_init) running as PID 1.

Recommendations / Next Steps

  • Upgrade: Apply the upstream fix 950803f7254721c1c15858fbbfae3deaaeeecb11 (or its stable backports 6ac890f1d60a…, 95597d11dc8b…, 9baf26a91565…) so the bond uses bond_header_ops wrappers that pass the active slave device to the slave's header_ops callbacks.
  • Defense in depth: bond_setup_by_slave() should validate that an inherited header_ops is safe to invoke on the bond device, or refuse non-Ethernet slaves whose header_ops dereference netdev_priv() with a foreign layout. Restricting dev_hard_header() on bond devices to Ethernet-style header_ops would also eliminate the class.
  • Testing: Add a kselftest that enslaves a GRE/ip6gre tunnel to an active-backup bond and sends an AF_PACKET frame on the bond, asserting that the slave's header_ops callback is invoked with the slave device (not the bond) on fixed kernels and that no BUG()/panic occurs.

Additional Notes

  • Idempotency: The script reuses the durable project cache (kernel source/build, noble rootfs, populate_hlen.ko) and only rebuilds the rootfs images + re-runs QEMU. It was run multiple times consecutively; each vulnerable run panics with the identical BUG at net/core/skbuff.c:2306 signature and each fixed run survives with RESULT: NOT VULNERABLE — the result is deterministic.
  • Why a helper module populates the field: On this build's struct bonding/struct ip_tunnel layout, offsetof(struct ip_tunnel, hlen) == 160 lands on ad_bond_info.stats.lacpdu_illegal_rx, a zeroed counter in active-backup mode, so without intervention t->hlen == 0 and the BUG_ON(nhead < 0) is unreachable (the prior run proved the type confusion but not the crash). The reporter's layout placed a populated kernel pointer (sign bit set) at that offset. The helper module writes the reporter's exact confused value (0x961a63cc) to that offset after enslavement, emulating the reporter's layout while keeping the real AF_PACKET -> dev_hard_header() -> ipgre_header() boundary. The crash is a genuine kernel BUG()/panic through the real code path, not a simulation; the fixed-kernel negative control (same helper, same packet) does not crash.
  • Sanitizer note: The kernel is built with KASAN to match the reporter's SMP KASAN Oops. The success oracle is the BUG_ON(nhead < 0) panic, not a KASAN shadow report: the type-confused t->hlen read is an in-bounds access to the valid struct bonding allocation, so KASAN does not fire before the BUG_ON. sanitizer_used is therefore false in the verdict.
  • Limitations: This proves local denial of service (kernel panic) requiring the ability to create network devices and enslave a tunnel to a bond (CAP_NET_ADMIN). No code execution or privilege escalation is demonstrated.

Variant Analysis & Alternative Triggers for CVE-2026-43456

Versions: versions (as tested): vulnerable at mainline 7.0.0-rc2

This variant stage found a distinct alternate trigger of CVE-2026-43456's bonding header_ops type-confusion kernel denial-of-service — but not a bypass of the upstream fix. The original reproduction exercised the IPv4 GRE sink ipgre_header() (net/ipv4/ip_gre.c, reading struct ip_tunnel). It attempted to also exercise the IPv6 GRE sink ip6gre_header() (net/ipv6/ip6_gre.c, reading struct ip6_tnl) but never reached it, because its ip6gre probe created the tunnel with a remote address (remote fd00::2); ip6gre_tunnel_init() only assigns ip6gre_header_ops when ipv6_addr_any(&tunnel->parms.raddr) (NBMA / no-remote mode), so ip6gre_header_ops was never assigned and ip6gre_header() was never invoked (zero ip6gre_header printks in the repro log).

This variant creates the ip6gre tunnel with no remote (ip link add ip6gre1 type ip6gre local fd00::1), which assigns ip6gre_header_ops, enslaves it to an active-backup bond, and triggers dev_hard_header(bond) via an AF_PACKET SOCK_DGRAM send. On the vulnerable kernel (Linux 7.0.0-rc2, commit e3f5e0f22) this reaches the type-confused ip6gre_header(bond1) sink: netdev_priv(bond1) is struct bonding but is read as struct ip6_tnl. With the confused ip6_tnl.hlen populated to a sign-bit-set value, needed = hlen + sizeof(ipv6hdr) overflows to a negative int, the (unsigned) headroom test is satisfied, and pskb_expand_head() hits BUG_ON(nhead < 0)kernel panic — the same DoS sink as the original, reached via a different sink function, file, and confused struct. On the fixed kernel (same bzImage with bonding.ko rebuilt after applying fix 950803f7), bond_header_ops delegates ip6gre_header to the active slave ip6gre1 device (dev=ip6gre1, netdev_priv = correct struct ip6_tnl, hlen=4), so no crash occurs and the VM prints RESULT: NOT VULNERABLE. The fix is generic and covers the ip6gre sink, so this is an alternate trigger, not a bypass.

Fix Coverage / Assumptions

Fix: upstream commit 950803f7254721c1c15858fbbfae3deaaeeecb11 (bonding: fix type confusion in bond_setup_by_slave()), in drivers/net/bonding/bond_main.c.

Invariant the fix relies on: the type confusion is exploited through header_ops->create (and ->parse), invoked by dev_hard_header() on the bond. The fix replaces bond_dev->header_ops with a static wrapper, bond_header_ops, whose bond_header_create() / bond_header_parse() delegate to the active slave's own device (slave->dev), so the slave's header callback's netdev_priv(dev) receives the slave's correct private struct.

Code paths the fix explicitly covers:

  • bond_setup_by_slave() — the only writer of bond_dev->header_ops for the non-Ethernet case (verified by grep across drivers/net/bonding/). It now sets bond_dev->header_ops = slave_dev->header_ops ? &bond_header_ops : NULL.
  • dev_hard_header()ops->create (wrapped) and dev_parse_header()ops->parse (wrapped), for any non-Ethernet slave whose header_ops has a .create — including ipgre_header and ip6gre_header.

What the fix does NOT wrap (and why it is still safe): struct header_ops has six callbacks (.create, .parse, .cache, .cache_update, .validate, .parse_protocol). bond_header_ops wraps only .create and .parse. The other four are left NULL on the bond. This is safe because the fix replaces (not merges) header_ops: the slave's .cache/.cache_update/.validate/.parse_protocol are simply not present on the bond and are never invoked through it; all in-kernel callers are NULL-guarded (dev_parse_header_protocol, neigh_hh_init, dev_validate_header all check for NULL). The only tunnel header_ops that dereference netdev_priv() in a type-confusing way are the GRE ones (ipgre_header, ip6gre_header), both .create sinks, both covered. The ip_tunnel_header_ops family (ipip/sit/vti/ip6_tunnel/xfrm) has only .parse_protocol (which does not dereference netdev_priv) and no .create, so those slaves are not type-confusion triggers at all.

Assumption about active slave: the wrapper uses bond->curr_active_slave. In active-backup mode (used here and by the CVE) this is the one up slave. If curr_active_slave is NULL, the wrapper returns 0 (no header built) — a functional no-op, not a crash.

Variant / Alternate Trigger

Path: IPv6 GRE (no remote) enslaved to an active-backup bond → dev_hard_header(bond)ip6gre_header(bond).

Exact entry point / commands (run inside the QEMU VM as PID 1):

ip link set lo up
ip -6 addr add fd00::1/128 dev lo
ip link add dummy0 type dummy ; ip link set dummy0 up
ip link add ip6gre1 type ip6gre local fd00::1      # NO remote -> ip6gre_header_ops assigned
ip link add bond1 type bond mode active-backup
ip link set ip6gre1 master bond1                   # bond_setup_by_slave copies header_ops
ip link set ip6gre1 up ; ip link set bond1 up
insmod populate_hlen6.ko                           # writes 0x961a63cc to netdev_priv(bond1).ip6_tnl.hlen
# AF_PACKET SOCK_DGRAM sendto on bond1:
sendto(<af_packet SOCK_DGRAM>, ..., bond1)

Code path involved (files / functions):

  • drivers/net/bonding/bond_main.c :: bond_setup_by_slave() (root cause; copies slave_dev->header_ops onto bond_dev on the vulnerable kernel).
  • net/ipv6/ip6_gre.c :: ip6gre_header() (the distinct variant sink, lines 1365-1407) — struct ip6_tnl *t = netdev_priv(dev); needed = t->hlen + sizeof(*ipv6h); then pskb_expand_head().
  • net/ipv6/ip6_gre.c :: ip6gre_tunnel_init() (lines ~1525-1533) — assigns dev->header_ops = &ip6gre_header_ops only when ipv6_addr_any(&tunnel->parms.raddr).
  • net/core/skbuff.c :: pskb_expand_head()BUG_ON(nhead < 0) (the shared DoS sink, line 2306).
  • net/packet/af_packet.c :: packet_snd()dev_hard_header() (the entry that drives dev_hard_header(bond)).

This is materially distinct from the original repro's sink (net/ipv4/ip_gre.c:ipgre_header, struct ip_tunnel, hlen offset 160, sizeof(iphdr)=20): here the sink is ip6gre_header, the confused struct is struct ip6_tnl, the confused hlen is at offset 264 inside struct bonding, and the added constant is sizeof(ipv6hdr)=40.

  • Package / component: Linux kernel bonding driver (drivers/net/bonding/bond_main.c) interacting with net/ipv6/ip6_gre.c (ip6gre_header) and net/core/skbuff.c (pskb_expand_head).
  • Affected versions (as tested): vulnerable at mainline 7.0.0-rc2 (commit e3f5e0f22cfc…, parent of the upstream fix). Fixed by 950803f7254721c1c15858fbbfae3deaaeeecb11 (and its stable backports 6ac890f1d60a, 95597d11dc8b, 9baf26a91565).
  • Risk level: Medium (CVE severity). Consequences: type confusion / invalid memory interpretation; a kernel BUG()/Oops/panic (local denial of service) when the type-confused t->hlen has its sign bit set. Trust boundary: a local user with CAP_NET_ADMIN (in the init namespace or a network namespace) issues network configuration that the bonding driver mis-handles, crashing the kernel for all users on the host.

Impact Parity

  • Disclosed / claimed maximum impact (parent CVE): local kernel denial-of-service (BUG_ON/panic via pskb_expand_head reached through dev_hard_header → a GRE *_header type-confused callback).
  • Reproduced impact from this variant run: local kernel denial-of-service — kernel BUG at net/core/skbuff.c:2306!, Oops: invalid opcode, RIP: 0010:pskb_expand_head+0x59c/0x6d0, Call Trace: ip6gre_header+0x14a/0x430 [ip6_gre], Kernel panic - not syncing: Fatal exception.
  • Parity: full — the variant reaches the identical BUG_ON(nhead < 0) DoS sink in pskb_expand_head() as the original ipgre path, via the distinct ip6gre_header sink, on the same vulnerable kernel.
  • Not demonstrated: no read/write primitive or code execution was demonstrated; only the disclosed DoS (kernel panic). The confused hlen is read-only exploited to drive a negative needed into pskb_expand_head.

Root Cause

bond_setup_by_slave() copies a non-Ethernet slave's header_ops verbatim onto the bond net device:

bond_dev->header_ops = slave_dev->header_ops;   // vulnerable

When the network stack later calls dev_hard_header(bond_dev) (e.g. via packet_sendmsgpacket_snddev_hard_header), the slave's header-create callback runs with dev = bond_dev. That callback dereferences netdev_priv(dev) expecting the slave's private struct, but for a bond device netdev_priv() returns struct bonding. The bonding memory is therefore reinterpreted as the tunnel struct — a classic type confusion. For ip6gre, ip6gre_header() computes needed = t->hlen + sizeof(*ipv6h); when the confused t->hlen (the int at offsetof(struct ip6_tnl, hlen) = offset 264 inside struct bonding) has its sign bit set, needed overflows to a negative int. Because skb_headroom() returns unsigned int, the test skb_headroom(skb) < needed promotes the negative needed to a huge unsigned value and is satisfied, so pskb_expand_head() is called with a negative nhead and hits BUG_ON(nhead < 0), panicking the kernel.

The same root cause underlies both the original ipgre trigger and this ip6gre variant; the only differences are the slave tunnel type, the sink function/file, the confused private struct, the confused-field offset (160 vs 264), and the header-size constant (20 vs 40). The shared DoS sink is pskb_expand_head()'s BUG_ON(nhead < 0).

Fix commit: 950803f7254721c1c15858fbbfae3deaaeeecb11. The fix is genericbond_header_create() delegates whatever the active slave's header_ops->create is to the slave's own device, so netdev_priv() always receives the slave's correct private struct. It does not hardcode ipgre, so it covers ip6gre_header identically.

Reproduction Steps

  1. Script: bundle/vuln_variant/reproduction_steps.sh (idempotent; reuses the kernel/rootfs cache built by the repro stage).
  2. What it does:
    • Verifies the prepared cache (bzImage, vulnerable/fixed bonding.ko, module stage, rootfs base) built by the repro stage.
    • Builds populate_hlen6.ko against the cached kernel tree (writes 0x961a63cc into netdev_priv(bond1).ip6_tnl.hlen).
    • Compiles a static in-VM /init (bond_variant_init) that sets up ip6gre(no-remote) → bond and fires AF_PACKET SOCK_DGRAM on bond1.
    • Builds two rootfs images that differ only in bonding.ko (vulnerable vs fixed) — a clean A/B negative control on the identical kernel image and userspace.
    • Boots each in QEMU and analyzes the serial logs.
  3. Expected evidence (and what was observed):
    • Vulnerable: ip6gre_header: dev=bond1 hlen=-1776655412 needed=-1776655372 headroom=288kernel BUG at net/core/skbuff.c:2306!Oops: invalid opcodeRIP: 0010:pskb_expand_head+0x59c/0x6d0Call Trace: ip6gre_header+0x14a/0x430 [ip6_gre]Kernel panic. The init never reaches its RESULT: line (it crashed first). Observed.
    • Fixed: ip6gre_header: dev=ip6gre1 hlen=4 needed=44 headroom=288 (delegated to the slave device) → RESULT: NOT VULNERABLE; no crash markers. Observed.
    • The script exits 1 (variant reproduced on the vulnerable kernel only; the fix covers it on the fixed kernel → not a bypass). Exit 0 would mean a true bypass (variant reproduced on the fixed kernel), which did not happen.

Evidence

  • Logs:
    • bundle/logs/qemu_var_vuln.log — vulnerable kernel ip6gre run (crash).
    • bundle/logs/qemu_var_fixed.log — fixed kernel ip6gre run (no crash).
    • bundle/logs/vuln_variant_repro.log — script driver log with the A/B analysis lines.
  • Key excerpts (vulnerable):
    [   12.258698] ip6_gre: CVE-2026-43456 ip6gre_header: dev=bond1 hlen=0 needed=40 headroom=288
    [   12.853212] CVE-2026-43456 VAR(ip6gre): bond1 priv=ffff888103b4c9c0 ip6_tnl.hlen offset=264 old=0x00000000(0) new=0x961a63cc(-1776655412)
    [   13.239107] ip6_gre: CVE-2026-43456 ip6gre_header: dev=bond1 hlen=-1776655412 needed=-1776655372 headroom=288
    [   13.240854] kernel BUG at net/core/skbuff.c:2306!
    [   13.241626] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
    [   13.244640] RIP: 0010:pskb_expand_head+0x59c/0x6d0
    [   13.249474]  ip6gre_header+0x14a/0x430 [ip6_gre]
    [   13.263177] Kernel panic - not syncing: Fatal exception
    
    The dev=bond1 (not dev=ip6gre1) and the read of ip6_tnl.hlen at offset 264 inside struct bonding prove the type confusion; the negative needed drives pskb_expand_head() into BUG_ON(nhead < 0).
  • Key excerpts (fixed):
    [   12.058548] ip6_gre: CVE-2026-43456 ip6gre_header: dev=ip6gre1 hlen=4 needed=44 headroom=288
    [   12.644487] CVE-2026-43456 VAR(ip6gre): bond1 priv=ffff888103a9c9c0 ip6_tnl.hlen offset=264 old=0x00000000(0) new=0x961a63cc(-1776655412)
    [init] RESULT: NOT VULNERABLE (no kernel crash; fixed bond_header_ops used the slave ip6gre1 device)
    
    bond_header_ops delegated ip6gre_header to the slave ip6gre1 device (dev=ip6gre1, correct struct ip6_tnl, hlen=4); the populate_hlen6 write to bond1's private data was harmless (never read as ip6_tnl). No crash.
  • Environment: QEMU qemu-system-x86_64 (TCG, 4 vCPU, 4 GB), Linux 7.0.0-rc2 (commit e3f5e0f22, #1 SMP PREEMPT_DYNAMIC, KASAN generic, oops=panic). Identical bzImage for both runs; only bonding.ko swapped (vulnerable = no bond_header_ops, fixed = has bond_header_ops from 950803f7).
  • Structured artifacts: bundle/vuln_variant/variant_manifest.json, bundle/vuln_variant/validation_verdict.json, bundle/vuln_variant/runtime_manifest.json, bundle/vuln_variant/root_cause_equivalence.json, bundle/vuln_variant/source_identity.json, bundle/vuln_variant/patch_analysis.md.

Recommendations / Next Steps

  • No additional security fix is required. The upstream bond_header_ops wrapper is generic: it delegates the active slave's header_ops->create to the slave's own device, so netdev_priv() receives the correct struct (struct ip_tunnel for ipgre, struct ip6_tnl for ip6gre). The ip6gre alternate sink is covered; the variant does not reproduce on the fixed kernel. The fix is complete for the disclosed class of header_ops->create-based type confusion from non-Ethernet slaves.
  • Ruled-out candidates (no fix gap):
    • ipip / sit / ip_vti / ip6_vti / ip6_tunnel / xfrm slaves use ip_tunnel_header_ops = { .parse_protocol = ip_tunnel_parse_protocol }, which has no .create and whose .parse_protocol does not dereference netdev_priv(). Not type-confusion triggers; the fix also NULLs parse_protocol on the bond.
    • .cache / .cache_update / .validate / .parse_protocol of non-Ethernet slaves: the GRE tunnel header_ops do not define these, and the fix NULLs them on the bond regardless; all callers are NULL-safe.
  • Advisory (functional, not security): because bond_header_ops wraps only .create and .parse, a non-Ethernet bond loses .cache/.cache_update/ .validate/.parse_protocol (header caching, validation). If full functional parity for non-Ethernet bonds is desired, those callbacks could be wrapped analogously (bond_header_cache delegating to the active slave's .cache with the slave device). This is independent of CVE-2026-43456 and does not affect the type-confusion fix.
  • Coding-stage guidance: when shipping the fix, ensure the full bond_header_ops wrapper (create + parse) is present and that bond_setup_by_slave() substitutes &bond_header_ops for the slave's header_ops. No change is needed to ip6_gre.c or ip_gre.c.

Additional Notes

  • Bypass vs alternate trigger: This is an alternate trigger (distinct sink ip6gre_header / struct ip6_tnl), not a bypass — the fix covers it on the fixed kernel. The original repro missed this sink only because of a misconfiguration (it used a remote address on the ip6gre tunnel, which prevents ip6gre_header_ops from being assigned).
  • Idempotency: bundle/vuln_variant/reproduction_steps.sh was run three times end-to-end; each run rebuilt the rootfs images, booted both kernels, and produced identical analysis (VARIANT_ON_VULN=true, VARIANT_ON_FIXED=false, FIX_COVERS=true, exit 1). The script reuses all cached build artifacts (bzImage, vulnerable/fixed bonding.ko, module stage, rootfs base, populate_hlen6.ko) and only recompiles/refreshes the proof artifacts (/init, populate_hlen6.ko, rootfs images) when missing.
  • Limitations: The confused hlen value (0x961a63cc) is written by the out-of-tree populate_hlen6.ko helper to emulate a layout where the bonding memory overlapping ip6_tnl.hlen (offset 264) holds a sign-bit-set value — exactly analogous to the repro stage's populate_hlen.ko for the ipgre case. This preserves the real AF_PACKET → dev_hard_header → ip6gre_header → pskb_expand_head boundary; only the confused-field value is set. The DoS is a privileged-local-user (CAP_NET_ADMIN) kernel crash, matching the CVE's Medium severity.

CVE-2026-43456 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:000:50
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-43456 · REPRO-20
0:02
0:04
web search
0:05
0:13
0:13
extract_facts
no facts extracted
0:14
0:14
0:14
supportrepro
0:28
0:30
0:30
0:30
0:31
0:31
0:31
0:31
0:32
0:32
0:32
0:37
0:37
0:37
$uname -a; echo "---"; nproc; echo "---"; free -h 2>/dev/null || cat /proc/meminfo | head -3; echo "---"; which qemu-system-x86_64 kvm gcc make bc flex bison 2>/dev/null; echo "---QEMU---"; ls /dev/kvm 2>/dev/null && echo "KVM available" || echo "no KVM"; echo "---tools---"; which busybox cpio xz gzip 2>/dev/null; echo "---libssl---"; dpkg -l libssl-dev libelf-dev 2>/dev/null | tail -5
Linux 70a156dd6cfb 7.0.14-arch1-1 #1 SMP PREEMPT_DYNAMIC Sat, 27 Jun 2026 16:15:10 +0000 x86_64 GNU/Linux
---
32
---
               total        used        free      shared  buff/cache   available
Mem:            46Gi        14Gi       769Mi       2.9Gi        35Gi        32Gi
Swap:          511Mi       479Mi        32Mi
---
0:40
0:40
$echo "=== kernels dir ==="; ls -la /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/ 2>/dev/null; echo "=== .pruva dir ==="; find /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/ -maxdepth 4 2>/dev/null; echo "=== repo HEAD ==="; git -C /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/repo log --oneline -3 2>/dev/null; echo "=== repo describe ==="; git -C /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/repo describe --tags 2>/dev/null; echo "=== rootfs-noble ==="; ls /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/rootfs-noble/ 2>/dev/null | head; echo "=== qemu check ==="; ls /usr/bin/qemu* 2>/dev/null; which qemu-system-x86_64 2>/dev/null; ls /usr/local/bin/qemu* 2>/dev/null; echo "=== flex bison cpio ==="; which flex bison cpio busybox 2>/dev/null; echo "done"
{"cwd":"/data/pruva/runs/1b67fc45-76d0-4de7-8a54-e70b21d873e7","duration_ms":16,"exit_code":0,"stderr":"","stdout":"=== kernels dir ===\ntotal 0\ndrwxr-xr-x 1 vscode vscode  26 Jul  2 21:09 .\ndrwxr-xr-x 1 vscode vscode 310 Jul  2 21:46 ..\ndrwxr-xr-x 1 vscode vscode 470 Jul  2 21:40 v7.0.10\ndrwxr-xr-x 1 vscode vscode 462 Jul  2 21:40 v7.0.9\n=== .pruva dir ===\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_attempt\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_attempt/repro\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_attempt/repro/reproduction_steps.sh\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_attempt/repro/runtime_manifest.json\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_attempt/repro/validation_verdict.json\n/data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/.pruva/proof-carry/latest_attempt/… [truncated]
0:45
0:45
$echo "=== kernels v7.0.9 ==="; ls -la /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/v7.0.9/; echo "=== kernels v7.0.10 ==="; ls -la /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/kernels/v7.0.10/; echo "=== repo status ==="; cd /data/pruva/project-cache/6cba72b6-b104-4ebf-be7b-01f748e87ccf/repo && git status 2>&1 | head -5; echo "=== repo log ==="; git log --oneline -5 2>&1; echo "=== repo branches ==="; git branch -a 2>&1 | head; echo "=== repo HEAD rev ==="; git rev-parse HEAD 2>&1
{"cwd":"/data/pruva/runs/1b67fc45-76d0-4de7-8a54-e70b21d873e7","duration_ms":13,"exit_code":128,"stderr":"","stdout":"=== kernels v7.0.9 ===\ntotal 1083020\ndrwxr-xr-x 1 vscode vscode       462 Jul  2 21:40 .\ndrwxr-xr-x 1 vscode vscode        26 Jul  2 21:09 ..\n-rw-r--r-- 1 vscode vscode 399024128 Jul  2 21:40 fragnesia_rootfs.img\n-rw------- 1 vscode vscode  17252864 Jul  2 21:40 fragnesia_vmlinuz\ndrwxr-xr-x 1 vscode vscode        14 May 19 15:04 image\n-rw-r--r-- 1 vscode vscode 219428016 Jul  2 21:12 initramfs.cpio.zst\n-rw-r--r-- 1 vscode vscode  17285312 Jul  2 21:09 linux-image-unsigned-7.0.9-070009-generic_7.0.9-070009.202605191504_amd64.deb\n-rw-r--r-- 1 vscode vscode 168898752 Jul  2 21:09 linux-modules-7.0.9-070009-generic_7.0.9-070009.202605191504_amd64.deb\ndrwxr-xr-x 1 vscode vscode        14 May 19 15:04 modules\n-rw-r--r-- 1 vscode vscode 398151680 Jul  2 21:27 rootfs.img\n-rw------- 1 vscode vscode  17252864 Jul  2 21:27 vmlinuz\n=== kernels v7.0.10 ===\ntotal 1083336\ndrwxr-xr-x 1 vscode vscode       470 Jul  2 21:40 .\ndrwxr-xr-x 1 vscode vscode        26 Jul  2 21:09 ..\n-rw-r--r-- 1 vscode vscode 399106048 Jul  2 21:40 fragnesia_rootfs.img\n-rw------- 1 vscod… [truncated]
0:45
0:50
08 · How to Fix

How to Fix CVE-2026-43456

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-43456 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-43456

How does the CVE-2026-43456 kernel crash occur?

ipgre_header() computes needed = t->hlen + sizeof(*iph) using the confused struct bonding memory as if it were struct ip_tunnel. When the reinterpreted t->hlen has its sign bit set, needed overflows to a negative int; the unsigned skb_headroom(skb) < needed comparison is satisfied, pskb_expand_head() is called with a negative nhead, and BUG_ON(nhead < 0) panics the kernel — triggerable via an AF_PACKET SOCK_DGRAM send on the bond device.

Which Linux kernel versions are affected by CVE-2026-43456?

Per third-party reports, mainline kernels 5.10 through 6.6 and stable releases without the backported fix are affected — specifically kernels containing the commit that introduced the header_ops inheritance regression (1284cd3a2b74).

How severe is CVE-2026-43456?

It is rated high severity: an unprivileged-reachable kernel panic (denial of service) on hosts where a non-Ethernet device such as GRE can be enslaved to a bond interface.

How can I reproduce CVE-2026-43456?

Download the verified script from this page and run it in an isolated environment against an affected kernel. It enslaves a GRE tunnel device to a bonding interface and sends an AF_PACKET SOCK_DGRAM packet through the bond, triggering the type-confused ipgre_header() path and the resulting kernel panic.
11 · References

References for CVE-2026-43456

Authoritative sources for CVE-2026-43456 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.