#!/bin/bash
# kbuild.sh - build the Linux 6.12.111 control kernels used by the
# ZDI-CAN-33367 reproduction.
#
# Produces, in $OUT_DIR:
#   bzImage.vuln   - 6.12.111 as published (skb_zerocopy() does NOT propagate
#                    SKBFL_SHARED_FRAG)  == the vulnerable product
#   bzImage.fixed  - the same tree with the upstream review patch
#                    "net: skbuff: propagate shared-frag marker in skb_zerocopy()"
#                    applied (skb_shinfo(to)->flags |= skb_shinfo(from)->flags & SKBFL_SHARED_FRAG)
#   config         - the exact .config used for both builds
#
# The two kernels come from the same tree, the same .config and the same
# compiler; the only difference is the two-line marker propagation.
#
# Usage: kbuild.sh <out_dir> [jobs]
set -uo pipefail

OUT_DIR="${1:?usage: kbuild.sh <out_dir> [jobs]}"
JOBS="${2:-$(nproc)}"
KVER="6.12.111"
TARBALL_URL="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${KVER}.tar.xz"
TARBALL_SHA256="9e59dc67624188fa12a6601f9598499cd6662a9066be572b59f935e3d7849810"
DOCKER="${DOCKER_BIN:-docker}"
CNAME="zdi33367-kbuilder"
LOG="${KBUILD_LOG:-$OUT_DIR/kbuild.log}"

mkdir -p "$OUT_DIR"
: > "$LOG"
say() { printf '%s\n' "$*" | tee -a "$LOG"; }

if [ -s "$OUT_DIR/bzImage.vuln" ] && [ -s "$OUT_DIR/bzImage.fixed" ]; then
	say "[*] reusing cached kernels in $OUT_DIR"
	exit 0
fi

command -v "$DOCKER" >/dev/null 2>&1 || { say "[FATAL] docker required"; exit 2; }

# ---------------------------------------------------------------- tarball
if [ ! -f "$OUT_DIR/linux-${KVER}.tar.xz" ]; then
	say "[*] downloading $TARBALL_URL"
	curl -sSL --max-time 1800 -o "$OUT_DIR/linux-${KVER}.tar.xz" "$TARBALL_URL" || exit 2
fi
got="$(sha256sum "$OUT_DIR/linux-${KVER}.tar.xz" | cut -d' ' -f1)"
say "[*] tarball sha256 $got"
if [ "$got" != "$TARBALL_SHA256" ]; then
	say "[FATAL] kernel tarball digest mismatch"
	exit 2
fi

# ---------------------------------------------------------------- container
"$DOCKER" rm -f "$CNAME" >/dev/null 2>&1 || true
"$DOCKER" run -d --name "$CNAME" -v "$OUT_DIR":/b debian:trixie-slim sleep infinity >>"$LOG" 2>&1 \
	|| { say "[FATAL] cannot start build container"; exit 2; }
trap '"$DOCKER" rm -f "$CNAME" >/dev/null 2>&1 || true' EXIT

"$DOCKER" exec "$CNAME" bash -c \
	'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq build-essential bc libelf-dev libssl-dev flex bison xz-utils cpio python3 kmod' \
	>>"$LOG" 2>&1 || { say "[FATAL] toolchain install failed"; exit 2; }

# ---------------------------------------------------------------- build
"$DOCKER" exec "$CNAME" bash -c "
set -e
cd /b
rm -rf linux-${KVER} fixed-src vuln-src
mkdir fixed-src vuln-src
tar xf linux-${KVER}.tar.xz
mv linux-${KVER} fixed-src/linux
cp -a fixed-src/linux vuln-src/linux

