conf: Introduce SEV-SNP support

SEV-SNP is an enhancement of SEV/SEV-ES and thus it shares some
fields with it. Nevertheless, on XML level, it's yet another type
of <launchSecurity/>.

Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Michal Privoznik 2024-06-11 11:58:41 +02:00
parent 1abcba9d4d
commit c65eba1f57
18 changed files with 399 additions and 0 deletions

View File

@ -8867,6 +8867,114 @@ spec <https://support.amd.com/TechDocs/55766_SEV-KM_API_Specification.pdf>`__
session blob defined in the SEV API spec. See SEV spec LAUNCH_START section
for the session blob format.
Some modern AMD processors support Secure Encrypted Virtualization with Secure
Nested Paging enhancement, also known as SEV-SNP. :since:`Since 10.5.0` To
enable it ``<launchSecurity type='sev-snp'>`` should be used. It shares some
attributes and elements with ``type='sev'`` but differs in others. Example configuration:
::
<domain>
...
<launchSecurity type='sev-snp' authorKey='yes' vcek='no'>
<cbitpos>47</cbitpos>
<reducedPhysBits>1</reducedPhysBits>
<policy>0x00030000</policy>
<guestVisibleWorkarounds>...</guestVisibleWorkarounds>
<idBlock>...</idBlock>
<idAuth>...</idAuth>
<hostData>.../hostData>
</launchSecurity>
...
</domain>
The ``<launchSecurity/>`` element accepts the following attributes:
``kernelHashes``
The optional ``kernelHashes`` attribute indicates whether the
hashes of the kernel, ramdisk and command line should be included
in the measurement done by the firmware. This is only valid if
using direct kernel boot.
``authorKey``
The optional ``authorKey`` attribute indicates whether ``<idAuth/>`` element
contains the 'AUTHOR_KEY' field defined SEV-SNP firmware ABI.
``vcek``
The optional ``vcek`` attribute indicates whether the guest is allowed to
chose between VLEK (Versioned Loaded Endorsement Key) or VCEK (Versioned
Chip Endorsement Key) when requesting attestation reports from firmware.
Set this to ``no`` to disable the use of VCEK.
Aforementioned SEV-SNP firmware ABI can be found here:
`<https://www.amd.com/system/files/TechDocs/56860.pdf>`__
The ``<launchSecurity/>`` element then accepts the following child elements:
``cbitpos``
The required ``cbitpos`` element provides the C-bit (aka encryption bit)
location in guest page table entry. The value of ``cbitpos`` is hypervisor
dependent and can be obtained through the ``sev`` element from the domain
capabilities.
``reducedPhysBits``
The required ``reducedPhysBits`` element provides the physical address bit
reduction. Similar to ``cbitpos`` the value of ``reduced-phys-bit`` is
hypervisor dependent and can be obtained through the ``sev`` element from the
domain capabilities.
``policy``
The required ``policy`` element provides the guest policy which must be
maintained by the SEV-SNP firmware. This policy is enforced by the firmware
and restricts what configuration and operational commands can be performed
on this guest by the hypervisor. The guest policy provided during guest
launch is bound to the guest and cannot be changed throughout the lifetime
of the guest. The policy is also transmitted during snapshot and migration
flows and enforced on the destination platform. The guest policy is a 64bit
unsigned number with the fields shown in table (See section `4.3 Guest
Policy` in aforementioned firmware ABI specification):
====== =========================================================================================
Bit(s) Description
====== =========================================================================================
63:25 Reserved. Must be zero.
24 Ciphertext hiding must be enabled when set, otherwise may be enabled or disabled.
23 Running Average Power Limit (RAPL) must be disabled when set.
22 Require AES 256 XTS for memory encryption when set, otherwise AES 128 XEX may be allowed.
21 CXL can be populated with devices or memory when set.
20 Guest can be activated only on one socket when set.
19 Debugging is allowed when set.
18 Association with a migration agent is allowed when set.
17 Reserved. Must be set.
16 SMT is allowed.
15:8 The minimum ABI major version required for this guest to run.
7:0 The minimum ABI minor version required for this guest to run.
====== =========================================================================================
The default value is hypervisor dependant and QEMU defaults to value 0x30000
meaning no minimum ABI major/minor version is required and SMT is allowed.
``guestVisibleWorkarounds``
The optional ``guestVisibleWorkarounds`` element is a 16-byte,
base64-encoded blob to report hypervisor-defined workarounds, corresponding
to the 'GOSVW' parameter of the SNP_LAUNCH_START command defined in the
SEV-SNP firmware ABI.
``idBlock``
The optional ``idBlock`` element is a 96-byte, base64-encoded blob to
provide the 'ID Block' structure for the SNP_LAUNCH_FINISH command defined
in the SEV-SNP firmware ABI.
``idAuth``
The optional ``idAuth`` element is a 4096-byte, base64-encoded blob to
provide the 'ID Authentication Information Structure' for the
SNP_LAUNCH_FINISH command defined in the SEV-SNP firmware ABI.
``hostData``
The optional ``hostData`` element is a 32-byte, base64-encoded, user-defined
blob to provide to the guest, as documented for the 'HOST_DATA' parameter of
the SNP_LAUNCH_FINISH command in the SEV-SNP firmware ABI.
Example configs
===============

