Skip to content

Commit 49d22f3

Browse files
committed
Add isolated AMDSEV initrd validation script
1 parent ec7b3cb commit 49d22f3

File tree

2 files changed

+351
-0
lines changed

2 files changed

+351
-0
lines changed

misc/AMDSEV/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ If `--katana` is not provided, `build.sh` prompts for confirmation (`y/N`) befor
3939
| `build-ovmf.sh` | Builds OVMF firmware from AMD's fork with SEV-SNP support |
4040
| `build-kernel.sh` | Downloads and extracts Ubuntu kernel (`vmlinuz`) |
4141
| `build-initrd.sh` | Creates minimal initrd with busybox, SEV-SNP modules, and katana |
42+
| `test-initrd.sh` | Runs isolated initrd validation (archive checks + plain QEMU boot smoke) |
4243
| `build-config` | Pinned versions and checksums for reproducible builds |
4344
| `start-vm.sh` | Starts a TEE VM with SEV-SNP enabled using QEMU |
4445

@@ -116,6 +117,21 @@ The script:
116117
- Forwards RPC port 5050 to host port 15051
117118
- Outputs serial log to a temp file and follows it
118119

120+
## Isolated Initrd Testing
121+
122+
Use `test-initrd.sh` for focused initrd validation without the full SEV-SNP launch path:
123+
124+
```sh
125+
# Run static archive/content checks and plain-QEMU boot smoke test
126+
./misc/AMDSEV/test-initrd.sh
127+
128+
# Only check initrd archive contents
129+
./misc/AMDSEV/test-initrd.sh --static-only
130+
131+
# Only run plain-QEMU boot smoke test
132+
./misc/AMDSEV/test-initrd.sh --boot-only
133+
```
134+
119135
### Launch Measurement Verification
120136

121137
To verify a TEE VM's integrity, compute the expected launch measurement using `snp-digest`:

misc/AMDSEV/test-initrd.sh

