#!/usr/bin/env bash
# create-image-userns.sh
#
# Build a syzkaller-compatible Debian VM image in environments where syzkaller's
# own tools/create-image.sh cannot run -- specifically inside a user namespace
# (e.g. rootless / userns-remapped Docker), where the kernel forbids mknod of
# device nodes regardless of capabilities. That blocks debootstrap, and loop
# mounting needs CAP_SYS_ADMIN + loop devices that also may not be available.
#
# This produces the SAME artifacts as tools/create-image.sh:
#     <output>.img            ext4 root disk image
#     <output>.id_rsa         ssh private key (point sshkey at this)
#     <output>.id_rsa.pub     ssh public key
#
# How it differs from tools/create-image.sh:
#   - rootfs  : mmdebstrap --mode=root  (never calls mknod) instead of debootstrap
#   - packing : mkfs.ext4 -d            (populates the image from a dir; no loop mount)
# The guest kernel populates /dev at boot via devtmpfs (CONFIG_DEVTMPFS_MOUNT=y),
# so the absence of static /dev nodes in the rootfs is harmless.
#
# Usage:
#   .devcontainer/create-image-userns.sh                 # defaults: bookworm, ~2GB
#   RELEASE=trixie .devcontainer/create-image-userns.sh
#   OUTPUT=myimg SEEK=4095 .devcontainer/create-image-userns.sh
set -eux

RELEASE="${RELEASE:-bookworm}"
OUTPUT="${OUTPUT:-$RELEASE}"
ARCH="${ARCH:-amd64}"
MIRROR="${MIRROR:-http://deb.debian.org/debian}"
SEEK="${SEEK:-2047}"   # image size in MB minus one (matches create-image.sh)

# Package set. ifupdown + isc-dhcp-client bring eth0 up via /etc/network/interfaces
# (how syzkaller VMs get networking); udev/kmod for device setup; the rest mirror
# create-image.sh's minimal set (ssh in, run the executor). Add more via ADD_PACKAGE.
PKGS="openssh-server,ifupdown,isc-dhcp-client,udev,kmod,curl,tar,gcc,libc6-dev,time,strace,less,psmisc,ca-certificates,dmsetup"
if [ -n "${ADD_PACKAGE:-}" ]; then
    PKGS="$PKGS,$ADD_PACKAGE"
fi

DIR="$OUTPUT"
rm -rf "$DIR" "$DIR.img"
mkdir -p "$DIR"

# 1. Build the rootfs without mknod. --mode=root uses a real chroot (we are root
#    in this userns) but, unlike debootstrap, never creates device nodes; it
#    bind-mounts the host /dev during the build for maintainer scripts and leaves
#    the image's /dev empty.
#    --variant=important mirrors debootstrap's default: it pulls in an init system
#    (systemd) so the image is actually bootable, plus priority-important packages.
# Prefer an explicit Debian keyring so this works even when the host distro
# ships a stale debian-archive-keyring (common on Ubuntu).
KEYRING="${KEYRING:-/usr/share/keyrings/debian-archive-keyring.gpg}"
KEYRING_ARGS=()
if [ -f "$KEYRING" ]; then
    KEYRING_ARGS=(--keyring="$KEYRING")
fi

mmdebstrap \
    --mode=root \
    --variant=important \
    --arch="$ARCH" \
    --include="$PKGS" \
    --components=main,contrib,non-free,non-free-firmware \
    "${KEYRING_ARGS[@]}" \
    "$RELEASE" "$DIR" "$MIRROR"

