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 <katexochen0@gmail.com>
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" "$(</proc/cmdline)"
|
||||
assert_eq "$(systemd-creds --system cat "$COCO_CRED_CMDLINE_ID")" "$COCO_CRED_CMDLINE_VALUE"
|
||||
}
|
||||
|
||||
# fw_cfg is unmeasured on every platform and import_credentials_qemu() drops it for any confidential
|
||||
# guest, so this applies to SNP and TDX alike.
|
||||
testcase_coco_creds_hostile_fwcfg() {
|
||||
local fwcfg_dir=/sys/firmware/qemu_fw_cfg/by_name
|
||||
|
||||
[[ -d "$fwcfg_dir" ]] || modprobe qemu_fw_cfg || :
|
||||
|
||||
assert_ok test -e "$fwcfg_dir/opt/io.systemd.credentials/$COCO_CRED_FWCFG_ID/raw"
|
||||
_coco_assert_cred_absent "$COCO_CRED_FWCFG_ID"
|
||||
}
|
||||
|
||||
# SNP only: the SMBIOS tables are not covered by the launch measurement, so
|
||||
# import_credentials_smbios() bails out. Under TDX they are measured into RTMR0 and thus trusted.
|
||||
testcase_coco_creds_hostile_snp_smbios() {
|
||||
_coco_assert_smbios_injected "$COCO_CRED_SMBIOS_ID"
|
||||
_coco_assert_cred_absent "$COCO_CRED_SMBIOS_ID"
|
||||
}
|
||||
|
||||
# A host-injected SMBIOS string must not displace a credential the measured channel already
|
||||
# delivered: boot-channel credentials import first and names are never overwritten, on every
|
||||
# platform. The probe proves the overwrite was attempted.
|
||||
testcase_coco_creds_hostile_shadow() {
|
||||
_coco_assert_smbios_injected "$COCO_CRED_TRUSTED_ID"
|
||||
assert_eq "$(systemd-creds --system cat "$COCO_CRED_TRUSTED_ID")" "$COCO_CRED_TRUSTED_VALUE"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user