From d6f96ac3112ea9809108d8599abe3fd7b0eb53c6 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Fri, 11 Sep 2026 15:02:35 +0200 Subject: [PATCH] test: coco credential rejection on snp Not all channels used for credential transport in systemd are trusted on SNP: while cmdline and initrd are measured and should be used, smbios and fwcfg are untrusted and the guest should ignore creds from those delivery paths. Implement an offensive credential test that tries to inject credentials via those untrusted channels and check the guest won't consume those credentials. Signed-off-by: Paul Meyer --- test/units/TEST-94-COCO.snp-direct.sh | 27 ++++++++++-- test/units/TEST-94-COCO/fixtures.sh | 4 ++ test/units/TEST-94-COCO/guest-test.sh | 60 +++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) diff --git a/test/units/TEST-94-COCO.snp-direct.sh b/test/units/TEST-94-COCO.snp-direct.sh index 3bb9bda902..7933a8775f 100755 --- a/test/units/TEST-94-COCO.snp-direct.sh +++ b/test/units/TEST-94-COCO.snp-direct.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # SPDX-License-Identifier: LGPL-2.1-or-later # -# SEV-SNP, direct kernel boot: launch a confidential guest with --linux=/--initrd=. +# SEV-SNP, direct kernel boot: launch confidential guests with --linux=/--initrd=. set -eux set -o pipefail @@ -16,10 +16,12 @@ fi MACHINE="coco-snp-direct-$$" WORKDIR="$(mktemp -d)" +mkdir -p "$WORKDIR/honest" "$WORKDIR/hostile" at_exit() { set +e jobs -p | xargs -r kill 2>/dev/null - machinectl terminate "$MACHINE" 2>/dev/null || : + machinectl terminate "$MACHINE-honest" 2>/dev/null || : + machinectl terminate "$MACHINE-hostile" 2>/dev/null || : rm -rf "$WORKDIR" } trap at_exit EXIT @@ -29,7 +31,7 @@ trap at_exit EXIT # - detect_virt: systemd-detect-virt --cvm detects SNP as the CVM type # - creds_vmspawn: credential transport by vmspawn is working (via initrd for SNP) # - creds_cmdline: credential transport via cmdline is working (cmdline is measured on SNP) -vmspawn_boot_coco "$MACHINE" "$COCO_TYPE" "$WORKDIR" 'detect_virt creds_vmspawn creds_cmdline' \ +vmspawn_boot_coco "$MACHINE-honest" "$COCO_TYPE" "$WORKDIR/honest" 'detect_virt creds_vmspawn creds_cmdline' \ --image="$IMAGE_DIR/image.raw" \ --linux="$IMAGE_DIR/image.vmlinuz" \ --initrd="$IMAGE_DIR/image.initrd" \ @@ -37,3 +39,22 @@ vmspawn_boot_coco "$MACHINE" "$COCO_TYPE" "$WORKDIR" 'detect_virt creds_vmspawn selinux=0 systemd.firstboot=no rw \ "systemd.set_credential=$COCO_CRED_CMDLINE_ID:$COCO_CRED_CMDLINE_VALUE" echo "SEV-SNP direct-boot guest correctly reported confidential virtualization and trusted credentials" + +# Offensive test: +# +# - creds_hostile_*: ensure the guest isn't tricked into consuming untrusted/unmeasured credentials. +QEMU_EXTRA=( + -smbios "type=11,value=io.systemd.credential:$COCO_CRED_SMBIOS_ID=$COCO_CRED_SMBIOS_VALUE" + -smbios "type=11,value=io.systemd.credential:$COCO_CRED_TRUSTED_ID=$COCO_CRED_SMBIOS_VALUE" + -fw_cfg "name=opt/io.systemd.credentials/$COCO_CRED_FWCFG_ID,string=$COCO_CRED_FWCFG_VALUE" +) + +SYSTEMD_VMSPAWN_QEMU_EXTRA="${QEMU_EXTRA[*]}" \ +vmspawn_boot_coco "$MACHINE-hostile" "$COCO_TYPE" "$WORKDIR/hostile" \ + 'detect_virt creds_vmspawn creds_hostile_fwcfg creds_hostile_snp_smbios creds_hostile_shadow' \ + --image="$IMAGE_DIR/image.raw" \ + --linux="$IMAGE_DIR/image.vmlinuz" \ + --initrd="$IMAGE_DIR/image.initrd" \ + --set-credential="$COCO_CRED_TRUSTED_ID:$COCO_CRED_TRUSTED_VALUE" \ + selinux=0 systemd.firstboot=no rw +echo "SEV-SNP direct-boot guest correctly ignored the host-injected credentials" diff --git a/test/units/TEST-94-COCO/fixtures.sh b/test/units/TEST-94-COCO/fixtures.sh index c690537b18..5676aabd14 100644 --- a/test/units/TEST-94-COCO/fixtures.sh +++ b/test/units/TEST-94-COCO/fixtures.sh @@ -20,3 +20,7 @@ COCO_CRED_TRUSTED_ID="coco.trusted" COCO_CRED_TRUSTED_VALUE="trusted-via-vmspawn" COCO_CRED_CMDLINE_ID="coco.cmdline" COCO_CRED_CMDLINE_VALUE="trusted-via-cmdline" +COCO_CRED_SMBIOS_ID="coco.hostile.smbios" +COCO_CRED_SMBIOS_VALUE="injected-via-smbios" +COCO_CRED_FWCFG_ID="coco.hostile.fwcfg" +COCO_CRED_FWCFG_VALUE="injected-via-fwcfg" diff --git a/test/units/TEST-94-COCO/guest-test.sh b/test/units/TEST-94-COCO/guest-test.sh index b7778c07db..c2f89258d8 100644 --- a/test/units/TEST-94-COCO/guest-test.sh +++ b/test/units/TEST-94-COCO/guest-test.sh @@ -13,6 +13,40 @@ fi # shellcheck source=test/units/TEST-94-COCO/fixtures.sh . "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/fixtures.sh" +# _coco_assert_cred_absent ID +# Assert no credential named ID reached the guest at all, neither trusted nor encrypted. +_coco_assert_cred_absent() { + local id="${1:?}" + local system_creds_dir=/run/credentials/@system + local encrypted_creds_dir=/run/credentials/@encrypted + + assert_fail test -e "$system_creds_dir/$id" + assert_fail test -e "$encrypted_creds_dir/$id" +} + +# _coco_assert_smbios_injected ID +# Assert the guest received an SMBIOS type #11 OEM string carrying credential ID. The scenario +# injected it, so a miss means the injection was lost on the way in and concluding anything from the +# credential's absence afterwards would be meaningless. +_coco_assert_smbios_injected() { + local id="${1:?}" raw + + [[ -d /sys/firmware/dmi/entries ]] || modprobe dmi-sysfs || : + + for raw in /sys/firmware/dmi/entries/11-*/raw; do + [[ -e "$raw" ]] || continue + # The entry is a blob of NUL-separated strings: --text to search it anyway, --fixed-strings + # because credential names contain dots. Redirect rather than -q, which would exit early and + # risk SIGPIPE on the reader. + if grep --text --fixed-strings "io.systemd.credential:$id=" "$raw" >/dev/null; then + return 0 + fi + done + + echo "FAIL: no SMBIOS OEM string for credential '$id' in the guest's DMI type #11 tables" >&2 + return 1 +} + # Assert the guest observes the confidential-virtualization technology the host launched it with. # COCO_TYPE is provided by the injected guest unit (Environment=, see lib.sh). testcase_coco_detect_virt() { @@ -34,3 +68,29 @@ testcase_coco_creds_cmdline() { assert_in "systemd.set_credential=$COCO_CRED_CMDLINE_ID:$COCO_CRED_CMDLINE_VALUE" "$(