# 2. Customizations -- identical in effect to tools/create-image.sh.
# Passwordless root.
sed -i '/^root/ { s/:x:/::/ }' "$DIR/etc/passwd"
# Serial console login.
echo 'T0:23:respawn:/sbin/getty -L ttyS0 115200 vt100' >> "$DIR/etc/inittab"
# DHCP on eth0.
mkdir -p "$DIR/etc/network"
printf '\nauto eth0\niface eth0 inet dhcp\n' >> "$DIR/etc/network/interfaces"
# fstab: root + optional debug/security/config mounts.
# Use /dev/sda (not /dev/root): QEMU boots with root=/dev/sda.
# nofail: a minimal defconfig kernel may lack CONFIGFS/SECURITYFS; without
# nofail, systemd fails local-fs.target and drops into emergency mode (SSH never starts).
{
    echo '/dev/sda / ext4 defaults 0 0'
    echo 'debugfs /sys/kernel/debug debugfs defaults,nofail 0 0'
    echo 'securityfs /sys/kernel/security securityfs defaults,nofail 0 0'
    echo 'configfs /sys/kernel/config configfs defaults,nofail 0 0'
    echo 'binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc defaults,nofail 0 0'
} >> "$DIR/etc/fstab"
# Mask systemd's built-in configfs mount unit — it still fails boot when the
# kernel lacks CONFIG_CONFIGFS_FS (fstab nofail does not cover this unit).
# Also mask remount-fs: QEMU -snapshot + bare cmdline `rw` already mounts
# root RW; remount-fs often exits 1 and can still force emergency mode.
mkdir -p "$DIR/etc/systemd/system"
ln -sf /dev/null "$DIR/etc/systemd/system/sys-kernel-config.mount"
ln -sf /dev/null "$DIR/etc/systemd/system/systemd-remount-fs.service"
printf '127.0.0.1\tlocalhost\n' > "$DIR/etc/hosts"
echo 'nameserver 8.8.8.8' > "$DIR/etc/resolv.conf"
echo 'syzkaller' > "$DIR/etc/hostname"

# Allow root login over ssh (key-based; root has an empty password above).
echo 'PermitRootLogin yes' >> "$DIR/etc/ssh/sshd_config"

# SSH key + authorized_keys for passwordless root login from syz-manager.
rm -f "$DIR.id_rsa" "$DIR.id_rsa.pub"
ssh-keygen -f "$DIR.id_rsa" -t rsa -N ''
mkdir -p "$DIR/root/.ssh/"
cp "$DIR.id_rsa.pub" "$DIR/root/.ssh/authorized_keys"

# udev rule for the vim2m test driver (matches upstream).
mkdir -p "$DIR/etc/udev/rules.d"
echo 'ATTR{name}=="vim2m", SYMLINK+="vim2m"' >> "$DIR/etc/udev/rules.d/50-udev-default.rules"

# Pin the primary NIC to eth0 so the name is stable regardless of whether the
# kernel uses predictable naming (enp0s4) or legacy naming (eth0).  This is the
# single source of truth for the interface name -- the /etc/network/interfaces
# entry below and the syzkaller VM cmdline both depend on it.
mkdir -p "$DIR/etc/systemd/network"
cat > "$DIR/etc/systemd/network/10-eth0.link" <<'LINK'
[Match]
OriginalName=enp0s4 eth0

[Link]
Name=eth0
LINK

# 3. Pack into an ext4 image directly from the directory -- no loop mount needed.
dd if=/dev/zero of="$DIR.img" bs=1M seek="$SEEK" count=1
mkfs.ext4 -F -d "$DIR" "$DIR.img"

set +x
echo
echo "Created:"
echo "  $(pwd)/$DIR.img"
echo "  $(pwd)/$DIR.id_rsa"
echo "  $(pwd)/$DIR.id_rsa.pub"
echo
echo "syzkaller vm config (copy into my.cfg):"
echo '  "vm": {'
echo '    "count":  1,'
echo '    "kernel": "<path>/bzImage",'
echo '    "image":  "'"$(pwd)/$DIR.img"'",'
echo '    "sshkey": "'"$(pwd)/$DIR.id_rsa"'",'
echo '    "cpu":    2,'
echo '    "mem":    2048'
echo '  }'
echo
echo "NOTE: The image uses a systemd .link rule to pin the NIC to eth0."
echo "      If your kernel does not support systemd .link rules, add to vm config:"
echo '        "cmdline": "net.ifnames=0 biosdevname=0"'
