199 lines
7.4 KiB
Bash
Executable File
199 lines
7.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ============================================================================
|
|
# qcrows-verify — Verify integrity and compliance of a QCrows image archive
|
|
# ============================================================================
|
|
set -euo pipefail
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${GREEN}[qcrows-verify]${NC} $*"; }
|
|
warn() { echo -e "${YELLOW}[qcrows-verify]${NC} WARN: $*"; }
|
|
fail() { echo -e "${RED}[qcrows-verify]${NC} FAIL: $*"; }
|
|
|
|
FILE="${1:-}"
|
|
[[ -z "$FILE" ]] && { echo "Usage: qcrows-verify <file.qcrows>"; exit 1; }
|
|
[[ ! -f "$FILE" ]] && { echo "File not found: $FILE" >&2; exit 1; }
|
|
|
|
TMPDIR="$(mktemp -d /tmp/qcrows-verify-XXXXXX)"
|
|
trap 'rm -rf "$TMPDIR"' EXIT
|
|
|
|
# Extract archive
|
|
case "$FILE" in
|
|
*.gz) tar xzf "$FILE" -C "$TMPDIR" 2>/dev/null || tar xf "$FILE" -C "$TMPDIR" 2>/dev/null ;;
|
|
*) tar xf "$FILE" -C "$TMPDIR" 2>/dev/null ;;
|
|
esac
|
|
|
|
# Descend into subdirectory if the archive wraps everything in one
|
|
# (i.e., only one child dir and no regular files at the top level)
|
|
top_level_files=$(find "$TMPDIR" -maxdepth 1 -type f | wc -l)
|
|
top_level_dirs=$(find "$TMPDIR" -maxdepth 1 -type d ! -path "$TMPDIR" | wc -l)
|
|
if [[ $top_level_dirs -eq 1 && $top_level_files -eq 0 ]]; then
|
|
TMPDIR="$(find "$TMPDIR" -maxdepth 1 -type d ! -path "$TMPDIR" | head -1)"
|
|
fi
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
WARN_COUNT=0
|
|
|
|
# ─── 1. Required files ─────────────────────────────────────────────────────
|
|
for req in metadata.toml menu.toml hashes.sha256; do
|
|
if [[ -f "${TMPDIR}/${req}" ]]; then
|
|
log "PASS: ${req} present"
|
|
((PASS++)) || true
|
|
else
|
|
fail "FAIL: ${req} missing (required)"
|
|
((FAIL++)) || true
|
|
fi
|
|
done
|
|
|
|
# ─── 2. Rootfs ─────────────────────────────────────────────────────────────
|
|
rootfs_found=false
|
|
for ext in .tar.gz .tar.xz .tar.zst .tgz; do
|
|
if ls "${TMPDIR}"/rootfs*${ext} 2>/dev/null | head -1 | grep -q .; then
|
|
rootfs_found=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$rootfs_found" == "false" ]] && ls "${TMPDIR}"/rootfs* 2>/dev/null | head -1 | grep -q .; then
|
|
rootfs_found=true
|
|
fi
|
|
|
|
if [[ "$rootfs_found" == "true" ]]; then
|
|
log "PASS: rootfs found"
|
|
((PASS++)) || true
|
|
else
|
|
fail "FAIL: rootfs missing (required)"
|
|
((FAIL++)) || true
|
|
fi
|
|
|
|
# ─── 3. Kernel binary (required in v0.2+) ──────────────────────────────────
|
|
kernel_file=""
|
|
for name in kernel/vmlinuz kernel/vmlinux; do
|
|
if [[ -f "${TMPDIR}/${name}" ]]; then
|
|
kernel_file="${TMPDIR}/${name}"
|
|
log "PASS: kernel binary found (${name})"
|
|
((PASS++)) || true
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -z "$kernel_file" ]]; then
|
|
fail "FAIL: kernel missing (required in QCrows v0.2+)"
|
|
((FAIL++)) || true
|
|
else
|
|
# Kernel format validation
|
|
if file "$kernel_file" 2>/dev/null | grep -qE '(ELF|Linux.*boot)'; then
|
|
log "PASS: kernel binary format valid"
|
|
((PASS++)) || true
|
|
else
|
|
fail "FAIL: kernel binary format invalid (expected ELF or bzImage)"
|
|
((FAIL++)) || true
|
|
fi
|
|
|
|
# Kernel size warning
|
|
kernel_size=$(stat --format='%s' "$kernel_file" 2>/dev/null || echo 0)
|
|
kernel_size_mb=$(( kernel_size / (1024 * 1024) ))
|
|
if [[ $kernel_size_mb -gt 100 ]]; then
|
|
warn "WARN: kernel exceeds 100MiB (${kernel_size_mb}MiB)"
|
|
((WARN_COUNT++)) || true
|
|
fi
|
|
fi
|
|
|
|
# ─── 4. Kernel config (required) ───────────────────────────────────────────
|
|
if [[ -f "${TMPDIR}/kernel/config" ]]; then
|
|
log "PASS: kernel/config found"
|
|
((PASS++)) || true
|
|
|
|
# Required Kata config options — array-driven validation
|
|
missing_options=0
|
|
for opt in CONFIG_VSOCKETS CONFIG_VIRTIO CONFIG_VIRTIO_PCI CONFIG_DEVTMPFS CONFIG_DEVTMPFS_MOUNT; do
|
|
if ! grep -q "${opt}=y" "${TMPDIR}/kernel/config" 2>/dev/null; then
|
|
warn "WARN: required Kata option ${opt}=y not found in kernel config"
|
|
((missing_options++)) || true
|
|
fi
|
|
done
|
|
|
|
if [[ $missing_options -eq 0 ]]; then
|
|
log "PASS: all required Kata kernel options present"
|
|
((PASS++)) || true
|
|
else
|
|
warn "WARN: ${missing_options} required Kata options missing from kernel config"
|
|
((WARN_COUNT++)) || true
|
|
fi
|
|
else
|
|
fail "FAIL: kernel/config missing (required in QCrows v0.2+)"
|
|
((FAIL++)) || true
|
|
fi
|
|
|
|
# ─── 5. Boot params (optional — warn about dangerous params) ───────────────
|
|
if [[ -f "${TMPDIR}/boot-params.conf" ]]; then
|
|
log "PASS: boot-params.conf present"
|
|
((PASS++)) || true
|
|
if grep -qE 'module.sig_enforce=0|nokaslr|nopti|nosmap|nosmep' "${TMPDIR}/boot-params.conf" 2>/dev/null; then
|
|
warn "WARN: potentially dangerous kernel parameters in boot-params.conf"
|
|
((WARN_COUNT++)) || true
|
|
fi
|
|
fi
|
|
|
|
# ─── 6. Initrd ─────────────────────────────────────────────────────────────
|
|
initrd_found=false
|
|
for name in initrd.img initrd.cpio.gz initrd.cpio.lz4 initrd.cpio.xz initrd.cramfs; do
|
|
if [[ -f "${TMPDIR}/${name}" ]]; then
|
|
initrd_found=true
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$initrd_found" == "true" ]]; then
|
|
log "PASS: initrd present"
|
|
((PASS++)) || true
|
|
else
|
|
warn "WARN: no initrd found (one of initrd/cramfs is recommended)"
|
|
((WARN_COUNT++)) || true
|
|
fi
|
|
|
|
# ─── 7. Hash verification ──────────────────────────────────────────────────
|
|
if [[ -f "${TMPDIR}/hashes.sha256" ]]; then
|
|
cd "$TMPDIR"
|
|
if sha256sum --quiet -c hashes.sha256 2>/dev/null; then
|
|
log "PASS: all SHA-256 checksums match"
|
|
((PASS++)) || true
|
|
else
|
|
fail "FAIL: SHA-256 checksum mismatch"
|
|
((FAIL++)) || true
|
|
fi
|
|
cd - >/dev/null
|
|
fi
|
|
|
|
# ─── 8. metadata.toml format check ─────────────────────────────────────────
|
|
if [[ -f "${TMPDIR}/metadata.toml" ]]; then
|
|
if grep -q "format_version" "${TMPDIR}/metadata.toml"; then
|
|
meta_ver=$(grep "format_version" "${TMPDIR}/metadata.toml" | head -1 | grep -oP '"[\d.]+"' | tr -d '"')
|
|
log "PASS: metadata format_version = ${meta_ver}"
|
|
((PASS++)) || true
|
|
else
|
|
fail "FAIL: metadata.toml missing format_version"
|
|
((FAIL++)) || true
|
|
fi
|
|
|
|
if grep -q "hypervisors" "${TMPDIR}/metadata.toml"; then
|
|
log "PASS: hypervisor compatibility declared"
|
|
((PASS++)) || true
|
|
else
|
|
warn "WARN: no hypervisor compatibility in metadata"
|
|
((WARN_COUNT++)) || true
|
|
fi
|
|
fi
|
|
|
|
# ─── Summary ────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
|
|
echo -e " Passed: ${GREEN}${PASS}${NC} Failed: ${RED}${FAIL}${NC} Warnings: ${YELLOW}${WARN_COUNT}${NC}"
|
|
echo -e "${CYAN}──────────────────────────────────────────────────────${NC}"
|
|
|
|
[[ $FAIL -gt 0 ]] && exit 1
|
|
exit 0
|