#!/bin/bash
# rootfs.sh - build the QEMU guest used by the ZDI-CAN-33367 reproduction.
#
# Produces in $OUT_DIR:
#   initrd.img   - Debian trixie based initramfs with iproute2/nftables/busybox,
#                  the static exploit binary and the /init proof driver
#   target.img   - 16 MiB ext4 image holding the read-only root-owned scratch
#                  file whose page cache the exploit corrupts
#   skbpoc       - the compiled static exploit binary (also kept for evidence)
#   rootme       - the compiled root-owned setuid binary used for the LPE proof
#
# Everything runs in a throw-away debian:trixie-slim container so the host only
# needs docker.
#
# Usage: rootfs.sh <out_dir> <bundle_repro_dir>
set -uo pipefail

OUT_DIR="${1:?usage: rootfs.sh <out_dir> <repro_dir>}"
REPRO_DIR="${2:?usage: rootfs.sh <out_dir> <repro_dir>}"
OUT_DIR="$(cd "$OUT_DIR" 2>/dev/null && pwd || echo "$OUT_DIR")"
REPRO_DIR="$(cd "$REPRO_DIR" 2>/dev/null && pwd || echo "$REPRO_DIR")"
DOCKER="${DOCKER_BIN:-docker}"
CNAME="zdi33367-rootfs"
ROOTFS="$OUT_DIR/rootfs"
LOG="${ROOTFS_LOG:-$OUT_DIR/rootfs.log}"

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

if [ -s "$OUT_DIR/initrd.img" ] && [ -s "$OUT_DIR/target.img" ]; then
	say "[*] reusing cached guest in $OUT_DIR"
	exit 0
fi

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

if "$DOCKER" inspect "$CNAME" >/dev/null 2>&1; then
	say "[*] reusing the existing guest build container"
	"$DOCKER" start "$CNAME" >/dev/null 2>&1 || true
else
	"$DOCKER" run -d --name "$CNAME" -v "$OUT_DIR":/out -v "$REPRO_DIR":/src debian:trixie-slim sleep infinity >>"$LOG" 2>&1 \
		|| { say "[FATAL] cannot start rootfs container"; exit 2; }
	"$DOCKER" exec "$CNAME" bash -c \
		'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
		 gcc libc6-dev libssl-dev iproute2 nftables busybox-static kmod e2fsprogs \
		 coreutils grep findutils tar gzip cpio util-linux' >>"$LOG" 2>&1 \
		|| { say "[FATAL] guest package install failed"; exit 2; }
fi
if [ -z "${ZDI_KEEP:-}" ]; then
	trap '"$DOCKER" rm -f "$CNAME" >/dev/null 2>&1 || true' EXIT
fi

"$DOCKER" exec "$CNAME" bash -c "
set -e
export DEBIAN_FRONTEND=noninteractive
mkdir -p /usr/local/bin
# ---- exploit binary (static: the guest has no dynamic loader path issues)
gcc -O2 -w -o /out/skbpoc /src/skbpoc.c -lcrypto
# ---- setuid root-owned escalation target
gcc -O2 -w -o /out/rootme /src/rootme.c
install -o root -g root -m 4755 /out/rootme /usr/local/bin/rootme
install -o root -g root -m 0755 /out/skbpoc /usr/local/bin/skbpoc
# ---- unprivileged attacker account
grep -q '^tester:' /etc/passwd || echo 'tester:x:1000:1000:tester:/home/tester:/bin/sh' >> /etc/passwd
grep -q '^tester:' /etc/group  || echo 'tester:x:1000:' >> /etc/group
mkdir -p /home/tester && chown 1000:1000 /home/tester
# ---- target ext4 image (created empty; /init fills it in the guest)
rm -f /out/target.img
dd if=/dev/zero of=/out/target.img bs=1M count=16 status=none
mkfs.ext4 -q -F -L zdi33367 /out/target.img
# ---- /init
install -m 0755 /src/guest_init.sh /init
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
" >>"$LOG" 2>&1 || { say "[FATAL] guest build failed"; exit 2; }

# Build the initramfs from *inside* the container so that file ownership and
# the setuid bit of /usr/local/bin/rootme are preserved exactly.
"$DOCKER" exec "$CNAME" bash -c '
	set -e
	cd /
	find . -xdev -print0 | cpio --null -o -H newc 2>/dev/null | gzip -9
' > "$OUT_DIR/initrd.img" || { say "[FATAL] cpio failed"; exit 2; }

say "[*] initrd.img $(stat -c%s "$OUT_DIR/initrd.img") bytes sha256 $(sha256sum "$OUT_DIR/initrd.img" | cut -d' ' -f1)"
say "[*] target.img $(stat -c%s "$OUT_DIR/target.img") bytes sha256 $(sha256sum "$OUT_DIR/target.img" | cut -d' ' -f1)"
say "[*] skbpoc     sha256 $(sha256sum "$OUT_DIR/skbpoc" | cut -d' ' -f1)"
say "[*] rootme     sha256 $(sha256sum "$OUT_DIR/rootme" | cut -d' ' -f1)"
[ -s "$OUT_DIR/initrd.img" ] || exit 2
exit 0
