#!/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 "; 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