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
This commit is contained in:
Noah
2026-09-09 19:17:54 -06:00
co-authored by Claude Fable 5.1
parent bda6c6c633
commit 2b6e8a2098
24 changed files with 1823 additions and 137 deletions
+13 -1
View File
@@ -12,6 +12,14 @@
# Same shape as fetch-kernel-tarball.sh, deliberately: a version bump edits this
# file and the pin beside it, nothing else. FAILS CLOSED on a missing pin.
#
# Not wired into build-firmware.sh or CI yet -- tools/build-firmware.sh stages
# sdk-patches/buildroot/ onto whatever buildroot tree the vendor SDK already
# has, it does not yet extract this pinned tarball over it. Exercised today by
# tests/fetch-buildroot-tarball/run-fetch-buildroot-tarball-tests.sh and by
# running the script by hand; wiring it into the real build path is a separate
# change (it has to reconcile the pristine tree with the existing SDK buildroot
# checkout first).
#
# Usage: fetch-buildroot-tarball.sh <destination-path>
set -euo pipefail
@@ -43,7 +51,11 @@ fi
for attempt in 1 2 3; do
echo "== fetching buildroot-$BRVER (attempt $attempt)"
if curl -fsSL --retry 2 -o "$TB" "$URL" && verify; then
# --retry only re-fires once curl decides a transfer has failed; a connection
# that opens and then stalls (blackholed route, hung proxy) never reaches
# that decision and would otherwise block forever. --connect-timeout bounds
# the handshake, --max-time bounds the whole request.
if curl -fsSL --retry 2 --connect-timeout 20 --max-time 120 -o "$TB" "$URL" && verify; then
echo "buildroot-$BRVER: sha256 verified"
exit 0
fi
+18 -1
View File
@@ -26,7 +26,11 @@ while [ $# -gt 0 ]; do
case "$1" in
--check) MODE="check"; shift ;;
--fetch) MODE="fetch"; shift ;;
-h|--help) sed -n '2,18p' "$0"; exit 0 ;;
# Print the header comment (line 1 is the shebang, so start at 2) and
# stop at the first line of code rather than a hardcoded line count --
# a fixed range silently starts printing code again the next time the
# header comment grows or shrinks.
-h|--help) awk '/^set /{exit} NR>1{print}' "$0"; exit 0 ;;
*) DIR="$1"; shift ;;
esac
done
@@ -34,6 +38,19 @@ done
[ -r "$MANIFEST" ] || { echo "FATAL: no manifest at $MANIFEST" >&2; exit 1; }
command -v git >/dev/null || { echo "FATAL: git not on PATH" >&2; exit 1; }
# A stalled clone (dead peer, wedged proxy) must not hang this script forever
# with no way for a caller to tell "still working" from "wedged" -- the
# luckfox-pico tree alone is ~21 GB, so a plain wall-clock timeout would also
# abort a clone that is merely slow. Abort only on a stall instead: git's http
# transport already aborts a transfer whose average speed drops below
# LOW_SPEED_LIMIT bytes/sec for LOW_SPEED_TIME seconds straight, so a slow but
# progressing clone is never penalized. Both are overridable for a link that
# is legitimately slow.
: "${WARDEN_VENDOR_LOW_SPEED_LIMIT:=1000}"
: "${WARDEN_VENDOR_LOW_SPEED_TIME:=60}"
export GIT_HTTP_LOW_SPEED_LIMIT="$WARDEN_VENDOR_LOW_SPEED_LIMIT"
export GIT_HTTP_LOW_SPEED_TIME="$WARDEN_VENDOR_LOW_SPEED_TIME"
if [ -z "$DIR" ]; then
DIR="${WARDEN_VENDOR_DIR:-$HOME/projects/scada/flare-edge}"
fi
+25 -13
View File
@@ -42,7 +42,12 @@ while [ $# -gt 0 ]; do
# displays it on an ordinary boot.
--logo-verbose) LOGO_VERBOSE="${2:?}"; shift 2 ;;
--resource-tool) RTOOL="${2:?}"; shift 2 ;;
-h|--help) sed -n '2,25p' "$0"; exit 0 ;;
# Print the header comment (line 1 is the shebang, so start at 2) and
# stop at the first line of code rather than a hardcoded line count --
# a fixed range silently starts printing code again the next time the
# header comment grows or shrinks (see build/fetch-vendor.sh's own
# --help, which had this exact bug).
-h|--help) awk '/^set /{exit} NR>1{print}' "$0"; exit 0 ;;
*) echo "FATAL: unknown argument '$1'" >&2; exit 1 ;;
esac
done
@@ -52,6 +57,11 @@ done
[ -n "$OUT" ] || { echo "FATAL: --out is required" >&2; exit 1; }
command -v mkimage >/dev/null || {
echo "FATAL: mkimage not on PATH (Debian/Ubuntu: u-boot-tools)" >&2; exit 1; }
# fdtget backs the post-build alignment assertions below (the whole point of
# which is that a misaligned image boots fine in CI and fails on a panel), so
# its absence must fail the build rather than silently skip those checks.
command -v fdtget >/dev/null || {
echo "FATAL: fdtget not on PATH (Debian/Ubuntu: device-tree-compiler)" >&2; exit 1; }
# resource_tool is a Rockchip host tool. It has no free-standing source here, so
# it is taken from the vendor SDK when one is present rather than vendored as a
@@ -160,11 +170,6 @@ ITS
# vendor's -p value -- the absolute position of the first payload -- not an
# alignment.
#
# The flag is feature-detected because the SDK vendors mkimage 2017.09,
# which has no -B at all and dies with "invalid option -- 'B'". Its packer
# already 512-aligns, so omitting the flag there is correct rather than a
# fallback. project/build.sh prepends the SDK tool dir to PATH, so that
# binary IS what a build inside the SDK environment resolves.
# A -B-capable mkimage is REQUIRED, not preferred. The SDK vendors 2017.09,
# which has no -B, and project/build.sh:64 puts it first on PATH -- so the
# wrong one is what a build inside the SDK environment picks up. Measured:
@@ -198,16 +203,24 @@ echo "== FIT (external data, -E -p 0x800 -B 0x200) using $MKIMAGE"
# Assert what U-Boot actually requires, on every build: the failure is silent --
# a misread offset does not fail the build, it fails on a panel, and sometimes
# only as a missing logo.
_meta="$(od -An -tu4 -j4 -N4 --endian=big "$WORKDIR/boot.img" | tr -d ' ')"
if [ $(( _meta % 512 )) -ne 0 ]; then
echo "FATAL: FIT metadata is $_meta bytes, not a multiple of 512;" >&2
# only as a missing logo. Computed once and reused below (the embedded-data-FIT
# check further down needs the same value) so a future fix to how this is read
# cannot land in one check and not the other.
meta="$(od -An -tu4 -j4 -N4 --endian=big "$WORKDIR/boot.img" | tr -d ' ')"
if [ $(( meta % 512 )) -ne 0 ]; then
echo "FATAL: FIT metadata is $meta bytes, not a multiple of 512;" >&2
echo " FIT_ALIGN would round it up and every payload reads late" >&2
exit 1
fi
for _n in fdt kernel resource; do
_pos="$(fdtget -t u "$WORKDIR/boot.img" "/images/$_n" data-position 2>/dev/null || true)"
[ -n "$_pos" ] || continue
# fdtget's presence is checked up front; a failure here means the FIT this
# script just built is malformed, not that the field is legitimately
# absent (mkimage -E gives every one of these images a data-position). Fail
# loud rather than treat an empty read as nothing to check.
if ! _pos="$(fdtget -t u "$WORKDIR/boot.img" "/images/$_n" data-position 2>&1)"; then
echo "FATAL: fdtget could not read /images/$_n data-position: $_pos" >&2
exit 1
fi
if [ $(( _pos % 512 )) -ne 0 ]; then
echo "FATAL: /images/$_n data-position $_pos is not 512-aligned;" >&2
echo " U-Boot's truncating block divide would read the wrong offset" >&2
@@ -218,7 +231,6 @@ done
# A FIT whose metadata swelled to the size of the whole image is an
# embedded-data build, which this U-Boot rejects. Catch it here rather than on
# a panel that will not come back.
meta="$(od -An -tu4 -j4 -N4 --endian=big "$WORKDIR/boot.img" | tr -d ' ')"
total="$(stat -c %s "$WORKDIR/boot.img")"
if [ "${meta:-0}" -ge 4096 ] || [ "${meta:-0}" -ge "$total" ]; then
echo "FATAL: FIT metadata is ${meta} bytes of a ${total}-byte image: that is an" >&2
+8 -1
View File
@@ -1351,7 +1351,14 @@ CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_NETFILTER_XT_NAT=y
CONFIG_NETFILTER_XT_TARGET_MASQUERADE=y
CONFIG_NF_DEFRAG_IPV4=y
CONFIG_NF_CONNTRACK_IPV4=y
# No NF_CONNTRACK_IPV4 symbol here -- IPv4 conntrack has been unconditional in
# NF_CONNTRACK's core since well before 6.18 (net/ipv4/netfilter/Kconfig has no
# such config). Only stale arch defconfigs (e.g. keystone_defconfig) still set
# it; Kconfig drops an unknown symbol with no warning, so it looked live but
# did nothing. Left out on purpose so this file does not claim a gate that
# does not exist. If a future kernel bump reintroduces a real symbol by this
# name, catch it by diffing the expanded .config, not by functional test alone
# -- see PORT-STATUS.md's own history of a silent-drop hiding a real gap.
CONFIG_IP_NF_IPTABLES=y
# 6.18 SPLIT THE LEGACY TABLES OUT. IP_NF_FILTER and IP_NF_NAT depend on
# IP_NF_IPTABLES_LEGACY, which did not exist in 5.10 -- so copying the vendor