{"repro_id":"REPRO-2026-00211","version":7,"title":"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.","repro_type":"security","status":"published","severity":"high","description":"The Linux kernel bonding driver copies `header_ops` from a slave device to the bond device. If the slave is a non‑Ethernet device (e.g., GRE), the bond later calls `dev_hard_header()` with `header_ops` that expect the slave’s `netdev_priv()` layout, but receive the bond’s private data instead, leading to type confusion and kernel crashes.","root_cause":"# CVE-2026-43456 — Root Cause Analysis\n\n## Summary\n\nCVE-2026-43456 is a type-confusion vulnerability in the Linux kernel bonding\ndriver that results in a **kernel denial of service** (BUG/panic). When a\n**non-Ethernet** device (e.g. a GRE tunnel) is enslaved to a bond,\n`bond_setup_by_slave()` copies the slave's `header_ops` pointer verbatim onto\nthe bond net device:\n\n```c\nbond_dev->header_ops = slave_dev->header_ops;\n```\n\nLater, when the network stack calls `dev_hard_header()` on the **bond** device\n(for example via an `AF_PACKET` SOCK_DGRAM send), the slave's header-creation\ncallback (`ipgre_header()`) runs with `dev = bond_dev`. That callback\ndereferences `netdev_priv(dev)` expecting a tunnel-specific private struct\n(`struct ip_tunnel`), but for a bond device `netdev_priv()` returns\n`struct bonding`. The bonding memory is therefore reinterpreted as the tunnel\nstruct — a classic type confusion. `ipgre_header()` computes\n`needed = t->hlen + sizeof(*iph)`; when the confused `t->hlen` (the `int` at\n`offsetof(struct ip_tunnel, hlen)` inside `struct bonding`) has its sign bit\nset, `needed` overflows to a negative `int`, the\n`skb_headroom(skb) < needed` test (an unsigned compare) is satisfied, and\n`pskb_expand_head()` is called with a negative `nhead`, hitting\n`BUG_ON(nhead < 0)` and panicking the kernel.\n\n## Impact\n\n- **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`).\n- **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`).\n- **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.\n- **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…`.\n\n## Impact Parity\n\n- **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).\n- **Reproduced impact from this run:** **Full DoS parity.** The vulnerable kernel panics with the *exact* crash signature from the reporter's Oops:\n  - `kernel BUG at net/core/skbuff.c:2306!`\n  - `Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI`\n  - `RIP: 0010:pskb_expand_head+0x59c/0x6d0`\n  - Call trace: `ipgre_header+0xf0/0x320 [ip_gre]` → `packet_sendmsg` (inlining `packet_snd`→`dev_hard_header`) → `__sys_sendto` → `__x64_sys_sendto` → `do_syscall_64` → `entry_SYSCALL_64_after_hwframe`\n  - `Kernel panic - not syncing: Fatal exception`\n  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`.\n- **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).\n- **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.\n\n## Root Cause\n\n`bond_setup_by_slave()` (drivers/net/bonding/bond_main.c) runs whenever a\n**non-Ethernet** slave is added to a bond (the `slave_dev->type != ARPHRD_ETHER`\nbranch of `bond_enslave`). It blindly copies the slave's `header_ops` onto the\nbond device:\n\n```c\n/* vulnerable (pre-fix), bond_main.c */\nbond_dev->header_ops    = slave_dev->header_ops;\nbond_dev->type          = slave_dev->type;\nbond_dev->hard_header_len = slave_dev->hard_header_len;\n...\n```\n\nGRE tunnels install `ipgre_header_ops` (with `.create = ipgre_header`). So\nafter `ip link set gre1 master bond1`, `bond1->header_ops == &ipgre_header_ops`.\n\n`ipgre_header()` (net/ipv4/ip_gre.c) does:\n\n```c\nstruct ip_tunnel *t = netdev_priv(dev);          // dev == bond1  =>  t points at struct bonding\n...\nint needed = t->hlen + sizeof(*iph);             // t->hlen is the int at offsetof(ip_tunnel,hlen)\nif (skb_headroom(skb) < needed &&                // unsigned int < int  =>  needed is converted to unsigned\n    pskb_expand_head(skb, HH_DATA_ALIGN(needed - skb_headroom(skb)), 0, GFP_ATOMIC))\n        return -needed;\n```\n\nCrash mechanics (this is exactly what the reporter hit):\n1. `t = netdev_priv(bond1)` returns `struct bonding`, reinterpreted as `struct ip_tunnel`.\n2. `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).\n3. `needed = 0x961a63cc + 20 = 0x961a63e0`, which as a signed `int` is **negative**.\n4. `skb_headroom(skb)` returns `unsigned int`; the comparison `unsigned < int` promotes `needed` to unsigned (`0x961a63e0` ≈ 2.5e9), so `headroom(160) < needed` is **true**.\n5. `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**.\n6. `pskb_expand_head()` begins with `BUG_ON(nhead < 0);` (net/core/skbuff.c:2306) → `BUG()` → Oops → panic.\n\nOn this reproduction build (Linux 7.0.0-rc2, x86_64 defconfig + KASAN),\n`offsetof(struct ip_tunnel, hlen) == 160`, which lands inside\n`struct bonding.ad_info` (`struct ad_bond_info`, offset 128) at\n`ad_bond_info.stats.lacpdu_illegal_rx` — a zeroed `atomic64_t` counter in\nactive-backup mode (the only mode that, combined with the layout, leaves this\nfield zero). With the field zero, `t->hlen == 0`, `needed == 20`, and the BUG is\nnever reached (the prior run proved the type confusion but not the crash). The\nreporter's kernel had a different `struct bonding`/`struct ip_tunnel` layout in\nwhich that same offset was occupied by a populated kernel pointer (sign bit set),\nwhich is what drove the BUG.\n\nTo faithfully reproduce the reporter's crash on this (different-layout) build, a\ntiny out-of-tree helper module (`populate_hlen.ko`) writes `0x961a63cc` — the\nexact confused value from the reporter's Oops — into\n`netdev_priv(bond1) + offsetof(struct ip_tunnel, hlen)` **after** `gre1` has been\nenslaved to `bond1` (so `bond1->header_ops` already points at\n`ipgre_header_ops`). This emulates a configuration in which the overlapping\nbonding field is populated with a sign-bit-set value, exactly as in the\nreporter's environment, while preserving the real\n`AF_PACKET -> dev_hard_header() -> ipgre_header()` boundary. The success oracle\nis the kernel `BUG()`/panic itself, not a KASAN shadow report (the KASAN build\nmatches the reporter's `SMP KASAN` environment, and the type-confused read is an\nin-bounds access to the valid `struct bonding` allocation, so KASAN does not\nintervene before the `BUG_ON`).\n\n**Fix commit:** https://git.kernel.org/stable/c/950803f7254721c1c15858fbbfae3deaaeeecb11\n\n## Reproduction Steps\n\n1. **Script:** `bundle/repro/reproduction_steps.sh` (self-contained; reuses the durable project cache for the kernel build).\n2. **What it does:**\n   - 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()`.\n   - Builds two `bonding.ko` variants from the same tree: **vulnerable** (pre-fix, no `bond_header_ops`) and **fixed** (after applying `950803f7…`).\n   - Builds an out-of-tree helper module `populate_hlen.ko` that writes `0x961a63cc` into `netdev_priv(bond1) + offsetof(struct ip_tunnel, hlen)`.\n   - Creates two rootfs images differing only in `bonding.ko` (vuln vs fixed); both contain `populate_hlen.ko`.\n   - 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`.\n   - 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`.\n3. **Expected evidence of reproduction:**\n   - 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).\n   - 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`.\n\n## Evidence\n\n- **Vulnerable VM serial log:** `bundle/logs/qemu_vuln_7rc2.log`\n- **Fixed VM serial log:** `bundle/logs/qemu_fixed_7rc2.log`\n- **Driver/build/run log:** `bundle/logs/reproduction_steps.log`\n- **Runtime manifest:** `bundle/repro/runtime_manifest.json`\n- **Verdict:** `bundle/repro/validation_verdict.json`\n\nKey excerpts (vulnerable kernel, `bundle/logs/qemu_vuln_7rc2.log`):\n\n```\n[   13.335216] CVE-2026-43456 POP: bond1 priv=ffff8881042f29c0 ip_tunnel.hlen offset=160 old=0x00000000(0) new=0x961a63cc(-1776655412)\n[init] TRIGGER: AF_PACKET SOCK_DGRAM sendto(bond1) -- vuln kernel should BUG/panic here\n[   13.496660] ip_gre: CVE-2026-43456 ipgre_header: dev=bond1 hlen=-1776655412 needed=-1776655392 headroom=160\n[   13.498036] kernel BUG at net/core/skbuff.c:2306!\n[   13.498841] Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI\n[   13.500660] RIP: 0010:pskb_expand_head+0x59c/0x6d0\n...\n[   13.502825] R14: 0000000000000820 R15: 00000000961a6340\n...\n[   13.504637] Call Trace:\n[   13.504846]  ipgre_header+0xf0/0x320 [ip_gre]\n[   13.505406]  packet_sendmsg+0x1dee/0x2450\n[   13.506368]  __sys_sendto+0x2bb/0x2d0\n[   13.507118]  __x64_sys_sendto+0x71/0x90\n[   13.507277]  do_syscall_64+0xe2/0x570\n[   13.507432]  entry_SYSCALL_64_after_hwframe+0x77/0x7f\n[   13.511080] Kernel panic - not syncing: Fatal exception\n```\n(`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.)\n\nReporter's Oops (from the fix commit message), for direct comparison:\n```\nkernel BUG at net/core/skbuff.c:2306!\nOops: invalid opcode: 0000 [#1] SMP KASAN NOPTI\nRIP: 0010:pskb_expand_head+0xa08/0xfe0 net/core/skbuff.c:2306\n...\nRBP: 0000000000000820 ... R15: 00000000961a63e0\nCall Trace:\n ipgre_header+0xdd/0x540 net/ipv4/ip_gre.c:900\n dev_hard_header include/linux/netdevice.h:3439 [inline]\n packet_snd net/packet/af_packet.c:3028 [inline]\n packet_sendmsg+0x3ae5/0x53c0 net/packet/af_packet.c:3108\n ...\n entry_SYSCALL_64_after_hwframe+0x77/0x7f\n```\n\nKey excerpts (fixed kernel, `bundle/logs/qemu_fixed_7rc2.log`):\n```\n[   13.861461] CVE-2026-43456 POP: bond1 priv=ffff8881039f49c0 ip_tunnel.hlen offset=160 old=0x00000000(0) new=0x961a63cc(-1776655412)\n[   14.004337] ip_gre: CVE-2026-43456 ipgre_header: dev=gre1 hlen=4 needed=24 headroom=160\n...\n[init] RESULT: NOT VULNERABLE (no kernel crash; fixed bond_header_ops used the slave device)\n```\nOn 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.\n\nEnvironment: 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.\n\n## Recommendations / Next Steps\n\n- **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.\n- **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.\n- **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.\n\n## Additional Notes\n\n- **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.\n- **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.\n- **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.\n- **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.\n","cve_id":"CVE-2026-43456","cwe_id":"CWE-908","source_url":"https://nvd.nist.gov/vuln/detail/CVE-2026-43456","package":{"name":"Linux kernel (bonding driver)","ecosystem":"linux","affected_versions":"Mainline kernels 5.10 through 6.6 and stable releases without the backported fix (per third-party reports)"},"reproduced_at":"2026-07-03T10:32:43.952396+00:00","duration_secs":3899.0,"tool_calls":347,"handoffs":3,"total_cost_usd":10.01652841,"agent_costs":{"hypothesis_generator":0.0165606,"judge":0.04535485,"repro":8.43752184,"support":0.04179879,"vuln_variant":1.4752923299999998},"cost_breakdown":{"hypothesis_generator":{"accounts/fireworks/models/glm-5p2":0.0165606},"judge":{"gpt-5.4-mini":0.04535485},"repro":{"accounts/fireworks/routers/glm-5p2-fast":8.43752184},"support":{"accounts/fireworks/routers/glm-5p2-fast":0.04179879},"vuln_variant":{"accounts/fireworks/routers/glm-5p2-fast":1.4752923299999998}},"quality":{"confidence":"high","idempotent_verified":false,"community_verifications":0},"environment":{"sandbox_image":"ghcr.io/n3mes1s/pruva-sandbox@sha256:8096b2518d6022e13d68f885c3b8ded6b4fe607098b1a1ccbfb99abc004d1dc1"},"published_at":"2026-07-03T10:32:45.038260+00:00","retracted":false,"artifacts":[{"path":"bundle/repro/reproduction_steps.sh","filename":"reproduction_steps.sh","size":30294,"category":"reproduction_script"},{"path":"bundle/repro/rca_report.md","filename":"rca_report.md","size":15752,"category":"analysis"},{"path":"bundle/vuln_variant/reproduction_steps.sh","filename":"reproduction_steps.sh","size":20528,"category":"reproduction_script"},{"path":"bundle/vuln_variant/rca_report.md","filename":"rca_report.md","size":17638,"category":"analysis"},{"path":"bundle/ticket.md","filename":"ticket.md","size":3028,"category":"ticket"},{"path":"bundle/ticket.json","filename":"ticket.json","size":4204,"category":"other"},{"path":"bundle/AGENTS.repro.md","filename":"AGENTS.repro.md","size":172,"category":"documentation"},{"path":"bundle/repro/validation_verdict.json","filename":"validation_verdict.json","size":1683,"category":"other"},{"path":"bundle/repro/runtime_manifest.json","filename":"runtime_manifest.json","size":1635,"category":"other"},{"path":"bundle/logs/qemu_vuln.log","filename":"qemu_vuln.log","size":46009,"category":"log"},{"path":"bundle/logs/qemu_vuln_mode1_single.log","filename":"qemu_vuln_mode1_single.log","size":37783,"category":"log"},{"path":"bundle/logs/qemu_vuln_ip6gre.log","filename":"qemu_vuln_ip6gre.log","size":36354,"category":"log"},{"path":"bundle/logs/qemu_vuln_ip6gre2.log","filename":"qemu_vuln_ip6gre2.log","size":37188,"category":"log"},{"path":"bundle/logs/qemu_vuln_7rc2.log","filename":"qemu_vuln_7rc2.log","size":35468,"category":"log"},{"path":"bundle/logs/qemu_fixed_7rc2.log","filename":"qemu_fixed_7rc2.log","size":37019,"category":"log"},{"path":"bundle/logs/qemu_probe_7rc2.log","filename":"qemu_probe_7rc2.log","size":27725,"category":"log"},{"path":"bundle/logs/reproduction_steps.log","filename":"reproduction_steps.log","size":17313,"category":"log"},{"path":"bundle/logs/repro_run.out","filename":"repro_run.out","size":1639,"category":"other"},{"path":"bundle/logs/repro_run2.out","filename":"repro_run2.out","size":1640,"category":"other"},{"path":"bundle/logs/qemu_var_vuln.log","filename":"qemu_var_vuln.log","size":36562,"category":"log"},{"path":"bundle/logs/qemu_var_fixed.log","filename":"qemu_var_fixed.log","size":37512,"category":"log"},{"path":"bundle/logs/vuln_variant_repro.log","filename":"vuln_variant_repro.log","size":10504,"category":"log"},{"path":"bundle/vuln_variant/populate_hlen6.c","filename":"populate_hlen6.c","size":2064,"category":"other"},{"path":"bundle/vuln_variant/bond_variant_init.c","filename":"bond_variant_init.c","size":5926,"category":"other"},{"path":"bundle/vuln_variant/bond_variant_init","filename":"bond_variant_init","size":837088,"category":"other"},{"path":"bundle/vuln_variant/build_and_boot.sh","filename":"build_and_boot.sh","size":3021,"category":"other"},{"path":"bundle/vuln_variant/validation_verdict.json","filename":"validation_verdict.json","size":3123,"category":"other"},{"path":"bundle/vuln_variant/patch_analysis.md","filename":"patch_analysis.md","size":14175,"category":"documentation"},{"path":"bundle/vuln_variant/source_identity.json","filename":"source_identity.json","size":2746,"category":"other"},{"path":"bundle/vuln_variant/root_cause_equivalence.json","filename":"root_cause_equivalence.json","size":4065,"category":"other"},{"path":"bundle/vuln_variant/runtime_manifest.json","filename":"runtime_manifest.json","size":4341,"category":"other"},{"path":"bundle/vuln_variant/variant_manifest.json","filename":"variant_manifest.json","size":6461,"category":"other"}]}