Skip to content

Commit bb8f719

Browse files
authored
AMDSEV: launch Katana asynchronously (#440)
Make AMDSEV startup asynchronous so Katana is launched after boot via a VM control channel. This keeps the launch measured digest remain constant regardless of the Katana runtime flags.
1 parent c07ade3 commit bb8f719

File tree

3 files changed

+372
-68
lines changed

3 files changed

+372
-68
lines changed

misc/AMDSEV/README.md

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ If `--katana` is not provided, `build.sh` prompts for confirmation (`y/N`) befor
4040
| `build-kernel.sh` | Downloads and extracts Ubuntu kernel (`vmlinuz`) |
4141
| `build-initrd.sh` | Creates minimal initrd with busybox, SEV-SNP modules, and katana |
4242
| `build-config` | Pinned versions and checksums for reproducible builds |
43-
| `start-vm.sh` | Starts a TEE VM with SEV-SNP enabled using QEMU |
43+
| `start-vm.sh` | Starts a TEE VM with SEV-SNP and launches Katana asynchronously |
4444

4545
## SNP Tools
4646

@@ -69,6 +69,9 @@ cargo build -p snp-tools
6969

7070
## Running
7171

72+
The QEMU command below boots the VM but does not automatically start Katana.
73+
Katana must be started asynchronously via the control channel.
74+
7275
```sh
7376
qemu-system-x86_64 \
7477
# Use KVM hardware virtualization (required for SEV-SNP)
@@ -93,11 +96,42 @@ qemu-system-x86_64 \
9396
# Initial ramdisk containing katana (measured when kernel-hashes=on)
9497
-initrd output/qemu/initrd.img \
9598
# Kernel command line (measured when kernel-hashes=on)
96-
# katana.args passes arguments to katana via init script
97-
-append "console=ttyS0 katana.args=--http.addr,0.0.0.0,--http.port,5050,--tee.provider,sev-snp" \
99+
-append "console=ttyS0" \
100+
# Katana control channel (used to start Katana asynchronously after boot)
101+
-device virtio-serial-pci,id=virtio-serial0 \
102+
-chardev socket,id=katanactl,path=/tmp/katana-control.sock,server=on,wait=off \
103+
-device virtserialport,chardev=katanactl,name=org.katana.control.0 \
98104
..
99105
```
100106

107+
### Start Katana via Control Channel
108+
109+
In the QEMU example above, this line defines the host-side control channel endpoint:
110+
111+
```sh
112+
-chardev socket,id=katanactl,path=/tmp/katana-control.sock,server=on,wait=off
113+
```
114+
115+
The `path=/tmp/katana-control.sock` value is the Unix socket file on the host.
116+
That socket is connected to the guest virtio-serial port:
117+
118+
```sh
119+
-device virtserialport,chardev=katanactl,name=org.katana.control.0
120+
```
121+
122+
So writes to that Unix socket become control commands inside the VM (`start`, `status`).
123+
124+
Example:
125+
126+
```sh
127+
# Start Katana with comma-separated CLI args
128+
printf 'start --http.addr,0.0.0.0,--http.port,5050,--tee.provider,sev-snp\n' \
129+
| socat - UNIX-CONNECT:/tmp/katana-control.sock
130+
131+
# Check launcher status
132+
printf 'status\n' | socat - UNIX-CONNECT:/tmp/katana-control.sock
133+
```
134+
101135
## Running the VM
102136

103137
The `start-vm.sh` script provides an easy way to launch a TEE VM with SEV-SNP enabled:
@@ -108,11 +142,16 @@ sudo ./misc/AMDSEV/start-vm.sh
108142

109143
# Or specify a custom boot components directory
110144
sudo ./misc/AMDSEV/start-vm.sh /path/to/boot-components
145+
146+
# Or customize Katana runtime flags (comma-separated)
147+
sudo ./misc/AMDSEV/start-vm.sh --katana-args "--http.addr,0.0.0.0,--http.port,5050,--tee.provider,sev-snp,--dev"
111148
```
112149

113150
The script:
114151
- Starts QEMU with SEV-SNP confidential computing enabled
115152
- Uses direct kernel boot with kernel-hashes=on for attestation
153+
- Keeps kernel cmdline stable (`console=ttyS0`) for deterministic measurement
154+
- Starts Katana asynchronously via virtio-serial control channel
116155
- Forwards RPC port 5050 to host port 15051
117156
- Outputs serial log to a temp file and follows it
118157

@@ -129,7 +168,7 @@ cargo build -p snp-tools
129168
--ovmf output/qemu/OVMF.fd \
130169
--kernel output/qemu/vmlinuz \
131170
--initrd output/qemu/initrd.img \
132-
--append "console=ttyS0 katana.args=--http.addr,0.0.0.0,--http.port,5050,--tee.provider,sev-snp" \
171+
--append "console=ttyS0" \
133172
--vcpus 1 \
134173
--cpu epyc-v4 \
135174
--vmm qemu \

misc/AMDSEV/build-initrd.sh

Lines changed: 109 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ log() { echo "[init] $*"; }
293293
KATANA_PID=""
294294
KATANA_DB_DIR="/mnt/data/katana-db"
295295
SHUTTING_DOWN=0
296+
KATANA_EXIT_CODE="never"
297+
CONTROL_PORT_NAME="org.katana.control.0"
298+
CONTROL_PORT_LINK="/dev/virtio-ports/org.katana.control.0"
296299
297300
fatal_boot() {
298301
log "ERROR: $*"
@@ -303,6 +306,90 @@ fatal_boot() {
303306
done
304307
}
305308
309+
refresh_katana_state() {
310+
if [ -n "$KATANA_PID" ] && ! kill -0 "$KATANA_PID" 2>/dev/null; then
311+
if wait "$KATANA_PID"; then
312+
KATANA_EXIT_CODE=0
313+
else
314+
KATANA_EXIT_CODE=$?
315+
fi
316+
log "Katana exited with code $KATANA_EXIT_CODE"
317+
KATANA_PID=""
318+
fi
319+
}
320+
321+
respond_control() {
322+
printf '%s\n' "$1" >&3 2>/dev/null || true
323+
}
324+
325+
resolve_control_port() {
326+
mkdir -p /dev/virtio-ports
327+
for name_file in /sys/class/virtio-ports/*/name; do
328+
[ -f "$name_file" ] || continue
329+
330+
PORT_NAME_VALUE="$(cat "$name_file" 2>/dev/null || true)"
331+
if [ "$PORT_NAME_VALUE" != "$CONTROL_PORT_NAME" ]; then
332+
continue
333+
fi
334+
335+
PORT_DIR="${name_file%/name}"
336+
PORT_DEV="/dev/${PORT_DIR##*/}"
337+
if [ -e "$PORT_DEV" ]; then
338+
ln -sf "$PORT_DEV" "$CONTROL_PORT_LINK"
339+
echo "$CONTROL_PORT_LINK"
340+
return 0
341+
fi
342+
done
343+
return 1
344+
}
345+
346+
handle_control_command() {
347+
RAW_CMD="$1"
348+
CMD="${RAW_CMD%% *}"
349+
CMD_PAYLOAD=""
350+
if [ "$CMD" != "$RAW_CMD" ]; then
351+
CMD_PAYLOAD="${RAW_CMD#* }"
352+
fi
353+
354+
case "$CMD" in
355+
start)
356+
refresh_katana_state
357+
if [ -n "$KATANA_PID" ] && kill -0 "$KATANA_PID" 2>/dev/null; then
358+
respond_control "err already-running pid=$KATANA_PID"
359+
return 0
360+
fi
361+
362+
KATANA_ARGS=""
363+
if [ -n "$CMD_PAYLOAD" ]; then
364+
KATANA_ARGS="$(echo "$CMD_PAYLOAD" | tr ',' ' ')"
365+
fi
366+
367+
log "Starting katana asynchronously..."
368+
# shellcheck disable=SC2086
369+
/bin/katana --db-dir="$KATANA_DB_DIR" $KATANA_ARGS &
370+
KATANA_PID=$!
371+
KATANA_EXIT_CODE="running"
372+
respond_control "ok started pid=$KATANA_PID"
373+
;;
374+
375+
status)
376+
refresh_katana_state
377+
if [ -n "$KATANA_PID" ] && kill -0 "$KATANA_PID" 2>/dev/null; then
378+
respond_control "running pid=$KATANA_PID"
379+
else
380+
respond_control "stopped exit=$KATANA_EXIT_CODE"
381+
fi
382+
;;
383+
384+
"")
385+
;;
386+
387+
*)
388+
respond_control "err unknown-command"
389+
;;
390+
esac
391+
}
392+
306393
shutdown_handler() {
307394
if [ "$SHUTTING_DOWN" -eq 1 ]; then
308395
return 0
@@ -404,15 +491,6 @@ else
404491
log "WARNING: eth0 interface not found; skipping static network setup"
405492
fi
406493
407-
# Parse katana args from cmdline
408-
CMDLINE="$(cat /proc/cmdline 2>/dev/null || true)"
409-
KATANA_ARGS=""
410-
for tok in $CMDLINE; do
411-
case "$tok" in
412-
katana.args=*) KATANA_ARGS="$(echo "${tok#katana.args=}" | tr ',' ' ')" ;;
413-
esac
414-
done
415-
416494
# Require persistent storage at /dev/sda
417495
if [ ! -b /dev/sda ]; then
418496
fatal_boot "required storage device /dev/sda not found"
@@ -426,22 +504,30 @@ fi
426504
mkdir -p "$KATANA_DB_DIR"
427505
log "Storage mounted at /mnt/data"
428506
429-
log "Starting katana..."
430-
# shellcheck disable=SC2086
431-
/bin/katana --db-dir="$KATANA_DB_DIR" $KATANA_ARGS &
432-
KATANA_PID=$!
433-
log "Katana started with PID $KATANA_PID"
434-
435-
if wait "$KATANA_PID"; then
436-
EXIT_CODE=0
437-
else
438-
EXIT_CODE=$?
439-
fi
440-
log "Katana exited with code $EXIT_CODE"
507+
# Start async control loop for Katana startup/status commands.
508+
log "Waiting for control channel ($CONTROL_PORT_NAME)..."
509+
CONTROL_PORT=""
510+
while [ -z "$CONTROL_PORT" ]; do
511+
CONTROL_PORT="$(resolve_control_port || true)"
512+
[ -n "$CONTROL_PORT" ] || sleep 1
513+
done
514+
log "Control channel ready: $CONTROL_PORT"
441515
442-
# PID 1 must stay alive unless explicitly powered off.
443516
while true; do
444-
sleep 60
517+
refresh_katana_state
518+
519+
if ! exec 3<>"$CONTROL_PORT"; then
520+
log "WARNING: failed to open control channel, retrying..."
521+
sleep 1
522+
continue
523+
fi
524+
525+
while IFS= read -r CONTROL_CMD <&3; do
526+
handle_control_command "$CONTROL_CMD"
527+
done
528+
529+
exec 3>&- 3<&-
530+
sleep 1
445531
done
446532
INIT_EOF
447533

0 commit comments

Comments
 (0)