View File

@ -1509,6 +1509,7 @@ VIR_ENUM_IMPL(virDomainLaunchSecurity,
VIR_DOMAIN_LAUNCH_SECURITY_LAST,
"",
"sev",
"sev-snp",
"s390-pv",
);
@ -3835,6 +3836,12 @@ virDomainSecDefFree(virDomainSecDef *def)
g_free(def->data.sev.dh_cert);
g_free(def->data.sev.session);
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
g_free(def->data.sev_snp.guest_visible_workarounds);
g_free(def->data.sev_snp.id_block);
g_free(def->data.sev_snp.id_auth);
g_free(def->data.sev_snp.host_data);
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
case VIR_DOMAIN_LAUNCH_SECURITY_NONE:
case VIR_DOMAIN_LAUNCH_SECURITY_LAST:
@ -13676,6 +13683,36 @@ virDomainSEVDefParseXML(virDomainSEVDef *def,
}
static int
virDomainSEVSNPDefParseXML(virDomainSEVSNPDef *def,
xmlXPathContextPtr ctxt)
{
if (virDomainSEVCommonDefParseXML(&def->common, ctxt) < 0)
return -1;
if (virXMLPropTristateBool(ctxt->node, "authorKey", VIR_XML_PROP_NONE,
&def->author_key) < 0)
return -1;
if (virXMLPropTristateBool(ctxt->node, "vcek", VIR_XML_PROP_NONE,
&def->vcek) < 0)
return -1;
if (virXPathULongLongBase("string(./policy)", ctxt, 16, &def->policy) < 0) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("failed to get launch security policy"));
return -1;
}
def->guest_visible_workarounds = virXPathString("string(./guestVisibleWorkarounds)", ctxt);
def->id_block = virXPathString("string(./idBlock)", ctxt);
def->id_auth = virXPathString("string(./idAuth)", ctxt);
def->host_data = virXPathString("string(./hostData)", ctxt);
return 0;
}
static virDomainSecDef *
virDomainSecDefParseXML(xmlNodePtr lsecNode,
xmlXPathContextPtr ctxt)
@ -13695,6 +13732,10 @@ virDomainSecDefParseXML(xmlNodePtr lsecNode,
if (virDomainSEVDefParseXML(&sec->data.sev, ctxt) < 0)
return NULL;
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
if (virDomainSEVSNPDefParseXML(&sec->data.sev_snp, ctxt) < 0)
return NULL;
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
break;
case VIR_DOMAIN_LAUNCH_SECURITY_NONE:
@ -26683,6 +26724,34 @@ virDomainSEVDefFormat(virBuffer *attrBuf,
}
static void
virDomainSEVSNPDefFormat(virBuffer *attrBuf,
virBuffer *childBuf,
virDomainSEVSNPDef *def)
{
virDomainSEVCommonDefFormat(attrBuf, childBuf, &def->common);
if (def->author_key != VIR_TRISTATE_BOOL_ABSENT) {
virBufferAsprintf(attrBuf, " authorKey='%s'",
virTristateBoolTypeToString(def->author_key));
}
if (def->vcek != VIR_TRISTATE_BOOL_ABSENT) {
virBufferAsprintf(attrBuf, " vcek='%s'",
virTristateBoolTypeToString(def->vcek));
}
virBufferAsprintf(childBuf, "<policy>0x%08llx</policy>\n", def->policy);
virBufferEscapeString(childBuf,
"<guestVisibleWorkarounds>%s</guestVisibleWorkarounds>\n",
def->guest_visible_workarounds);
virBufferEscapeString(childBuf, "<idBlock>%s</idBlock>\n", def->id_block);
virBufferEscapeString(childBuf, "<idAuth>%s</idAuth>\n", def->id_auth);
virBufferEscapeString(childBuf, "<hostData>%s</hostData>\n", def->host_data);
}
static void
virDomainSecDefFormat(virBuffer *buf, virDomainSecDef *sec)
{
@ -26700,6 +26769,10 @@ virDomainSecDefFormat(virBuffer *buf, virDomainSecDef *sec)
virDomainSEVDefFormat(&attrBuf, &childBuf, &sec->data.sev);
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
virDomainSEVSNPDefFormat(&attrBuf, &childBuf, &sec->data.sev_snp);
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
break;

View File

@ -2860,6 +2860,7 @@ struct _virDomainKeyWrapDef {
typedef enum {
VIR_DOMAIN_LAUNCH_SECURITY_NONE,
VIR_DOMAIN_LAUNCH_SECURITY_SEV,
VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP,
VIR_DOMAIN_LAUNCH_SECURITY_PV,
VIR_DOMAIN_LAUNCH_SECURITY_LAST,
@ -2882,10 +2883,24 @@ struct _virDomainSEVDef {
unsigned int policy;
};
struct _virDomainSEVSNPDef {
virDomainSEVCommonDef common;
unsigned long long policy;
char *guest_visible_workarounds;
char *id_block;
char *id_auth;
char *host_data;
virTristateBool author_key;
virTristateBool vcek;
};
struct _virDomainSecDef {
virDomainLaunchSecurity sectype;
union {
virDomainSEVDef sev;
virDomainSEVSNPDef sev_snp;
} data;
};

View File

@ -1800,6 +1800,47 @@ virDomainDefValidateIOThreads(const virDomainDef *def)
}
#define CHECK_BASE64_LEN(val, elemName, exp_len) \
{ \
size_t len; \
g_autofree unsigned char *tmp = NULL; \
if (val && (tmp = g_base64_decode(val, &len)) && len != exp_len) { \
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, \
_("Unexpected length of '%1$s', expected %2$u got %3$zu"), \
elemName, exp_len, len); \
return -1; \
} \
}
static int
virDomainDefLaunchSecurityValidate(const virDomainDef *def)
{
virDomainSEVSNPDef *sev_snp;
if (!def->sec)
return 0;
switch (def->sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
sev_snp = &def->sec->data.sev_snp;
CHECK_BASE64_LEN(sev_snp->guest_visible_workarounds, "guestVisibleWorkarounds", 16);
CHECK_BASE64_LEN(sev_snp->id_block, "idBlock", 96);
CHECK_BASE64_LEN(sev_snp->id_auth, "idAuth", 4096);
CHECK_BASE64_LEN(sev_snp->host_data, "hostData", 32);
break;
case VIR_DOMAIN_LAUNCH_SECURITY_NONE:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
case VIR_DOMAIN_LAUNCH_SECURITY_LAST:
}
return 0;
}
#undef CHECK_BASE64_LEN
static int
virDomainDefValidateInternal(const virDomainDef *def,
virDomainXMLOption *xmlopt)
@ -1855,6 +1896,9 @@ virDomainDefValidateInternal(const virDomainDef *def,
if (virDomainDefValidateIOThreads(def) < 0)
return -1;
if (virDomainDefLaunchSecurityValidate(def) < 0)
return -1;
return 0;
}

View File

@ -515,6 +515,9 @@
<group>
<ref name="launchSecuritySEV"/>
</group>
<group>
<ref name="launchSecuritySEVSNP"/>
</group>
<group>
<attribute name="type">
<value>s390-pv</value>
@ -569,6 +572,52 @@
</interleave>
</define>
<define name="launchSecuritySEVSNP">
<attribute name="type">
<value>sev-snp</value>
</attribute>
<optional>
<attribute name="kernelHashes">
<ref name="virYesNo"/>
</attribute>
</optional>
<optional>
<attribute name="authorKey">
<ref name="virYesNo"/>
</attribute>
</optional>
<optional>
<attribute name="vcek">
<ref name="virYesNo"/>
</attribute>
</optional>
<interleave>
<ref name="launchSecuritySEVCommon"/>
<element name="policy">
<ref name="hexuint"/>
</element>
<optional>
<element name="guestVisibleWorkarounds">
<data type="string"/>
</element>
</optional>
<optional>
<element name="idBlock">
<data type="string"/>
</element>
</optional>
<optional>
<element name="idAuth">
<data type="string"/>
</element>
</optional>
<optional>
<element name="hostData">
<data type="string"/>
</element>
</optional>
</interleave>
</define>
<!--
Enable or disable perf events for the domain. For each
of the events the following rules apply:

View File

@ -214,6 +214,8 @@ typedef struct _virDomainSEVCommonDef virDomainSEVCommonDef;
typedef struct _virDomainSEVDef virDomainSEVDef;
typedef struct _virDomainSEVSNPDef virDomainSEVSNPDef;
typedef struct _virDomainSecDef virDomainSecDef;
typedef struct _virDomainShmemDef virDomainShmemDef;

View File

@ -848,6 +848,7 @@ qemuSetupDevicesCgroup(virDomainObj *vm)
if (vm->def->sec) {
switch (vm->def->sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
if (qemuSetupSEVCgroup(vm) < 0)
return -1;
break;

View File

@ -7062,6 +7062,8 @@ qemuBuildMachineCommandLine(virCommand *cmd,
virBufferAddLit(&buf, ",memory-encryption=lsec0");
}
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
virBufferAddLit(&buf, ",confidential-guest-support=lsec0");
break;
@ -9781,6 +9783,8 @@ qemuBuildSecCommandLine(virDomainObj *vm, virCommand *cmd,
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
return qemuBuildSEVCommandLine(vm, cmd, &sec->data.sev);
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
return qemuBuildPVCommandLine(vm, cmd);
break;

View File

@ -19128,6 +19128,7 @@ qemuDomainGetLaunchSecurityInfo(virDomainPtr domain,
switch (vm->def->sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
if (qemuDomainGetSEVInfo(vm, params, nparams, flags) < 0)
goto cleanup;
break;

View File

@ -1338,6 +1338,9 @@ qemuFirmwareMatchDomain(const virDomainDef *def,
return false;
}
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
break;
case VIR_DOMAIN_LAUNCH_SECURITY_NONE:

View File

@ -653,6 +653,7 @@ qemuDomainSetupLaunchSecurity(virDomainObj *vm,
switch (sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
VIR_DEBUG("Setting up launch security for SEV");
*paths = g_slist_prepend(*paths, g_strdup(QEMU_DEV_SEV));

View File

@ -6744,6 +6744,7 @@ qemuProcessPrepareDomain(virQEMUDriver *driver,
if (vm->def->sec) {
switch (vm->def->sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
VIR_DEBUG("Updating SEV platform info");
if (qemuProcessUpdateSEVInfo(vm) < 0)
return -1;
@ -6818,6 +6819,8 @@ qemuProcessPrepareLaunchSecurityGuestInput(virDomainObj *vm)
switch (sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
return qemuProcessPrepareSEVGuestInput(vm);
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
return 0;
case VIR_DOMAIN_LAUNCH_SECURITY_NONE:

View File

@ -1325,6 +1325,15 @@ qemuValidateDomainDef(const virDomainDef *def,
return -1;
}
break;
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_SEV_SNP_GUEST)) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
_("SEV SNP launch security is not supported with this QEMU binary"));
return -1;
}
break;
case VIR_DOMAIN_LAUNCH_SECURITY_PV:
if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_MACHINE_CONFIDENTAL_GUEST_SUPPORT) ||
!virQEMUCapsGet(qemuCaps, QEMU_CAPS_S390_PV_GUEST)) {

View File

@ -1954,6 +1954,7 @@ virSecurityDACRestoreAllLabel(virSecurityManager *mgr,
if (def->sec) {
switch (def->sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
if (virSecurityDACRestoreSEVLabel(mgr, def) < 0)
rc = -1;
break;
@ -2187,6 +2188,7 @@ virSecurityDACSetAllLabel(virSecurityManager *mgr,
if (def->sec) {
switch (def->sec->sectype) {
case VIR_DOMAIN_LAUNCH_SECURITY_SEV:
case VIR_DOMAIN_LAUNCH_SECURITY_SEV_SNP:
if (virSecurityDACSetSEVLabel(mgr, def) < 0)
return -1;
break;

View File

@ -0,0 +1,34 @@
LC_ALL=C \
PATH=/bin \
HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1 \
USER=test \
LOGNAME=test \
XDG_DATA_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.local/share \
XDG_CACHE_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.cache \
XDG_CONFIG_HOME=/var/lib/libvirt/qemu/domain--1-QEMUGuest1/.config \
/usr/bin/qemu-system-x86_64 \
-name guest=QEMUGuest1,debug-threads=on \
-S \
-object '{"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain--1-QEMUGuest1/master-key.aes"}' \
-machine pc,usb=off,dump-guest-core=off,memory-backend=pc.ram,acpi=off \
-accel kvm \
-cpu qemu64 \
-m size=219136k \
-object '{"qom-type":"memory-backend-ram","id":"pc.ram","size":224395264}' \
-overcommit mem-lock=off \
-smp 1,sockets=1,cores=1,threads=1 \
-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
-display none \
-no-user-config \
-nodefaults \
-chardev socket,id=charmonitor,fd=1729,server=on,wait=off \
-mon chardev=charmonitor,id=monitor,mode=control \
-rtc base=utc \
-no-shutdown \
-boot strict=on \
-device '{"driver":"piix3-usb-uhci","id":"usb","bus":"pci.0","addr":"0x1.0x2"}' \
-blockdev '{"driver":"host_device","filename":"/dev/HostVG/QEMUGuest1","node-name":"libvirt-1-storage","read-only":false}' \
-device '{"driver":"ide-hd","bus":"ide.0","unit":0,"drive":"libvirt-1-storage","id":"ide0-0-0","bootindex":1}' \
-audiodev '{"id":"audio1","driver":"none"}' \
-sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
-msg timestamp=on

View File

@ -0,0 +1 @@
launch-security-sev-snp.xml

File diff suppressed because one or more lines are too long

View File

@ -2849,6 +2849,8 @@ mymain(void)
QEMU_CAPS_SEV_GUEST,
QEMU_CAPS_LAST);
DO_TEST_CAPS_ARCH_LATEST("launch-security-sev-snp", "x86_64");
DO_TEST_CAPS_ARCH_LATEST("launch-security-s390-pv", "s390x");
DO_TEST_CAPS_LATEST("vhost-user-fs-fd-memory");