refactor: enhance code quality and defensive programming

Security & Hash Validation Improvements (quickget):
- Fix command injection: replace echo with printf for ISO filename handling
- Add hash length validation (MD5=32, SHA1=40, SHA256=64, SHA512/BLAKE2b=64-128)
- Fix Bash 3.2 compatibility: replace ${var,,} with tr command for macOS
- Add internal whitespace validation in hash algorithm prefix
- Support BLAKE2b variable output lengths (256-512 bits)
- Validate archboot editions: only allow 'default' and 'latest'

CPU Flag Management Improvements (quickemu):
- Auto-correct invalid flag format by prepending comma
- Add conflict detection for unprefixed vs +/- prefixed flag variants
- Strip =value suffix to detect value-assigned flag conflicts
- Add validation for malformed flags (++flag, --flag, +, -)
- Add defensive check that CPU is initialized before appending

All changes improve security, robustness, and portability.
This commit is contained in:
startergo 2026-01-23 15:26:21 -05:00
parent 1c33335517
commit bb3f8ee817
2 changed files with 203 additions and 26 deletions

114
quickemu
View File

@ -318,6 +318,76 @@ function efi_vars() {
}
function configure_cpu() {
local add_cpu_flag
add_cpu_flag() {
local flag="${1}"
# Skip empty flags
[[ -z "${flag}" ]] && return
# Defensive check: ensure CPU is initialized before appending
if [[ -z "${CPU}" ]]; then
echo "ERROR: add_cpu_flag called before CPU variable initialization"
exit 1
fi
# Auto-correct flag format by prepending comma if missing
if [[ "${flag}" != ,* ]]; then
flag=",${flag}"
fi
local needle="${flag#,}"
# Validate flag is not malformed
if [[ "${needle}" =~ ^[+-]{2} ]] || [[ "${needle}" == "+" ]] || [[ "${needle}" == "-" ]]; then
echo "ERROR: Malformed CPU flag: '${needle}' (empty flag name or multiple prefix characters)"
exit 1
fi
# Check for exact duplicate
if [[ ",${CPU}," == *",${needle},"* ]]; then
return
fi
# Check for conflicting flag with opposite prefix (+/-) or unprefixed variant
# Strip both prefix (+/-) and value assignment (=...) to get the base flag name
local base_flag="${needle#+}"
base_flag="${base_flag#-}"
base_flag="${base_flag%%=*}"
if [[ "${needle}" == +* ]]; then
# Check if -flag or unprefixed flag exists
if [[ ",${CPU}," == *",-${base_flag}"* ]]; then
echo "WARNING: Conflicting CPU flags detected: -${base_flag} already set, ignoring +${base_flag}"
return
fi
if [[ ",${CPU}," == *",${base_flag}"[,=]* ]]; then
echo "WARNING: Conflicting CPU flags detected: ${base_flag} already set, ignoring +${base_flag}"
return
fi
elif [[ "${needle}" == -* ]]; then
# Check if +flag or unprefixed flag exists
if [[ ",${CPU}," == *",+${base_flag}"* ]]; then
echo "WARNING: Conflicting CPU flags detected: +${base_flag} already set, ignoring -${base_flag}"
return
fi
if [[ ",${CPU}," == *",${base_flag}"[,=]* ]]; then
echo "WARNING: Conflicting CPU flags detected: ${base_flag} already set, ignoring -${base_flag}"
return
fi
else
# Unprefixed flag - check if +flag or -flag exists
# Also check for conflicting value assignments (e.g., flag=on vs flag=off)
local needle_base="${needle%%=*}"
if [[ ",${CPU}," == *",+${needle_base}"* ]]; then
echo "WARNING: Conflicting CPU flags detected: +${needle_base} already set, ignoring ${needle}"
return
fi
if [[ ",${CPU}," == *",-${needle_base}"* ]]; then
echo "WARNING: Conflicting CPU flags detected: -${needle_base} already set, ignoring ${needle}"
return
fi
# Check for value assignment conflicts (e.g., flag=on already exists, trying to add flag=off)
if [[ "${needle}" == *=* ]] && [[ ",${CPU}," == *",${needle_base}="* ]]; then
echo "WARNING: Conflicting CPU flags detected: ${needle_base}=... already set, ignoring ${needle}"
return
fi
fi
CPU+="${flag}"
}
HOST_CPU_CORES=$(get_nproc)
HOST_CPU_MODEL=$(get_cpu_info '^Model name:')
HOST_CPU_SOCKETS=$(get_cpu_info 'Socket')
@ -429,11 +499,13 @@ function configure_cpu() {
if [ "${QEMU_ACCEL}" == "tcg" ]; then
# TCG can emulate these features
if [ "${HOST_CPU_VENDOR}" != "GenuineIntel" ] && [ -z "${HYPERVISOR}" ]; then
CPU+=",+avx2,+sse4.2"
add_cpu_flag ",+avx2"
add_cpu_flag ",+sse4.2"
fi
elif check_cpu_flag sse4_2 && check_cpu_flag avx2; then
if [ "${HOST_CPU_VENDOR}" != "GenuineIntel" ] && [ -z "${HYPERVISOR}" ]; then
CPU+=",+avx2,+sse4.2"
add_cpu_flag ",+avx2"
add_cpu_flag ",+sse4.2"
fi
else
echo "ERROR! macOS ${macos_release} requires a CPU with SSE 4.2 and AVX2 support."
@ -445,11 +517,11 @@ function configure_cpu() {
if [ "${QEMU_ACCEL}" == "tcg" ]; then
# TCG can emulate this feature
if [ "${HOST_CPU_VENDOR}" != "GenuineIntel" ] && [ -z "${HYPERVISOR}" ]; then
CPU+=",+sse4.2"
add_cpu_flag ",+sse4.2"
fi
elif check_cpu_flag sse4_2; then
if [ "${HOST_CPU_VENDOR}" != "GenuineIntel" ] && [ -z "${HYPERVISOR}" ]; then
CPU+=",+sse4.2"
add_cpu_flag ",+sse4.2"
fi
else
echo "ERROR! macOS ${macos_release} requires a CPU with SSE 4.2 support."
@ -460,11 +532,11 @@ function configure_cpu() {
if [ "${QEMU_ACCEL}" == "tcg" ]; then
# TCG can emulate this feature
if [ "${HOST_CPU_VENDOR}" != "GenuineIntel" ] && [ -z "${HYPERVISOR}" ]; then
CPU+=",+sse4.1"
add_cpu_flag ",+sse4.1"
fi
elif check_cpu_flag sse4_1; then
if [ "${HOST_CPU_VENDOR}" != "GenuineIntel" ] && [ -z "${HYPERVISOR}" ]; then
CPU+=",+sse4.1"
add_cpu_flag ",+sse4.1"
fi
else
echo "ERROR! macOS ${macos_release} requires a CPU with SSE 4.1 support."
@ -478,7 +550,9 @@ function configure_cpu() {
mca mce mmx movbe mpx msr mtrr nx pae pat pcid pge pse popcnt pse36 \
rdrand rdtscp sep smep syscall tsc tsc_adjust vaes vbmi2 vmx vpclmulqdq \
x2apic xgetbv1 xsave xsaveopt; do
CPU+=$(configure_cpu_flag "${FLAG}")
local cpu_flag="$(configure_cpu_flag "${FLAG}")"
# Only call add_cpu_flag if configure_cpu_flag returned a non-empty result
[[ -n "${cpu_flag}" ]] && add_cpu_flag "${cpu_flag}"
done
fi
@ -512,7 +586,7 @@ function configure_cpu() {
esac
if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ] && [ "${guest_os}" != "macos" ]; then
CPU+=",topoext"
add_cpu_flag ",+topoext"
fi
if [ -z "${cpu_cores}" ]; then
@ -702,7 +776,7 @@ function configure_bios() {
# https://bugzilla.redhat.com/show_bug.cgi?id=1929357#c5
# TODO: Check if macOS should use 'edk2-i386-vars.fd'
if [ -n "${EFI_CODE}" ] || [ ! -e "${EFI_CODE}" ]; then
if [ -z "${EFI_CODE}" ] || [ ! -e "${EFI_CODE}" ]; then
# Architecture-specific firmware paths
if [ "${ARCH_VM}" == "aarch64" ]; then
# ARM64/AArch64 firmware (AAVMF)
@ -1005,9 +1079,17 @@ function check_display_gl_support() {
"${QEMU}" -M none -display "dbus,gl=${gl_mode}" >"$tmpfile" 2>&1 &
local qemu_pid=$!
sleep 0.1
kill -9 $qemu_pid 2>/dev/null
wait $qemu_pid 2>/dev/null
sleep 0.2
# Try graceful termination first
if kill -0 "$qemu_pid" 2>/dev/null; then
kill -TERM "$qemu_pid" 2>/dev/null
sleep 0.15
# Force kill if still running
if kill -0 "$qemu_pid" 2>/dev/null; then
kill -KILL "$qemu_pid" 2>/dev/null
fi
fi
wait "$qemu_pid" 2>/dev/null
local test_output=$(cat "$tmpfile" 2>/dev/null)
rm -f "$tmpfile"
@ -1131,7 +1213,7 @@ function configure_display() {
# Try and coerce the display resolution for Linux guests only.
# Check if the device supports xres/yres parameters
if [[ "${DISPLAY_DEVICE}" =~ ^(virtio-(vga|gpu-pci|gpu-gl-pci|vga-gl)|qxl|qxl-vga|bochs-display)$ ]] || [[ "${DISPLAY_DEVICE}" =~ ,virgl=on ]]; then
if [[ "${DISPLAY_DEVICE}" =~ ^(virtio-(vga|gpu-pci|vga-gl|gpu-gl-pci)|qxl|qxl-vga|bochs-display)$ ]] || [[ "${DISPLAY_DEVICE}" =~ ,virgl=on ]]; then
VIDEO="${VIDEO},xres=${X_RES},yres=${Y_RES}"
echo " @ (${X_RES} x ${Y_RES})"
else
@ -1347,6 +1429,8 @@ function vm_boot() {
USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci"
VGA=""
VIDEO=""
# Set QEMU_VER_LONG for backward compatibility with user scripts that may reference it
QEMU_VER_LONG="${QEMU_VER_RAW}"
KERNEL_NAME="$(uname -s)"
KERNEL_NODE="$(uname -n | cut -d'.' -f 1)"
@ -2179,6 +2263,8 @@ if [ -z "${QEMU_VER_MAJOR}" ]; then
fi
QEMU_VER_MINOR=${QEMU_VER_MINOR:-0}
QEMU_VER_SHORT=$((QEMU_VER_MAJOR * 10 + QEMU_VER_MINOR))
# QEMU_VER_LONG maintained for backward compatibility
QEMU_VER_LONG="${QEMU_VER_RAW}"
if [ "${QEMU_VER_SHORT}" -lt 60 ]; then
echo "ERROR! QEMU 6.0.0 or newer is required, detected ${QEMU_VER_RAW}."
exit 1
@ -2376,6 +2462,8 @@ if [ -n "${VM}" ] && [ -e "${VM}" ]; then
fi
QEMU_VER_MINOR=${QEMU_VER_MINOR:-0}
QEMU_VER_SHORT=$((QEMU_VER_MAJOR * 10 + QEMU_VER_MINOR))
# QEMU_VER_LONG maintained for backward compatibility
QEMU_VER_LONG="${QEMU_VER_RAW}"
if [ "${QEMU_VER_SHORT}" -lt 60 ]; then
echo "ERROR! QEMU 6.0.0 or newer is required, detected ${QEMU_VER_RAW}."
exit 1

115
quickget
View File

@ -1205,21 +1205,80 @@ function check_hash() {
iso="${VM_PATH}/${1}"
fi
hash="${2}"
# Validate no leading/trailing whitespace in hash parameter
if [[ "${hash}" =~ ^[[:space:]] ]] || [[ "${hash}" =~ [[:space:]]$ ]]; then
echo "ERROR! Invalid hash format for ${iso}: leading or trailing whitespace detected."
echo " Hash parameter: '${hash}'"
exit 1
fi
if [[ "${hash}" == *":"* ]]; then
hash_prefix="${hash%%:*}"
hash="${hash#*:}"
# Normalize algorithm prefix to lowercase for case-insensitive matching (Bash 3.2 compatible)
hash_prefix="$(echo "${hash_prefix}" | tr '[:upper:]' '[:lower:]')"
# Validate that prefix is not empty
if [ -z "${hash_prefix}" ]; then
echo "ERROR! Invalid hash format for ${iso}: empty algorithm prefix (found '${2}')."
echo " Expected format: 'algorithm:hash' (e.g., 'sha256:abc123...')"
exit 1
fi
# Validate no internal whitespace in algorithm prefix
if [[ "${hash_prefix}" =~ [[:space:]] ]]; then
echo "ERROR! Invalid hash format for ${iso}: algorithm prefix contains whitespace."
echo " Hash parameter: '${2}'"
exit 1
fi
# Validate that the remaining hash does not contain additional colons
if [[ "${hash}" == *":"* ]]; then
echo "ERROR! Invalid hash format for ${iso}: multiple colons found."
exit 1
fi
# Validate hash is not empty after removing prefix
if [ -z "${hash}" ]; then
echo "ERROR! Invalid hash format for ${iso}: empty hash value after prefix '${hash_prefix}:'."
exit 1
fi
fi
if [ -z "${hash}" ]; then
echo "WARNING! Empty hash value, not checking ${iso} hash."
return
fi
# Validate hash contains only hexadecimal characters (no internal whitespace or invalid chars)
if [[ ! "${hash}" =~ ^[0-9a-fA-F]+$ ]]; then
echo "ERROR! Invalid hash format for ${iso}: hash contains non-hexadecimal characters."
echo " Hash value: '${hash}'"
exit 1
fi
# Validate hash length matches algorithm
local hash_len=${#hash}
if [ -n "${hash_prefix}" ]; then
case "${hash_prefix}" in
md5) hash_algo=md5sum;;
sha1) hash_algo=sha1sum;;
sha256) hash_algo=sha256sum;;
sha512) hash_algo=sha512sum;;
b2sum|blake2|blake2b) hash_algo=b2sum;;
md5) hash_algo=md5sum
if [ ${hash_len} -ne 32 ]; then
echo "ERROR! MD5 hash must be 32 characters, got ${hash_len}: ${hash}"
exit 1
fi;;
sha1) hash_algo=sha1sum
if [ ${hash_len} -ne 40 ]; then
echo "ERROR! SHA1 hash must be 40 characters, got ${hash_len}: ${hash}"
exit 1
fi;;
sha256) hash_algo=sha256sum
if [ ${hash_len} -ne 64 ]; then
echo "ERROR! SHA256 hash must be 64 characters, got ${hash_len}: ${hash}"
exit 1
fi;;
sha512) hash_algo=sha512sum
if [ ${hash_len} -ne 128 ]; then
echo "ERROR! SHA512 hash must be 128 characters, got ${hash_len}: ${hash}"
exit 1
fi;;
b2sum|blake2|blake2b) hash_algo=b2sum
# BLAKE2b supports variable lengths from 256-512 bits (64-128 hex chars)
if [ ${hash_len} -lt 64 ] || [ ${hash_len} -gt 128 ] || [ $((hash_len % 2)) -ne 0 ]; then
echo "ERROR! BLAKE2b hash must be 64-128 characters (even length), got ${hash_len}: ${hash}"
exit 1
fi;;
*) echo "WARNING! Unknown hash algorithm '${hash_prefix}', not checking ${iso} hash."
return;;
esac
@ -1239,7 +1298,7 @@ function check_hash() {
case "${hash_algo}" in
md5sum|sha1sum)
echo "WARNING! ${hash_algo} is a weak hash algorithm; prefer sha256, sha512, or b2sum."
echo "WARNING! ${hash_algo} is a weak hash algorithm. Prefer sha256, sha512, or b2sum for better security."
;;
esac
@ -1255,7 +1314,7 @@ function check_hash() {
fi
echo -n "Checking ${iso} with ${hash_algo}... "
if ! echo "${hash} ${iso}" | ${hash_algo} --check --status; then
if ! printf '%s %s\n' "${hash}" "${iso}" | ${hash_algo} --check --status; then
echo "ERROR!"
echo "${iso} doesn't match ${hash}. Try running 'quickget' again."
exit 1
@ -1493,7 +1552,8 @@ function make_vm_config() {
elif [[ "${IMAGE_FILE}" =~ (riscv64) ]]; then
ARCH_HINT="riscv64"
elif [[ "${HOST_ARCH}" == "aarch64" && "${OS}" != "macos" && "${OS}" != windows* ]]; then
# Use host architecture for Linux guests when available
# Use host architecture for Linux guests that support aarch64
# Add more distributions to this list as they add aarch64 support
case "${OS}" in
archboot|alpine|debian|fedora|ubuntu*) ARCH_HINT="aarch64";;
esac
@ -1692,18 +1752,47 @@ function get_archboot() {
local LATEST_URL="${URL}/${HOST_ARCH}/latest/iso"
local ISO_PATTERN="${EDITION}"
# Adjust pattern: "default" edition doesn't have a suffix in filename
# "local" edition is intended for locally available ISOs, not remote downloads
if [ "${EDITION}" == "local" ]; then
echo "ERROR! Archboot edition 'local' refers to a local ISO and cannot be downloaded."
echo " Valid downloadable editions are: default, latest"
echo " For local ISOs, reference them in your .conf file:"
echo " fixed_iso=\"/path/to/archboot-local-${HOST_ARCH}.iso\""
exit 1
fi
# Adjust pattern: match actual archboot filename format with word boundaries
# - x86_64 default: archboot-YYYY.MM.DD-HH.MM-X.XX.X-arch1-1-x86_64.iso
# - x86_64 latest: archboot-YYYY.MM.DD-HH.MM-X.XX.X-arch1-1-latest-x86_64.iso
# - aarch64 default: archboot-YYYY.MM.DD-HH.MM-X.XX.X-X-aarch64-ARCH-aarch64.iso
# - aarch64 latest: archboot-YYYY.MM.DD-HH.MM-X.XX.X-X-aarch64-ARCH-latest-aarch64.iso
# Use specific patterns to avoid substring matching between editions
if [ "${EDITION}" == "default" ]; then
ISO_PATTERN="ARCH-${HOST_ARCH}.iso\""
if [ "${HOST_ARCH}" == "x86_64" ]; then
# Match arch1-1-x86_64.iso but not arch1-1-latest-x86_64.iso or arch1-1-local-x86_64.iso
ISO_PATTERN="arch1-1-${HOST_ARCH}\.iso\"$"
else
# aarch64 uses uppercase ARCH in the pattern
# Match ARCH-aarch64.iso but not ARCH-latest-aarch64.iso or ARCH-local-aarch64.iso
ISO_PATTERN="ARCH-${HOST_ARCH}\.iso\"$"
fi
elif [ "${EDITION}" == "latest" ]; then
if [ "${HOST_ARCH}" == "x86_64" ]; then
ISO_PATTERN="arch1-1-latest-${HOST_ARCH}\.iso\"$"
else
# aarch64 uses uppercase ARCH in the pattern
ISO_PATTERN="ARCH-latest-${HOST_ARCH}\.iso\"$"
fi
else
ISO_PATTERN="${EDITION}-${HOST_ARCH}.iso\""
echo "ERROR! Invalid archboot edition '${EDITION}'. Only 'default' and 'latest' are supported."
exit 1
fi
# Get the ISO filename from the iso directory
ISO=$(web_pipe "${LATEST_URL}/" | grep -o 'href="[^"]*\.iso"' | grep "${ISO_PATTERN}" | head -n 1 | cut -d'"' -f2 | sed 's|.*/||')
ISO=$(web_pipe "${LATEST_URL}/" | grep -o 'href="[^"]*\.iso"' | grep -E "${ISO_PATTERN}" | head -n 1 | cut -d'"' -f2 | sed 's|.*/||')
if [ -z "${ISO}" ]; then
echo "ERROR! Could not find Archboot ${EDITION} ISO for ${HOST_ARCH}"
echo "ERROR! Could not find Archboot ${EDITION} ISO for ${HOST_ARCH} at ${LATEST_URL}/"
exit 1
fi