conf() {
  cd \"\$1\"
  make defconfig >/dev/null 2>&1
  scripts/config \
    --enable NLMON --enable NFT_QUEUE --enable NETFILTER_NETLINK_QUEUE \
    --enable NETFILTER_XT_TARGET_NFQUEUE --enable IP_NF_IPTABLES --enable IP_NF_FILTER \
    --enable NETFILTER --enable NETFILTER_ADVANCED --enable NF_TABLES \
    --enable NF_TABLES_IPV4 --enable NF_TABLES_INET --enable NF_TABLES_NETDEV \
    --enable NET_CLS_U32 --enable NET_CLS_MATCHALL --enable NET_SCH_INGRESS \
    --enable NET_ACT_PEDIT --enable NET_ACT_MIRRED --enable NET_SCHED --enable NET_CLS_ACT \
    --enable INET_ESP --enable XFRM_USER --enable INET_AH \
    --enable USER_NS --enable NET_NS \
    --enable CRYPTO_AES --enable CRYPTO_CBC --enable CRYPTO_HMAC --enable CRYPTO_SHA256 \
    --enable CRYPTO_AUTHENC --enable CRYPTO_AEAD --enable CRYPTO_SEQIV --enable CRYPTO_MANAGER \
    --enable DUMMY --enable VETH --enable TUN \
    --enable VIRTIO_PCI --enable VIRTIO_NET --enable VIRTIO_BLK --enable EXT4_FS \
    --enable DEVTMPFS --enable DEVTMPFS_MOUNT --enable TMPFS \
    --enable BLK_DEV_INITRD --enable RD_GZIP \
    --enable SERIAL_8250 --enable SERIAL_8250_CONSOLE \
    --enable UNIX --enable PACKET --enable INET_DIAG --enable NETLINK_DIAG \
    --enable IKCONFIG --enable IKCONFIG_PROC
  make olddefconfig >/dev/null 2>&1
  grep -E '^CONFIG_(NLMON|NFT_QUEUE|NETFILTER_NETLINK_QUEUE|NET_CLS_MATCHALL|NET_CLS_U32|NET_ACT_PEDIT|NET_ACT_MIRRED|NET_SCH_INGRESS|INET_ESP|XFRM_USER|USER_NS|NET_NS|VIRTIO_BLK|EXT4_FS|CRYPTO_AUTHENC|CRYPTO_CBC|CRYPTO_HMAC|CRYPTO_SHA256)=' .config
}

# ---- fixed tree: apply the upstream review patch (2 lines)
python3 - <<'PY'
p='fixed-src/linux/net/core/skbuff.c'
s=open(p).read()
old='\tskb_shinfo(to)->nr_frags = j;\n\n\treturn 0;\n}\nEXPORT_SYMBOL_GPL(skb_zerocopy);'
new='\tskb_shinfo(to)->nr_frags = j;\n\tskb_shinfo(to)->flags |= skb_shinfo(from)->flags & SKBFL_SHARED_FRAG;\n\n\treturn 0;\n}\nEXPORT_SYMBOL_GPL(skb_zerocopy);'
assert s.count(old)==1, 'skb_zerocopy anchor not found'
open(p,'w').write(s.replace(old,new))
print('PATCH: marker propagation inserted into skb_zerocopy()')
PY
grep -n 'skb_shinfo(to)->flags |= skb_shinfo(from)->flags' fixed-src/linux/net/core/skbuff.c

cd /b/fixed-src/linux && conf . && make -j${JOBS} bzImage
cp arch/x86/boot/bzImage /b/bzImage.fixed
cp .config /b/config
cd /b/vuln-src/linux && conf . && make -j${JOBS} bzImage
cp arch/x86/boot/bzImage /b/bzImage.vuln
# prove the two trees really differ only in the fix
diff -u /b/vuln-src/linux/net/core/skbuff.c /b/fixed-src/linux/net/core/skbuff.c > /b/skbuff.diff || true
" >>"$LOG" 2>&1 || { say "[FATAL] kernel build failed; see $LOG"; exit 2; }

say "[*] bzImage.vuln  sha256 $(sha256sum "$OUT_DIR/bzImage.vuln" | cut -d' ' -f1)"
say "[*] bzImage.fixed sha256 $(sha256sum "$OUT_DIR/bzImage.fixed" | cut -d' ' -f1)"
[ -s "$OUT_DIR/bzImage.vuln" ] && [ -s "$OUT_DIR/bzImage.fixed" ] || exit 2
exit 0
