Skip to main content

max / alloy

6.5 KB · 128 lines History Blame Raw
1 #!/usr/bin/env bash
2 # Boot the Alloy installer ISO in qemu, headless, with ssh forwarded to :2222.
3 # run-vm.sh live boot the ISO (cdrom first)
4 # run-vm.sh installed boot the target disk only (no cdrom)
5 set -euo pipefail
6
7 HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8 REPO_ROOT="$(cd "$HERE/../.." && pwd)"
9
10 # Everything the run writes lives here, gitignored, and is safe to delete
11 # between runs. Not under output/, which build-iso.sh clears at the start of
12 # every build and would take a half-finished install with it.
13 SCRATCH="${VM_STATE:-$HERE/state}"; mkdir -p "$SCRATCH"
14 ISO=${ALLOY_ISO:-$REPO_ROOT/output/install.iso}
15 DISK="$SCRATCH/target.qcow2"
16 VARS="$SCRATCH/OVMF_VARS.fd"
17 MODE="${1:-live}"
18
19 [ -f "$VARS" ] || cp /usr/share/OVMF/OVMF_VARS_4M.fd "$VARS"
20
21 # A software TPM, because `bootc install --block-setup tpm2-luks` enrols the
22 # key into one and there is nothing to enrol into otherwise. State persists in
23 # tpm/ so an installed disk can unlock on the next boot the way real hardware
24 # would; delete that directory to simulate a cleared TPM.
25 mkdir -p "$SCRATCH/tpm"
26 if [ ! -S "$SCRATCH/tpm/swtpm-sock" ]; then
27 # Only mint a TPM when there is not one already. The setup call carries
28 # `--overwrite`, and this branch is taken every time the swtpm daemon has been
29 # stopped rather than only on a first run, so running it unconditionally gives
30 # the machine a new seed behind its own back. An installed disk that unlocks
31 # from the TPM then cannot load its sealed key and falls back to asking for the
32 # passphrase, reporting `Failed to unseal secret using TPM2: State not
33 # recoverable` — which reads as a broken enrolment rather than as hardware the
34 # harness swapped out. Deleting state/tpm/ is still how you clear a TPM on
35 # purpose; this only stops it happening by accident.
36 if [ ! -f "$SCRATCH/tpm/tpm2-00.permall" ]; then
37 swtpm_setup --tpm2 --tpmstate "$SCRATCH/tpm" --createek --create-ek-cert \
38 --create-platform-cert --lock-nvram --overwrite >/dev/null 2>&1 || true
39 fi
40 swtpm socket --tpm2 --tpmstate dir="$SCRATCH/tpm" \
41 --ctrl type=unixio,path="$SCRATCH/tpm/swtpm-sock" \
42 --flags startup-clear --daemon
43 fi
44 [ -f "$DISK" ] || qemu-img create -f qcow2 "$DISK" 40G >/dev/null
45
46 # How the target disk is attached, which matters to more than performance.
47 # `virtio` gives the guest a /dev/vda whose lsblk `tran` is null; `nvme` gives it
48 # a /dev/nvme0n1 that reports `tran: nvme`. A recipe whose disk rule is
49 # `single-internal-nvme` (build/hosts/fw12.env) can only be exercised against
50 # the second, so a scenario testing a prefilled install asks for it. Default
51 # stays virtio, because every scenario written before this one assumes vda.
52 TARGET_BUS="${TARGET_BUS:-virtio}"
53 case "$TARGET_BUS" in
54 virtio) TARGET_ARGS=(-drive "file=$DISK,if=virtio,format=qcow2") ;;
55 nvme) TARGET_ARGS=(
56 -drive "file=$DISK,if=none,id=target,format=qcow2"
57 -device "nvme,serial=alloyvmtest,drive=target"
58 ) ;;
59 *) echo "unknown TARGET_BUS '$TARGET_BUS'; expected virtio or nvme" >&2; exit 1 ;;
60 esac
61
62 # What keyboards the machine has, which is hardware the gate's bench tests need
63 # and no machine here can be. `usr/bin/alloy-usb-gate` suspends enforcement
64 # whenever the count of ID_INPUT_KEYBOARD devices reaches zero, so exercising it
65 # means a machine that can have zero, and every physical machine here has an
66 # i8042 keyboard soldered to it that usbguard has no jurisdiction over.
67 #
68 # ps2 q35's own i8042 controller. The default, and what every scenario
69 # written before this one assumes.
70 # none i8042=off. Zero keyboards of any kind, which is the machine the gate
71 # is supposed to open for at boot.
72 # usb i8042=off plus a USB keyboard on an xHCI controller, so the only
73 # keyboard on the machine is one usbguard CAN take away. `id=kbd0` is
74 # load-bearing: unplugging it mid-session is `device_del kbd0` over the
75 # monitor, which is the third bench test and cannot be done from inside
76 # the guest.
77 #
78 # q35 carries no USB controller unless one is asked for, so the `usb` case adds
79 # an xHCI rather than relying on a default that is not there.
80 KEYBOARD="${KEYBOARD:-ps2}"
81 case "$KEYBOARD" in
82 ps2) MACHINE="q35" ; KBD_ARGS=() ;;
83 none) MACHINE="q35,i8042=off"; KBD_ARGS=() ;;
84 usb) MACHINE="q35,i8042=off"
85 KBD_ARGS=(-device qemu-xhci,id=xhci -device usb-kbd,bus=xhci.0,id=kbd0) ;;
86 *) echo "unknown KEYBOARD '$KEYBOARD'; expected ps2, none or usb" >&2; exit 1 ;;
87 esac
88
89 args=(
90 -enable-kvm -machine "$MACHINE" -cpu host -m 4608 -smp 4
91 "${KBD_ARGS[@]}"
92 -drive if=pflash,format=raw,unit=0,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd
93 -drive "if=pflash,format=raw,unit=1,file=$VARS"
94 "${TARGET_ARGS[@]}"
95 # Two forwards, and the second one is the firewall probe. User-mode
96 # networking gives the guest no inbound path except a hostfwd, so a port the
97 # host cannot reach at all proves nothing about the firewall: :2223 is a real
98 # inbound path to guest :5555, which nothing in `50-alloy.preset` enables.
99 # Refused there while ssh answers on :2222 is what the zone claims. See
100 # build/vmtest/README.md, "Checking the firewall".
101 -netdev "user,id=n0,hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:${PROBE_PORT:-2223}-:5555"
102 # `id=` so the frontend can be named from outside. QMP's `set_link` takes a
103 # device id, and taking the guest's route away from the monitor is the only
104 # way to do it that does not depend on the guest: from inside, `ip link set
105 # <dev> down` travels over the ssh session it arrived on and kills it. See
106 # build/vmtest/qmp.py and offline-first-boot.sh.
107 -device virtio-net-pci,netdev=n0,id=nic0
108 -chardev "socket,id=chrtpm,path=$SCRATCH/tpm/swtpm-sock"
109 -tpmdev emulator,id=tpm0,chardev=chrtpm
110 -device tpm-crb,tpmdev=tpm0
111 -display none
112 -chardev "socket,id=ser0,path=$SCRATCH/serial.sock,server=on,wait=off,logfile=$SCRATCH/serial-$MODE.log"
113 -serial chardev:ser0
114 -monitor "unix:$SCRATCH/monitor.sock,server,nowait"
115 # The machine monitor, next to the human one rather than instead of it. HMP
116 # is what vm.py wants (sendkey, screendump); QMP is the only one that can say
117 # WHEN something happened inside the guest, because it emits events. A
118 # scripted first-boot test needs to know the machine rebooted, and `info
119 # status` reports running on both sides of a reset. See build/vmtest/qmp.py.
120 -qmp "unix:$SCRATCH/qmp.sock,server=on,wait=off"
121 )
122
123 if [ "$MODE" = live ]; then
124 args+=(-drive "file=$ISO,media=cdrom,readonly=on" -boot d)
125 fi
126
127 exec qemu-system-x86_64 "${args[@]}"
128