Files
bfe-core1106-sdk/qemu/tests/seed-dir.sh
T
NoahandClaude Fable 5.1 2b6e8a2098 qemu and build: review fixes across the rig driver, boot script, and fetch helpers
Bounded waits and validated arguments in run.sh and ui-drive.sh, a seeded
settings directory and root-only staged rootfs permissions with their own
tests, qmp.py and imgtools.py hardening, the fetch scripts checking what they
download, and ASCII typography throughout. Each fix carries its test under
qemu/tests or tests/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
2026-09-09 19:17:54 -06:00

191 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Regression test for mkimage.sh's SEED_DIR hook (see its Env note and the
# block right after --state is applied): a caller-supplied directory of
# pre-built userdata/warden files, copied in whole and applied AFTER --state
# so a seeded file can override a same-named --state value, after each entry
# is validated the same way --state's own KEY=VALUE is validated -- a
# symlink, a non-plain-file entry (a subdirectory included), or a name
# outside [A-Za-z0-9_.-]+ fails closed before cp -a runs. Nothing else in
# the qemu test suite ever sets SEED_DIR -- the CI qemu-tools job runs
# mkimage.sh unseeded, and only ui-drive.sh --seed exercises this path, and
# only when booting a real VM with a flare-edge checkout on hand -- so this
# is the only offline coverage of it.
#
# Builds a real disk image the same way mkimage.sh always does (unprivileged
# mkfs.ext4 -d), then reads the userdata partition back with debugfs -R
# (read-only, no mount or loop device needed) to check what actually landed
# on disk rather than trusting the script's own log output.
#
# bash seed-dir.sh
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # qemu/tests/
QEMU_DIR="$(cd "$HERE/.." && pwd)" # qemu/
FAIL=0
pass() { printf '[PASS] %s\n' "$1"; }
fail() { printf '[FAIL] %s\n' "$1"; FAIL=1; }
# mkfs.ext4 and debugfs both live in sbin, which user shells on Debian don't
# put on PATH -- same fix mkimage.sh itself applies.
PATH="$PATH:/usr/sbin:/sbin"
command -v debugfs >/dev/null || {
echo "FATAL: debugfs (e2fsprogs) not found: needed to read the userdata partition back" >&2
exit 1
}
# shellcheck source=../lib.sh disable=SC1091
. "$QEMU_DIR/lib.sh"
# shellcheck source=../blkdevparts.conf disable=SC1091
. "$QEMU_DIR/blkdevparts.conf"
SCRATCH="$(mktemp -d "${TMPDIR:-/tmp}/warden-qemu-seed-dir.XXXXXX")"
trap 'rm -rf "$SCRATCH"' EXIT
# userdata's byte offset/size come from the same blkdevparts string
# mkimage.sh itself parses, not a hardcoded number: a future layout change
# doesn't strand this test.
USERDATA_OFF=""
USERDATA_SIZE=""
capture_userdata() { [ "$1" = userdata ] && { USERDATA_OFF="$2"; USERDATA_SIZE="$3"; }; return 0; }
qemu_each_partition capture_userdata
[ -n "$USERDATA_OFF" ] || { echo "FATAL: no 'userdata' entry in blkdevparts.conf" >&2; exit 1; }
# extract_userdata DISK OUTFILE: pull the userdata partition window out of a
# built disk image. Sparse output so an otherwise near-empty 1G partition
# costs kilobytes of scratch space, not a real gigabyte, per scenario.
extract_userdata() {
dd if="$1" of="$2" bs=4096 skip=$((USERDATA_OFF / 4096)) \
count=$((USERDATA_SIZE / 4096)) conv=sparse status=none
}
# Reuse an already-verified busybox (read-only) so this test stays offline
# wherever a prior build has already produced one; only a checkout that has
# never run mkimage.sh falls back to the same fetch+verify mkimage.sh always
# does, once, shared by every scenario below.
BUSYBOX_BIN="$QEMU_DIR/out/busybox-armv7l"
if [ ! -f "$BUSYBOX_BIN" ]; then
OUT="$SCRATCH" qemu_get_busybox
BUSYBOX_BIN="$BB"
fi
# --- scenario 1: seed applied verbatim, secret mode preserved, seed beats a same-named --state ---
SEED="$SCRATCH/seed"
mkdir -p "$SEED"
printf 'plain-value\n' > "$SEED/plain.key"
printf 'secret-value\n' > "$SEED/secret.key"
chmod 0600 "$SEED/secret.key"
printf 'seeded-value\n' > "$SEED/override.key"
OUT1="$SCRATCH/out1"
if OUT="$OUT1" BUSYBOX="$BUSYBOX_BIN" SEED_DIR="$SEED" \
bash "$QEMU_DIR/mkimage.sh" --state "override.key=state-value" \
> "$SCRATCH/mkimage1.log" 2>&1; then
UIMG="$SCRATCH/userdata1.img"
extract_userdata "$OUT1/disk.img" "$UIMG"
plain_stat="$(debugfs -R "stat /warden/plain.key" "$UIMG" 2>/dev/null)"
if [ -n "$plain_stat" ]; then
pass "SEED_DIR: plain.key landed under userdata/warden"
else
fail "SEED_DIR: plain.key missing from userdata/warden"
fi
secret_stat="$(debugfs -R "stat /warden/secret.key" "$UIMG" 2>/dev/null)"
secret_mode="$(printf '%s' "$secret_stat" | grep -oE 'Mode: *[0-7]+' | grep -oE '[0-7]+$')"
if [ "$secret_mode" = "0600" ]; then
pass "SEED_DIR: secret.key kept mode 0600 through cp -a"
else
fail "SEED_DIR: secret.key mode '$secret_mode', want 0600"
fi
override_content="$(debugfs -R "cat /warden/override.key" "$UIMG" 2>/dev/null)"
if [ "$override_content" = "seeded-value" ]; then
pass "SEED_DIR: seeded override.key beats the same-named --state value"
else
fail "SEED_DIR: override.key = '$override_content', want 'seeded-value' (seed must apply after --state)"
fi
else
fail "SEED_DIR: mkimage.sh exited nonzero with a valid seed dir (see $SCRATCH/mkimage1.log)"
fi
# --- scenario 2: SEED_DIR that is not a directory fails closed ---
NOTADIR="$SCRATCH/notadir"
: > "$NOTADIR"
OUT2="$SCRATCH/out2"
err2="$(OUT="$OUT2" BUSYBOX="$BUSYBOX_BIN" SEED_DIR="$NOTADIR" \
bash "$QEMU_DIR/mkimage.sh" 2>&1 1>/dev/null)"
rc2=$?
if [ "$rc2" -ne 0 ] && printf '%s' "$err2" | grep -qF "FATAL: SEED_DIR '$NOTADIR' is not a directory"; then
pass "SEED_DIR: a non-directory path fails closed with the FATAL message"
else
fail "SEED_DIR: non-directory path gave rc=$rc2, stderr='$err2' (want nonzero + the FATAL message)"
fi
# --- scenario 3: a symlink entry fails closed instead of being copied verbatim ---
SEED3="$SCRATCH/seed3"
mkdir -p "$SEED3"
printf 'plain-value\n' > "$SEED3/plain.key"
ln -s /etc/passwd "$SEED3/evil.key"
OUT3="$SCRATCH/out3"
err3="$(OUT="$OUT3" BUSYBOX="$BUSYBOX_BIN" SEED_DIR="$SEED3" \
bash "$QEMU_DIR/mkimage.sh" 2>&1 1>/dev/null)"
rc3=$?
if [ "$rc3" -ne 0 ] && printf '%s' "$err3" | grep -qF "FATAL: SEED_DIR entry 'evil.key' is a symlink"; then
pass "SEED_DIR: a symlink entry fails closed instead of being copied verbatim"
else
fail "SEED_DIR: symlink entry gave rc=$rc3, stderr='$err3' (want nonzero + the symlink FATAL message)"
fi
[ -e "$OUT3/disk.img" ] && fail "SEED_DIR: a disk image was written despite the symlink entry"
# --- scenario 4: an entry with a character outside [A-Za-z0-9_.-]+ fails closed ---
SEED4="$SCRATCH/seed4"
mkdir -p "$SEED4"
printf 'x\n' > "$SEED4/bad key"
OUT4="$SCRATCH/out4"
err4="$(OUT="$OUT4" BUSYBOX="$BUSYBOX_BIN" SEED_DIR="$SEED4" \
bash "$QEMU_DIR/mkimage.sh" 2>&1 1>/dev/null)"
rc4=$?
if [ "$rc4" -ne 0 ] && printf '%s' "$err4" | grep -qF "FATAL: SEED_DIR entry 'bad key' must match [A-Za-z0-9_.-]+"; then
pass "SEED_DIR: an entry name outside [A-Za-z0-9_.-]+ fails closed"
else
fail "SEED_DIR: bad-name entry gave rc=$rc4, stderr='$err4' (want nonzero + the charset FATAL message)"
fi
# --- scenario 5: a subdirectory entry fails closed (not a plain file) ---
SEED5="$SCRATCH/seed5"
mkdir -p "$SEED5/subdir"
printf 'x\n' > "$SEED5/subdir/leaf.key"
OUT5="$SCRATCH/out5"
err5="$(OUT="$OUT5" BUSYBOX="$BUSYBOX_BIN" SEED_DIR="$SEED5" \
bash "$QEMU_DIR/mkimage.sh" 2>&1 1>/dev/null)"
rc5=$?
if [ "$rc5" -ne 0 ] && printf '%s' "$err5" | grep -qF "FATAL: SEED_DIR entry 'subdir' is not a plain file"; then
pass "SEED_DIR: a subdirectory entry fails closed instead of being recursed into"
else
fail "SEED_DIR: subdirectory entry gave rc=$rc5, stderr='$err5' (want nonzero + the plain-file FATAL message)"
fi
# --- scenario 6: a hyphenated key (seed-fixtures.py's KEY_RE, e.g.
# "gas-plant.devices") still seeds cleanly -- guards against tightening the
# charset to --state's stricter [A-Za-z0-9_.]+ by mistake, which would
# reject keys committed flow specs already seed through this path ---
SEED6="$SCRATCH/seed6"
mkdir -p "$SEED6"
printf 'r5\n' > "$SEED6/gas-plant.devices"
OUT6="$SCRATCH/out6"
if OUT="$OUT6" BUSYBOX="$BUSYBOX_BIN" SEED_DIR="$SEED6" \
bash "$QEMU_DIR/mkimage.sh" > "$SCRATCH/mkimage6.log" 2>&1; then
UIMG6="$SCRATCH/userdata6.img"
extract_userdata "$OUT6/disk.img" "$UIMG6"
hyphen_content="$(debugfs -R "cat /warden/gas-plant.devices" "$UIMG6" 2>/dev/null)"
if [ "$hyphen_content" = "r5" ]; then
pass "SEED_DIR: a hyphenated key (gas-plant.devices) still seeds cleanly"
else
fail "SEED_DIR: gas-plant.devices = '$hyphen_content', want 'r5'"
fi
else
fail "SEED_DIR: mkimage.sh rejected a valid hyphenated key (see $SCRATCH/mkimage6.log)"
fi
[ "$FAIL" -eq 0 ] && echo "ALL SEED_DIR TESTS PASSED" || echo "SEED_DIR TESTS FAILED"
[ "$FAIL" -eq 0 ]