etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

setup.sh (12830B)

      1 #!/bin/bash
      2 # setup.sh — one-shot bootstrap for the linux-usb-stack syzkaller fuzzing environment.
      3 #
      4 # What this does (in order):
      5 #   1. Install system dependencies
      6 #   2. Install Go (from upstream if system Go < 1.21)
      7 #   3. Clone + build syzkaller
      8 #   4. Download + configure + build Linux mainline kernel
      9 #   5. Build static busybox
     10 #   6. Build static dropbear (SSH server for syzkaller VM access)
     11 #   7. Generate SSH key pair for syzkaller ↔ VM communication
     12 #   8. Assemble initramfs (busybox + dropbear + authorized_keys + /init)
     13 #   9. Create workdirs for each fuzzing profile
     14 #
     15 # Re-running is safe: each step checks for existing artifacts and skips.
     16 #
     17 # Usage:
     18 #   bash setup.sh
     19 #   bash setup.sh --no-kernel      # skip kernel build (use existing bzImage)
     20 #   bash setup.sh --no-syzkaller   # skip syzkaller build
     21 #   bash setup.sh --skip-deps      # skip apt-get installs
     22 
     23 set -euo pipefail
     24 
     25 SCRIPT_DIR="$(cd "$(dirname "${0}")" && pwd)"
     26 cd "${SCRIPT_DIR}"
     27 
     28 BUSYBOX_VER="${BUSYBOX_VER:-1.36.1}"
     29 DROPBEAR_VER="${DROPBEAR_VER:-2024.86}"
     30 GO_MIN_VER="1.21"
     31 GO_INSTALL_VER="${GO_INSTALL_VER:-1.22.4}"  # upstream Go version to install if needed
     32 SYZKALLER_REPO="https://github.com/google/syzkaller"
     33 LINUX_REPO="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git"
     34 
     35 SKIP_KERNEL=0
     36 SKIP_SYZKALLER=0
     37 SKIP_DEPS=0
     38 for arg in "$@"; do
     39     [[ "${arg}" == "--no-kernel"    ]] && SKIP_KERNEL=1
     40     [[ "${arg}" == "--no-syzkaller" ]] && SKIP_SYZKALLER=1
     41     [[ "${arg}" == "--skip-deps"    ]] && SKIP_DEPS=1
     42 done
     43 
     44 log()  { echo "[*] $*"; }
     45 ok()   { echo "[+] $*"; }
     46 warn() { echo "[!] $*"; }
     47 
     48 # ── 1. System dependencies ───────────────────────────────────────────────────
     49 if [[ "${SKIP_DEPS}" -eq 1 ]]; then
     50     ok "Skipping dependency install (--skip-deps)."
     51 else
     52     log "Installing system dependencies..."
     53     sudo apt-get update -qq
     54     sudo apt-get install -y \
     55         build-essential git curl wget python3 python3-pip tmux \
     56         libelf-dev libssl-dev flex bison bc cpio \
     57         libncurses-dev libncurses5-dev \
     58         pkg-config autoconf automake libtool \
     59         qemu-system-x86 \
     60         gcc-multilib \
     61         openssh-client \
     62         zlib1g-dev \
     63         gdb
     64     ok "System dependencies installed."
     65 fi
     66 
     67 # ── 2. Go ────────────────────────────────────────────────────────────────────
     68 # syzkaller requires Go >= 1.21. Check system Go; install upstream if too old.
     69 GO_BIN=""
     70 for cand in "$(go env GOROOT 2>/dev/null)/bin/go" "${SCRIPT_DIR}/go/bin/go" /usr/local/go/bin/go; do
     71     if [[ -x "${cand}" ]]; then
     72         ver="$("${cand}" version | awk '{print $3}' | sed 's/go//')"
     73         major="${ver%%.*}"
     74         minor="${ver#*.}"; minor="${minor%%.*}"
     75         req_minor="${GO_MIN_VER#*.}"
     76         if [[ "${major}" -gt 1 ]] || [[ "${major}" -eq 1 && "${minor}" -ge "${req_minor}" ]]; then
     77             GO_BIN="${cand}"
     78             break
     79         fi
     80     fi
     81 done
     82 
     83 if [[ -z "${GO_BIN}" ]]; then
     84     log "Go >= ${GO_MIN_VER} not found. Installing Go ${GO_INSTALL_VER} from upstream..."
     85     GO_ARCHIVE="go${GO_INSTALL_VER}.linux-amd64.tar.gz"
     86     GO_URL="https://golang.org/dl/${GO_ARCHIVE}"
     87     wget -c --timeout=60 --tries=3 "${GO_URL}" -O "${GO_ARCHIVE}"
     88     tar -C "${SCRIPT_DIR}" -xzf "${GO_ARCHIVE}"
     89     rm -f "${GO_ARCHIVE}"
     90     GO_BIN="${SCRIPT_DIR}/go/bin/go"
     91     ok "Go ${GO_INSTALL_VER} installed to ${SCRIPT_DIR}/go/"
     92 else
     93     ok "Go found: ${GO_BIN} ($(${GO_BIN} version | awk '{print $3}'))"
     94 fi
     95 
     96 export PATH="$(dirname "${GO_BIN}"):${PATH}"
     97 export GOPATH="${SCRIPT_DIR}/gopath"
     98 export GOROOT="$(dirname "$(dirname "${GO_BIN}")")"
     99 
    100 # ── 3. syzkaller ─────────────────────────────────────────────────────────────
    101 if [[ "${SKIP_SYZKALLER}" -eq 1 ]]; then
    102     ok "Skipping syzkaller build (--no-syzkaller)."
    103 elif [[ -x "${SCRIPT_DIR}/syzkaller/bin/linux_amd64/syz-manager" ]]; then
    104     ok "syzkaller already built, skipping."
    105 else
    106     log "Cloning syzkaller..."
    107     [[ -d "syzkaller" ]] || git clone --depth=1 "${SYZKALLER_REPO}" syzkaller
    108 
    109     log "Building syzkaller (this takes a few minutes)..."
    110     cd syzkaller
    111     make all -j"$(nproc)" 2>&1 | tail -5
    112     cd "${SCRIPT_DIR}"
    113     SYZ_MANAGER="$(find syzkaller/bin -name "syz-manager" -type f 2>/dev/null | head -1)"
    114     [[ -n "${SYZ_MANAGER}" ]] || { warn "syz-manager not found after build. Check build output above."; exit 1; }
    115     ok "syzkaller built: ${SYZ_MANAGER}"
    116 fi
    117 
    118 # ── 4. Linux kernel (mainline) ───────────────────────────────────────────────
    119 if [[ "${SKIP_KERNEL}" -eq 1 ]]; then
    120     warn "--no-kernel: skipping kernel build."
    121 elif [[ -f "bzImage" ]]; then
    122     ok "bzImage already exists, skipping kernel build."
    123 else
    124     if [[ ! -d "linux" ]]; then
    125         log "Cloning Linux mainline (shallow — latest tag only)..."
    126         git clone --depth=1 "${LINUX_REPO}" linux
    127     fi
    128 
    129     log "Configuring kernel (allnoconfig + USB stack fragment)..."
    130     cd linux
    131 
    132     # GCC 15+ defaults to C23, where 'bool'/'false' are keywords — patch compressed/Makefile
    133     if gcc --version 2>&1 | grep -qE "gcc \(.*\) (1[5-9]|[2-9][0-9])\."; then
    134         sed -i 's/^KBUILD_CFLAGS := /KBUILD_CFLAGS := -std=gnu11 /' \
    135             arch/x86/boot/compressed/Makefile
    136         warn "GCC 15+ detected: patched compressed/Makefile with -std=gnu11"
    137     fi
    138 
    139     make allnoconfig
    140     scripts/kconfig/merge_config.sh -m .config "${SCRIPT_DIR}/qemu/kernel.config"
    141     make olddefconfig
    142 
    143     log "Building kernel (this takes ~20-30 min on first run)..."
    144     make -j"$(nproc)" bzImage 2>&1 | tee /tmp/kbuild.log | \
    145         grep -E "^\s*(CC|LD|AR|error:|warning: )" || \
    146         { grep "error:" /tmp/kbuild.log | tail -20; exit 1; }
    147 
    148     cp arch/x86/boot/bzImage "${SCRIPT_DIR}/bzImage"
    149     cd "${SCRIPT_DIR}"
    150     ok "Kernel built: bzImage"
    151 fi
    152 
    153 # ── 5. Busybox (static) ──────────────────────────────────────────────────────
    154 BUSYBOX_DIR="busybox-${BUSYBOX_VER}"
    155 BUSYBOX_TAG="${BUSYBOX_VER//./_}"   # 1.36.1 → 1_36_1
    156 BUSYBOX_REPO="https://github.com/mirror/busybox"
    157 
    158 if [[ -d "busybox-install" ]]; then
    159     ok "Busybox already built, skipping."
    160 else
    161     if [[ ! -d "${BUSYBOX_DIR}" ]]; then
    162         log "Cloning busybox ${BUSYBOX_VER} (tag ${BUSYBOX_TAG})..."
    163         git clone --depth=1 --branch "${BUSYBOX_TAG}" "${BUSYBOX_REPO}" "${BUSYBOX_DIR}"
    164     fi
    165 
    166     log "Building static busybox..."
    167     cd "${BUSYBOX_DIR}"
    168     make defconfig
    169     sed -i 's/# CONFIG_STATIC is not set/CONFIG_STATIC=y/' .config
    170     sed -i 's/CONFIG_STATIC=n/CONFIG_STATIC=y/' .config
    171     echo "CONFIG_STATIC=y" >> .config
    172     sed -i 's/CONFIG_TC=y/CONFIG_TC=n/' .config
    173     echo "CONFIG_TC=n" >> .config
    174     make oldconfig < /dev/null || true
    175     make -j"$(nproc)"
    176     make install CONFIG_PREFIX="${SCRIPT_DIR}/busybox-install"
    177     cd "${SCRIPT_DIR}"
    178     ok "Busybox installed to busybox-install/"
    179 fi
    180 
    181 # ── 6. Dropbear (static SSH server) ──────────────────────────────────────────
    182 # syzkaller connects to VMs via SSH. We bake a static dropbear into the initramfs
    183 # so the VM runs a minimal SSH server without needing a full OS.
    184 DROPBEAR_DIR="dropbear-${DROPBEAR_VER}"
    185 DROPBEAR_TAR="${DROPBEAR_DIR}.tar.bz2"
    186 DROPBEAR_URL="https://matt.ucc.asn.au/dropbear/releases/${DROPBEAR_TAR}"
    187 
    188 if [[ -f "dropbear-install/sbin/dropbear" ]]; then
    189     ok "Dropbear already built, skipping."
    190 else
    191     if [[ ! -d "${DROPBEAR_DIR}" ]]; then
    192         log "Downloading dropbear ${DROPBEAR_VER}..."
    193         wget -c --timeout=30 --tries=2 "${DROPBEAR_URL}"
    194         tar -xjf "${DROPBEAR_TAR}"
    195         rm -f "${DROPBEAR_TAR}"
    196     fi
    197 
    198     log "Building static dropbear (SSH server + dropbearkey)..."
    199     cd "${DROPBEAR_DIR}"
    200     ./configure \
    201         --prefix="${SCRIPT_DIR}/dropbear-install" \
    202         --disable-syslog \
    203         --disable-pam \
    204         --disable-utmp \
    205         --disable-wtmp \
    206         --disable-lastlog \
    207         CFLAGS="-O2 -static" \
    208         LDFLAGS="-static"
    209 
    210     # Build only the server + key generator (not the client)
    211     make PROGRAMS="dropbear dropbearkey" -j"$(nproc)"
    212     mkdir -p "${SCRIPT_DIR}/dropbear-install/sbin"
    213     cp dropbear "${SCRIPT_DIR}/dropbear-install/sbin/dropbear"
    214     cp dropbearkey "${SCRIPT_DIR}/dropbear-install/sbin/dropbearkey"
    215     cd "${SCRIPT_DIR}"
    216     ok "Dropbear installed to dropbear-install/"
    217 fi
    218 
    219 # ── 7. SSH key pair ───────────────────────────────────────────────────────────
    220 # syz-manager uses qemu/id_rsa to SSH into VMs.
    221 # The matching public key is baked into initramfs as authorized_keys.
    222 if [[ -f "qemu/id_rsa" ]]; then
    223     ok "SSH key pair already exists."
    224 else
    225     log "Generating SSH key pair for syzkaller ↔ VM access..."
    226     ssh-keygen -t rsa -b 4096 -f qemu/id_rsa -N "" -C "syz-manager@usb-fuzz"
    227     ok "SSH keys: qemu/id_rsa (private), qemu/id_rsa.pub (public)"
    228 fi
    229 
    230 # ── 8. Initramfs ─────────────────────────────────────────────────────────────
    231 if [[ -f "qemu/initramfs.cpio.gz" ]]; then
    232     ok "qemu/initramfs.cpio.gz already exists, skipping."
    233 else
    234     log "Assembling initramfs..."
    235     rm -rf initramfs
    236     mkdir -p initramfs/{bin,sbin,usr/bin,usr/sbin,proc,sys,dev,tmp,run,lib,lib64,etc,etc/dropbear,root/.ssh}
    237 
    238     # Busybox — all applets as symlinks
    239     cp busybox-install/bin/busybox initramfs/bin/busybox
    240     (cd initramfs && bin/busybox --list | while read applet; do
    241         ln -sf /bin/busybox bin/"${applet}" 2>/dev/null || true
    242     done)
    243     ln -sf /bin/busybox initramfs/bin/sh
    244     ln -sf /bin/busybox initramfs/sbin/ip
    245     ln -sf /bin/busybox initramfs/sbin/modprobe
    246 
    247     # Dropbear (static, so no shared libraries needed)
    248     cp dropbear-install/sbin/dropbear    initramfs/sbin/dropbear
    249     cp dropbear-install/sbin/dropbearkey initramfs/sbin/dropbearkey
    250     chmod +x initramfs/sbin/dropbear initramfs/sbin/dropbearkey
    251 
    252     # Bake in the SSH public key so syzkaller can authenticate
    253     cp qemu/id_rsa.pub initramfs/root/.ssh/authorized_keys
    254     chmod 700 initramfs/root/.ssh
    255     chmod 600 initramfs/root/.ssh/authorized_keys
    256 
    257     # init script
    258     cp qemu/init initramfs/init
    259     chmod +x initramfs/init
    260 
    261     log "Packing initramfs..."
    262     (cd initramfs && find . | cpio -oH newc 2>/dev/null | gzip -9) > qemu/initramfs.cpio.gz
    263     ok "qemu/initramfs.cpio.gz: $(du -sh qemu/initramfs.cpio.gz | cut -f1)"
    264 fi
    265 
    266 # ── 9. Disk image (required by syzkaller's QEMU VM type) ─────────────────────
    267 # syzkaller validates that the image file exists even when booting from
    268 # kernel + initramfs. We create a minimal ext4 image; QEMU attaches it as
    269 # an unused second drive — the actual rootfs comes from the initramfs.
    270 if [[ -f "qemu/disk.img" ]]; then
    271     ok "qemu/disk.img already exists, skipping."
    272 else
    273     log "Creating minimal disk image (128M)..."
    274     dd if=/dev/zero of=qemu/disk.img bs=1M count=128 status=none
    275     mkfs.ext4 -q qemu/disk.img
    276     ok "qemu/disk.img created."
    277 fi
    278 
    279 # ── 10. Workdirs ──────────────────────────────────────────────────────────────
    280 for profile in raw-gadget gadget-fw usbfs; do
    281     mkdir -p "workdir-${profile}"
    282 done
    283 ok "Workdirs created: workdir-raw-gadget/ workdir-gadget-fw/ workdir-usbfs/"
    284 
    285 # ── Done ─────────────────────────────────────────────────────────────────────
    286 echo ""
    287 ok "Setup complete. Next steps:"
    288 echo "  1. Start fuzzing:    bash run-fuzzers.sh [--raw-gadget|--gadget-fw|--usbfs|--all]"
    289 echo "  2. Web UI:           http://127.0.0.1:56741  (raw-gadget profile)"
    290 echo "                       http://127.0.0.1:56742  (gadget-fw profile)"
    291 echo "                       http://127.0.0.1:56743  (usbfs profile)"
    292 echo "  3. Verify QEMU:      qemu-system-x86_64 -kernel bzImage -initrd qemu/initramfs.cpio.gz \\"
    293 echo "                         -nographic -append 'console=ttyS0 quiet' -m 2048M"
    294 echo "  4. Stop:             bash cleanup.sh"