Lines changed: 335 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,335 @@
1+
#!/bin/bash
2+
# ==============================================================================
3+
# TEST-INITRD.SH - Isolated initrd validation for AMDSEV
4+
# ==============================================================================
5+
#
6+
# Runs focused checks for initrd behavior without requiring the full SEV-SNP
7+
# launch path:
8+
# 1) Static archive/content checks (no VM boot)
9+
# 2) Plain-QEMU boot smoke test with RPC health check (no OVMF/SEV)
10+
#
11+
# Usage:
12+
# ./test-initrd.sh [OPTIONS]
13+
#
14+
# Options:
15+
# --output-dir DIR Boot artifacts directory (default: ./output/qemu)
16+
# --static-only Run only static initrd checks
17+
# --boot-only Run only boot smoke test
18+
# --host-rpc-port PORT Host port for forwarded Katana RPC (default: 15052)
19+
# --vm-rpc-port PORT Guest Katana RPC port (default: 5050)
20+
# --timeout SEC Boot wait timeout in seconds (default: 90)
21+
# -h, --help Show usage
22+
#
23+
# Environment:
24+
# QEMU_BIN Optional path to qemu-system-x86_64
25+
# TEST_DISK_SIZE Ephemeral test disk size (default: 1G)
26+
# ==============================================================================
27+
28+
set -euo pipefail
29+
30+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31+
OUTPUT_DIR="${SCRIPT_DIR}/output/qemu"
32+
INITRD_FILE=""
33+
KERNEL_FILE=""
34+
RUN_STATIC=1
35+
RUN_BOOT=1
36+
HOST_RPC_PORT=15052
37+
VM_RPC_PORT=5050
38+
BOOT_TIMEOUT=90
39+
TEST_DISK_SIZE="${TEST_DISK_SIZE:-1G}"
40+
41+
TEMP_DIR="$(mktemp -d /tmp/katana-amdsev-initrd-test.XXXXXX)"
42+
EXTRACT_DIR="${TEMP_DIR}/extract"
43+
SERIAL_LOG="${TEMP_DIR}/serial.log"
44+
DISK_IMG="${TEMP_DIR}/test-disk.img"
45+
QEMU_PID=""
46+
47+
usage() {
48+
cat <<USAGE
49+
Usage: $0 [OPTIONS]
50+
51+
Options:
52+
--output-dir DIR Boot artifacts directory (default: ./output/qemu)
53+
--static-only Run only static initrd checks
54+
--boot-only Run only boot smoke test
55+
--host-rpc-port PORT Host port for forwarded Katana RPC (default: 15052)
56+
--vm-rpc-port PORT Guest Katana RPC port (default: 5050)
57+
--timeout SEC Boot wait timeout in seconds (default: 90)
58+
-h, --help Show this help
59+
USAGE
60+
}
61+
62+
log() {
63+
echo "[test-initrd] $*"
64+
}
65+
66+
warn() {
67+
echo "[test-initrd] WARN: $*" >&2
68+
}
69+
70+
die() {
71+
echo "[test-initrd] ERROR: $*" >&2
72+
exit 1
73+
}
74+
75+
require_tool() {
76+
local tool="$1"
77+
command -v "$tool" >/dev/null 2>&1 || die "Required tool not found: $tool"
78+
}
79+
80+
cleanup() {
81+
local exit_code=$?
82+
83+
if [ -n "$QEMU_PID" ] && kill -0 "$QEMU_PID" 2>/dev/null; then
84+
log "Stopping QEMU (PID $QEMU_PID)..."
85+
kill "$QEMU_PID" 2>/dev/null || true
86+
87+
for _ in $(seq 1 10); do
88+
if ! kill -0 "$QEMU_PID" 2>/dev/null; then
89+
break
90+
fi
91+
sleep 0.5
92+
done
93+
94+
if kill -0 "$QEMU_PID" 2>/dev/null; then
95+
warn "QEMU still running, force killing"
96+
kill -9 "$QEMU_PID" 2>/dev/null || true
97+
fi
98+
99+
wait "$QEMU_PID" 2>/dev/null || true
100+
fi
101+
102+
rm -rf "$TEMP_DIR"
103+
exit "$exit_code"
104+
}
105+
trap cleanup EXIT INT TERM
106+
107+
while [[ $# -gt 0 ]]; do
108+
case "$1" in
109+
--output-dir)
110+
OUTPUT_DIR="${2:?Missing value for --output-dir}"
111+
shift 2
112+
;;
113+
--static-only)
114+
RUN_STATIC=1
115+
RUN_BOOT=0
116+
shift
117+
;;
118+
--boot-only)
119+
RUN_STATIC=0
120+
RUN_BOOT=1
121+
shift
122+
;;
123+
--host-rpc-port)
124+
HOST_RPC_PORT="${2:?Missing value for --host-rpc-port}"
125+
shift 2
126+
;;
127+
--vm-rpc-port)
128+
VM_RPC_PORT="${2:?Missing value for --vm-rpc-port}"
129+
shift 2
130+
;;
131+
--timeout)
132+
BOOT_TIMEOUT="${2:?Missing value for --timeout}"
133+
shift 2
134+
;;
135+
-h|--help)
136+
usage
137+
exit 0
138+
;;
139+
*)
140+
die "Unknown argument: $1"
141+
;;
142+
esac
143+
done
144+
145+
INITRD_FILE="${OUTPUT_DIR}/initrd.img"
146+
KERNEL_FILE="${OUTPUT_DIR}/vmlinuz"
147+
148+
assert_extract_path() {
149+
local rel_path="$1"
150+
if [ ! -e "${EXTRACT_DIR}/${rel_path}" ]; then
151+
die "Expected initrd path missing: ${rel_path}"
152+
fi
153+
}
154+
155+
assert_init_contains() {
156+
local pattern="$1"
157+
if ! grep -Fq -- "$pattern" "${EXTRACT_DIR}/init"; then
158+
die "Expected pattern missing in init script: ${pattern}"
159+
fi
160+
}
161+
162+
run_static_checks() {
163+
log "Running static initrd checks"
164+
165+
require_tool gzip
166+
require_tool cpio
167+
require_tool grep
168+
169+
[ -f "$INITRD_FILE" ] || die "Initrd not found: $INITRD_FILE"
170+
171+
if ! gzip -t "$INITRD_FILE" 2>/dev/null; then
172+
die "Initrd is not valid gzip: $INITRD_FILE"
173+
fi
174+
175+
mkdir -p "$EXTRACT_DIR"
176+
(
177+
cd "$EXTRACT_DIR"
178+
gzip -dc "$INITRD_FILE" | cpio -id --quiet
179+
)
180+
181+
REQUIRED_PATHS=(
182+
init
183+
bin/busybox
184+
bin/katana
185+
etc/passwd
186+
etc/group
187+
bin/sh
188+
bin/mount
189+
bin/umount
190+
bin/ip
191+
bin/insmod
192+
bin/poweroff
193+
bin/sync
194+
)
195+
196+
for path in "${REQUIRED_PATHS[@]}"; do
197+
assert_extract_path "$path"
198+
done
199+
200+
[ -x "${EXTRACT_DIR}/init" ] || die "Init script is not executable"
201+
[ -x "${EXTRACT_DIR}/bin/katana" ] || die "Katana binary in initrd is not executable"
202+
203+
assert_init_contains "trap shutdown_handler TERM INT"
204+
assert_init_contains "poweroff -f"
205+
assert_init_contains "exec 0</dev/console"
206+
assert_init_contains "if [ -d /sys/class/net/eth0 ]; then"
207+
assert_init_contains "katana.args="
208+
209+
if [ ! -e "${EXTRACT_DIR}/lib/modules/tsm.ko" ]; then
210+
warn "tsm.ko not present in initrd"
211+
fi
212+
if [ ! -e "${EXTRACT_DIR}/lib/modules/sev-guest.ko" ]; then
213+
warn "sev-guest.ko not present in initrd"
214+
fi
215+
216+
log "Static initrd checks passed"
217+
}
218+
219+
resolve_qemu_bin() {
220+
if [ -n "${QEMU_BIN:-}" ]; then
221+
echo "$QEMU_BIN"
222+
return 0
223+
fi
224+
225+
if command -v qemu-system-x86_64 >/dev/null 2>&1; then
226+
command -v qemu-system-x86_64
227+
return 0
228+
fi
229+
230+
if [ -x "${OUTPUT_DIR}/bin/qemu-system-x86_64" ]; then
231+
echo "${OUTPUT_DIR}/bin/qemu-system-x86_64"
232+
return 0
233+
fi
234+
235+
return 1
236+
}
237+
238+
run_boot_smoke_test() {
239+
local qemu_bin
240+
local response=""
241+
local ready=0
242+
243+
log "Running plain-QEMU boot smoke test"
244+
245+
[ -f "$KERNEL_FILE" ] || die "Kernel not found: $KERNEL_FILE"
246+
[ -f "$INITRD_FILE" ] || die "Initrd not found: $INITRD_FILE"
247+
248+
qemu_bin="$(resolve_qemu_bin)" || die "qemu-system-x86_64 not found (set QEMU_BIN if needed)"
249+
250+
require_tool curl
251+
require_tool mkfs.ext4
252+
require_tool truncate
253+
254+
truncate -s "$TEST_DISK_SIZE" "$DISK_IMG"
255+
mkfs.ext4 -q -F "$DISK_IMG"
256+
257+
KVM_OPTS=()
258+
if [ -r /dev/kvm ] && [ -w /dev/kvm ]; then
259+
KVM_OPTS=(-enable-kvm -cpu host)
260+
log "Using KVM acceleration"
261+
else
262+
warn "/dev/kvm not accessible; using software emulation"
263+
KVM_OPTS=(-cpu max)
264+
fi
265+
266+
"$qemu_bin" \
267+
"${KVM_OPTS[@]}" \
268+
-m 512M \
269+
-smp 1 \
270+
-nographic \
271+
-serial "file:$SERIAL_LOG" \
272+
-kernel "$KERNEL_FILE" \
273+
-initrd "$INITRD_FILE" \
274+
-append "console=ttyS0 katana.args=--http.addr,0.0.0.0,--http.port,${VM_RPC_PORT},--tee.provider,sev-snp" \
275+
-device virtio-scsi-pci,id=scsi0 \
276+
-drive "file=${DISK_IMG},format=raw,if=none,id=disk0,cache=none" \
277+
-device scsi-hd,drive=disk0,bus=scsi0.0 \
278+
-netdev "user,id=net0,hostfwd=tcp::${HOST_RPC_PORT}-:${VM_RPC_PORT}" \
279+
-device virtio-net-pci,netdev=net0 \
280+
&
281+
282+
QEMU_PID=$!
283+
log "QEMU started with PID $QEMU_PID"
284+
285+
for ((elapsed = 1; elapsed <= BOOT_TIMEOUT; elapsed++)); do
286+
if ! kill -0 "$QEMU_PID" 2>/dev/null; then
287+
warn "QEMU exited before RPC became ready"
288+
if [ -f "$SERIAL_LOG" ]; then
289+
echo "=== Serial output ===" >&2
290+
tail -n 200 "$SERIAL_LOG" >&2 || true
291+
fi
292+
die "Boot smoke test failed"
293+
fi
294+
295+
response="$(curl -s --max-time 2 -X POST \
296+
-H "Content-Type: application/json" \
297+
-d '{"jsonrpc":"2.0","method":"starknet_chainId","id":1}' \
298+
"http://127.0.0.1:${HOST_RPC_PORT}" || true)"
299+
300+
if echo "$response" | grep -q '"result"'; then
301+
ready=1
302+
break
303+
fi
304+
305+
if (( elapsed % 5 == 0 )); then
306+
log "Waiting for Katana RPC... (${elapsed}s/${BOOT_TIMEOUT}s)"
307+
fi
308+
sleep 1
309+
done
310+
311+
if [ "$ready" -ne 1 ]; then
312+
warn "Timed out waiting for Katana RPC"
313+
if [ -f "$SERIAL_LOG" ]; then
314+
echo "=== Serial output ===" >&2
315+
tail -n 200 "$SERIAL_LOG" >&2 || true
316+
fi
317+
die "Boot smoke test timed out"
318+
fi
319+
320+
log "RPC check passed: $response"
321+
log "Boot smoke test passed"
322+
}
323+
324+
log "Output directory: $OUTPUT_DIR"
325+
log "Modes: static=$RUN_STATIC boot=$RUN_BOOT"
326+
327+
if [ "$RUN_STATIC" -eq 1 ]; then
328+
run_static_checks
329+
fi
330+
331+
if [ "$RUN_BOOT" -eq 1 ]; then
332+
run_boot_smoke_test
333+
fi
334+
335+
log "All requested initrd checks passed"

0 commit comments

Comments
 (0)