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.
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).
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 — serious impact or readily exploitable. Prioritize remediation.
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 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 Proof of Reproduction for CVE-2026-43456
- reached the target end-to-end
- crash observed
- on the real production code path
- high confidence
- the upstream fix blocks the same 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…
- 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
- header_ops=slave_dev
- 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
- packet_sendmsg
- packet_snd
- dev_hard_header(bond1) -…
reproduction_steps.sh 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
Root Cause and Exploit Chain for CVE-2026-43456
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 withnet/ipv4/ip_gre.c(ipgre_header) andnet/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 (commite3f5e0f22cfc…, the parent of the upstream fix950803f7254721c1c15858fbbfae3deaaeeecb11). - Risk level: Medium (CVE severity). Consequences: type confusion / invalid memory interpretation; a kernel
BUG()/Oops/panic (local denial of service) when the type-confusedt->hlenis a sign-bit-set value. - Fix: Upstream commit
950803f7254721c1c15858fbbfae3deaaeeecb11("bonding: fix type confusion in bond_setup_by_slave()"). It introducesbond_header_opswrapper functions that delegate to the active slave'sheader_opswhile passing the slave device, sonetdev_priv()insideipgre_header/ip6gre_headerreceives 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 viaipgre_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 NOPTIRIP: 0010:pskb_expand_head+0x59c/0x6d0- Call trace:
ipgre_header+0xf0/0x320 [ip_gre]→packet_sendmsg(inliningpacket_snd→dev_hard_header) →__sys_sendto→__x64_sys_sendto→do_syscall_64→entry_SYSCALL_64_after_hwframe Kernel panic - not syncing: Fatal exceptionThe confusedt->hlenwas0x961a63cc(taken verbatim from the reporter's Oops, wherepskb_expand_head'snheadregister was0x961a63e0); our run showsneeded=0x961a63e0(=t->hlen + 20), matching the reporter'snheadregister, andR14=0x820matching the reporter'sRBP=0x820.
- Parity:
full. The claimed kernel DoS (BUG/panic viapskb_expand_headreached throughipgre_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):
t = netdev_priv(bond1)returnsstruct bonding, reinterpreted asstruct ip_tunnel.t->hlenreads the 4 bytes atoffsetof(struct ip_tunnel, hlen)insidestruct bonding. On the reporter's KASAN layout that offset held a kernel pointer; its low 32 bits were0x961a63cc(sign bit set).needed = 0x961a63cc + 20 = 0x961a63e0, which as a signedintis negative.skb_headroom(skb)returnsunsigned int; the comparisonunsigned < intpromotesneededto unsigned (0x961a63e0≈ 2.5e9), soheadroom(160) < neededis true.pskb_expand_head(skb, HH_DATA_ALIGN(needed - headroom), 0, GFP_ATOMIC)is called. Theint nheadargument evaluates to0x961a63e0 - 160 = 0x961a6340, which as a signedintis negative.pskb_expand_head()begins withBUG_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
- Script:
bundle/repro/reproduction_steps.sh(self-contained; reuses the durable project cache for the kernel build). - What it does:
- Boots a real vulnerable Linux 7.0.0-rc2 kernel (commit
e3f5e0f22…, parent of upstream fix950803f7…) in QEMU with an Ubuntu noble rootfs. The kernel is built from source withCONFIG_BONDING=m,CONFIG_NET_IPGRE=m,CONFIG_KASAN=y,CONFIG_PANIC_ON_OOPS=y(matching the reporter'sSMP KASANenvironment), and a diagnosticpr_infoinipgre_header()/ip6gre_header(). - Builds two
bonding.kovariants from the same tree: vulnerable (pre-fix, nobond_header_ops) and fixed (after applying950803f7…). - Builds an out-of-tree helper module
populate_hlen.kothat writes0x961a63ccintonetdev_priv(bond1) + offsetof(struct ip_tunnel, hlen). - Creates two rootfs images differing only in
bonding.ko(vuln vs fixed); both containpopulate_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, assignsfe80::1/64tobond1, theninsmod populate_hlen.ko, then firesAF_PACKET SOCK_DGRAM sendtoonbond1. - Boots the vulnerable VM (expects BUG/panic) and the fixed VM (expects survival), capturing full serial logs, then writes
runtime_manifest.jsonandvalidation_verdict.json.
- Boots a real vulnerable Linux 7.0.0-rc2 kernel (commit
- Expected evidence of reproduction:
- Vulnerable kernel:
POP: bond1 ... new=0x961a63cc, thenipgre_header: dev=bond1 hlen=-1776655412 needed=-1776655392 headroom=160, thenkernel BUG at net/core/skbuff.c:2306!/Oops: invalid opcode ... SMP KASAN NOPTI/RIP: 0010:pskb_expand_head/Kernel panic. The init never reaches itsRESULT:line (it crashed first). - Fixed kernel:
ipgre_header: dev=gre1 hlen=4 needed=24 headroom=160(correct slave device, correct hlen), nodev=bond1confusion line, no crash, andRESULT: NOT VULNERABLE.
- Vulnerable kernel:
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 backports6ac890f1d60a…,95597d11dc8b…,9baf26a91565…) so the bond usesbond_header_opswrappers that pass the active slave device to the slave'sheader_opscallbacks. - Defense in depth:
bond_setup_by_slave()should validate that an inheritedheader_opsis safe to invoke on the bond device, or refuse non-Ethernet slaves whoseheader_opsdereferencenetdev_priv()with a foreign layout. Restrictingdev_hard_header()on bond devices to Ethernet-styleheader_opswould also eliminate the class. - Testing: Add a kselftest that enslaves a GRE/ip6gre tunnel to an active-backup bond and sends an
AF_PACKETframe on the bond, asserting that the slave'sheader_opscallback is invoked with the slave device (not the bond) on fixed kernels and that noBUG()/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 identicalBUG at net/core/skbuff.c:2306signature and each fixed run survives withRESULT: NOT VULNERABLE— the result is deterministic. - Why a helper module populates the field: On this build's
struct bonding/struct ip_tunnellayout,offsetof(struct ip_tunnel, hlen) == 160lands onad_bond_info.stats.lacpdu_illegal_rx, a zeroed counter in active-backup mode, so without interventiont->hlen == 0and theBUG_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 realAF_PACKET -> dev_hard_header() -> ipgre_header()boundary. The crash is a genuine kernelBUG()/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 KASANOops. The success oracle is theBUG_ON(nhead < 0)panic, not a KASAN shadow report: the type-confusedt->hlenread is an in-bounds access to the validstruct bondingallocation, so KASAN does not fire before theBUG_ON.sanitizer_usedis thereforefalsein 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
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 ofbond_dev->header_opsfor the non-Ethernet case (verified by grep acrossdrivers/net/bonding/). It now setsbond_dev->header_ops = slave_dev->header_ops ? &bond_header_ops : NULL.dev_hard_header()→ops->create(wrapped) anddev_parse_header()→ops->parse(wrapped), for any non-Ethernet slave whoseheader_opshas a.create— includingipgre_headerandip6gre_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; copiesslave_dev->header_opsontobond_devon 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);thenpskb_expand_head().net/ipv6/ip6_gre.c::ip6gre_tunnel_init()(lines ~1525-1533) — assignsdev->header_ops = &ip6gre_header_opsonly whenipv6_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 drivesdev_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 withnet/ipv6/ip6_gre.c(ip6gre_header) andnet/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 by950803f7254721c1c15858fbbfae3deaaeeecb11(and its stable backports6ac890f1d60a,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-confusedt->hlenhas its sign bit set. Trust boundary: a local user withCAP_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 viapskb_expand_headreached throughdev_hard_header→ a GRE*_headertype-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 inpskb_expand_head()as the original ipgre path, via the distinctip6gre_headersink, on the same vulnerable kernel. - Not demonstrated: no read/write primitive or code execution was
demonstrated; only the disclosed DoS (kernel panic). The confused
hlenis read-only exploited to drive a negativeneededintopskb_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_sendmsg → packet_snd → dev_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
generic — bond_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
- Script:
bundle/vuln_variant/reproduction_steps.sh(idempotent; reuses the kernel/rootfs cache built by the repro stage). - What it does:
- Verifies the prepared cache (
bzImage, vulnerable/fixedbonding.ko, module stage, rootfs base) built by the repro stage. - Builds
populate_hlen6.koagainst the cached kernel tree (writes0x961a63ccintonetdev_priv(bond1).ip6_tnl.hlen). - Compiles a static in-VM
/init(bond_variant_init) that sets up ip6gre(no-remote) → bond and firesAF_PACKET SOCK_DGRAMonbond1. - 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.
- Verifies the prepared cache (
- Expected evidence (and what was observed):
- Vulnerable:
ip6gre_header: dev=bond1 hlen=-1776655412 needed=-1776655372 headroom=288→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. The init never reaches itsRESULT: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.
- Vulnerable:
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):
The[ 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 exceptiondev=bond1(notdev=ip6gre1) and the read ofip6_tnl.hlenat offset 264 insidestruct bondingprove the type confusion; the negativeneededdrivespskb_expand_head()intoBUG_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_opsdelegatedip6gre_headerto the slaveip6gre1device (dev=ip6gre1, correctstruct ip6_tnl,hlen=4); thepopulate_hlen6write tobond1's private data was harmless (never read asip6_tnl). No crash. - Environment: QEMU
qemu-system-x86_64(TCG, 4 vCPU, 4 GB), Linux 7.0.0-rc2 (commite3f5e0f22,#1 SMP PREEMPT_DYNAMIC, KASAN generic,oops=panic). Identical bzImage for both runs; onlybonding.koswapped (vulnerable = nobond_header_ops, fixed = hasbond_header_opsfrom950803f7). - 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_opswrapper is generic: it delegates the active slave'sheader_ops->createto the slave's own device, sonetdev_priv()receives the correct struct (struct ip_tunnelfor ipgre,struct ip6_tnlfor ip6gre). The ip6gre alternate sink is covered; the variant does not reproduce on the fixed kernel. The fix is complete for the disclosed class ofheader_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.createand whose.parse_protocoldoes not dereferencenetdev_priv(). Not type-confusion triggers; the fix also NULLsparse_protocolon the bond. .cache/.cache_update/.validate/.parse_protocolof non-Ethernet slaves: the GRE tunnelheader_opsdo not define these, and the fix NULLs them on the bond regardless; all callers are NULL-safe.
- ipip / sit / ip_vti / ip6_vti / ip6_tunnel / xfrm slaves use
- Advisory (functional, not security): because
bond_header_opswraps only.createand.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_cachedelegating to the active slave's.cachewith 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_opswrapper (create + parse) is present and thatbond_setup_by_slave()substitutes&bond_header_opsfor the slave'sheader_ops. No change is needed toip6_gre.corip_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 preventsip6gre_header_opsfrom being assigned). - Idempotency:
bundle/vuln_variant/reproduction_steps.shwas 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/fixedbonding.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
hlenvalue (0x961a63cc) is written by the out-of-treepopulate_hlen6.kohelper to emulate a layout where the bonding memory overlappingip6_tnl.hlen(offset 264) holds a sign-bit-set value — exactly analogous to the repro stage'spopulate_hlen.kofor the ipgre case. This preserves the realAF_PACKET → dev_hard_header → ip6gre_header → pskb_expand_headboundary; 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.
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 -5Linux 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
---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]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]Artifacts and Evidence for CVE-2026-43456
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-43456
FAQ: CVE-2026-43456
How does the CVE-2026-43456 kernel crash occur?
Which Linux kernel versions are affected by CVE-2026-43456?
How severe is CVE-2026-43456?
How can I reproduce CVE-2026-43456?
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.