From nobody Wed May 15 21:34:59 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) client-ip=170.10.133.124; envelope-from=libvir-list-bounces@redhat.com; helo=us-smtp-delivery-124.mimecast.com; Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com ARC-Seal: i=1; a=rsa-sha256; t=1645098873; cv=none; d=zohomail.com; s=zohoarc; b=IqUGjK3P7eOMVqkzQYAnIoRssM2K6WESgQ5hAUojvW+KLWDa9kzmH4nn8lK8KZanihL+VRuaePAhzNlRZJ4w6pVeaFlcpSHcQH3cTsNyZgzcj6v7CbGZ5rcbhPhtsp3H30xRJJo1i6jVirDPKfnwh+X0Fp4bYsZqYn+aI+J4yVM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1645098873; h=Content-Type:Content-Transfer-Encoding:Date:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:To; bh=h0tPHN5MxXyNoxy1IxAOqkf69g7odk3sJsruL6HP9jQ=; b=lcu2xooCBRXCh17Y7TJ1G0e8dcP8jRKXX4CQuEXIJeEYcjRXwT6BGAMEaNCWdWNrDQ3gTRh4UQFg2wkWKmWoznoA63ZuPhN33YVN78BIe3H5AzY9Iwydh6TldcrxVmxzLrhW7vkLAcEU+FEjyTGFi25b8UUrGWQQwSyq19URehk= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of redhat.com designates 170.10.133.124 as permitted sender) smtp.mailfrom=libvir-list-bounces@redhat.com; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by mx.zohomail.com with SMTPS id 1645098873476357.0482620311534; Thu, 17 Feb 2022 03:54:33 -0800 (PST) Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-488-W6o1YSBmNWSSKQWlllgoJw-1; Thu, 17 Feb 2022 06:54:29 -0500 Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id 2A14B814243; Thu, 17 Feb 2022 11:54:24 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id BBAB97B025; Thu, 17 Feb 2022 11:54:23 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id 080FE4BB7C; Thu, 17 Feb 2022 11:54:23 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id 21HBsLUE026071 for ; Thu, 17 Feb 2022 06:54:21 -0500 Received: by smtp.corp.redhat.com (Postfix) id 74C8073146; Thu, 17 Feb 2022 11:54:21 +0000 (UTC) Received: from localhost.localdomain.com (unknown [10.33.36.132]) by smtp.corp.redhat.com (Postfix) with ESMTP id 88AEC73145; Thu, 17 Feb 2022 11:54:09 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1645098872; h=from:from:sender:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding:list-id:list-help: list-unsubscribe:list-subscribe:list-post; bh=h0tPHN5MxXyNoxy1IxAOqkf69g7odk3sJsruL6HP9jQ=; b=CF8bbjniVl/l0c/Wvakk0fXfOd0PqPcY38vRDF7d0p6hjGYH12WCzfOlo8esTPK+bKY9po yWzXRPRkj6RtUqTEeIczaio4zxvJ9L2ZcUksCoN1tB8VZWg0Zxo1txaVNVMUwVt2lUNBci Ke/uzoAOUETH2MxaodarllP9zteTcas= X-MC-Unique: W6o1YSBmNWSSKQWlllgoJw-1 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= To: libvir-list@redhat.com Subject: [libvirt PATCH] qemu: support parsing firmware descriptor flash 'mode' for optional NVRAM Date: Thu, 17 Feb 2022 11:54:07 +0000 Message-Id: <20220217115407.1782304-1-berrange@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-loop: libvir-list@redhat.com X-BeenThere: libvir-list@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Development discussions about the libvirt library & tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: libvir-list-bounces@redhat.com Errors-To: libvir-list-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 Authentication-Results: relay.mimecast.com; auth=pass smtp.auth=CUSA124A263 smtp.mailfrom=libvir-list-bounces@redhat.com X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-ZohoMail-DKIM: pass (identity @redhat.com) X-ZM-MESSAGEID: 1645098874535100001 Currently the 'nvram_template' entry is mandatory when parsing the firmware descriptor based on flash. QEMU is extending the firmware descriptor spec to make the 'nvram_template' optional, depending on the value of a new 'mode' field: - "split" * "executable" contains read-only CODE * "nvram_template" contains read-write VARS - "combined" * "executable" contains read-write CODE and VARs * "nvram_template" not present, as the "executable" is effectively the template on its own - "stateless" * "executable" contains read-only CODE and VARs * "nvram_template" not present In the latter case, the guest OS can write vars but the firmware will make no attempt to persist them, so any changes will be lost at poweroff. For now we parse this new 'mode' but discard any firmware which is not 'mode=3Dsplit' when matching for a domain. This is the minimum required to have libvirt not break when seeing the new firmware descriptors. Future changes will support the new modes. In the tests we have a mixture of files with and without the mode attribute. Signed-off-by: Daniel P. Berrang=C3=A9 Reviewed-by: Michal Privoznik --- src/qemu/qemu_firmware.c | 79 ++++++++++++++++--- .../share/qemu/firmware/50-ovmf-sb-keys.json | 33 ++++++++ .../out/usr/share/qemu/firmware/61-ovmf.json | 31 ++++++++ .../out/usr/share/qemu/firmware/70-aavmf.json | 28 +++++++ .../qemu/firmware/45-ovmf-sev-stateless.json | 31 ++++++++ .../qemu/firmware/55-ovmf-sb-combined.json | 33 ++++++++ .../usr/share/qemu/firmware/60-ovmf-sb.json | 1 + tests/qemufirmwaretest.c | 31 ++++++-- 8 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 tests/qemufirmwaredata/out/usr/share/qemu/firmware/50-o= vmf-sb-keys.json create mode 100644 tests/qemufirmwaredata/out/usr/share/qemu/firmware/61-o= vmf.json create mode 100644 tests/qemufirmwaredata/out/usr/share/qemu/firmware/70-a= avmf.json create mode 100644 tests/qemufirmwaredata/usr/share/qemu/firmware/45-ovmf-= sev-stateless.json create mode 100644 tests/qemufirmwaredata/usr/share/qemu/firmware/55-ovmf-= sb-combined.json diff --git a/src/qemu/qemu_firmware.c b/src/qemu/qemu_firmware.c index ff364996b8..e403ee98e4 100644 --- a/src/qemu/qemu_firmware.c +++ b/src/qemu/qemu_firmware.c @@ -59,6 +59,22 @@ VIR_ENUM_IMPL(qemuFirmwareOSInterface, ); =20 =20 +typedef enum { + QEMU_FIRMWARE_FLASH_MODE_SPLIT, + QEMU_FIRMWARE_FLASH_MODE_COMBINED, + QEMU_FIRMWARE_FLASH_MODE_STATELESS, + + QEMU_FIRMWARE_FLASH_MODE_LAST, +} qemuFirmwareFlashMode; + +VIR_ENUM_DECL(qemuFirmwareFlashMode); +VIR_ENUM_IMPL(qemuFirmwareFlashMode, + QEMU_FIRMWARE_FLASH_MODE_LAST, + "split", + "combined", + "stateless", +); + typedef struct _qemuFirmwareFlashFile qemuFirmwareFlashFile; struct _qemuFirmwareFlashFile { char *filename; @@ -68,6 +84,7 @@ struct _qemuFirmwareFlashFile { =20 typedef struct _qemuFirmwareMappingFlash qemuFirmwareMappingFlash; struct _qemuFirmwareMappingFlash { + qemuFirmwareFlashMode mode; qemuFirmwareFlashFile executable; qemuFirmwareFlashFile nvram_template; }; @@ -359,9 +376,31 @@ qemuFirmwareMappingFlashParse(const char *path, virJSONValue *doc, qemuFirmwareMappingFlash *flash) { + virJSONValue *mode; virJSONValue *executable; virJSONValue *nvram_template; =20 + if (!(mode =3D virJSONValueObjectGet(doc, "mode"))) { + /* Historical default */ + flash->mode =3D QEMU_FIRMWARE_FLASH_MODE_SPLIT; + } else { + const char *modestr =3D virJSONValueGetString(mode); + int modeval; + if (!modestr) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("Firmware flash mode value was malformed")); + return -1; + } + modeval =3D qemuFirmwareFlashModeTypeFromString(modestr); + if (modeval < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Firmware flash mode value '%s' unexpected"), + modestr); + return -1; + } + flash->mode =3D modeval; + } + if (!(executable =3D virJSONValueObjectGet(doc, "executable"))) { virReportError(VIR_ERR_INTERNAL_ERROR, _("missing 'executable' in '%s'"), @@ -372,15 +411,17 @@ qemuFirmwareMappingFlashParse(const char *path, if (qemuFirmwareFlashFileParse(path, executable, &flash->executable) <= 0) return -1; =20 - if (!(nvram_template =3D virJSONValueObjectGet(doc, "nvram-template"))= ) { - virReportError(VIR_ERR_INTERNAL_ERROR, - _("missing 'nvram-template' in '%s'"), - path); - return -1; - } + if (flash->mode =3D=3D QEMU_FIRMWARE_FLASH_MODE_SPLIT) { + if (!(nvram_template =3D virJSONValueObjectGet(doc, "nvram-templat= e"))) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("missing 'nvram-template' in '%s'"), + path); + return -1; + } =20 - if (qemuFirmwareFlashFileParse(path, nvram_template, &flash->nvram_tem= plate) < 0) - return -1; + if (qemuFirmwareFlashFileParse(path, nvram_template, &flash->nvram= _template) < 0) + return -1; + } =20 return 0; } @@ -693,10 +734,12 @@ qemuFirmwareMappingFlashFormat(virJSONValue *mapping, g_autoptr(virJSONValue) executable =3D NULL; g_autoptr(virJSONValue) nvram_template =3D NULL; =20 - if (!(executable =3D qemuFirmwareFlashFileFormat(flash->executable))) + if (virJSONValueObjectAppendString(mapping, + "mode", + qemuFirmwareFlashModeTypeToString(f= lash->mode)) < 0) return -1; =20 - if (!(nvram_template =3D qemuFirmwareFlashFileFormat(flash->nvram_temp= late))) + if (!(executable =3D qemuFirmwareFlashFileFormat(flash->executable))) return -1; =20 if (virJSONValueObjectAppend(mapping, @@ -704,11 +747,15 @@ qemuFirmwareMappingFlashFormat(virJSONValue *mapping, &executable) < 0) return -1; =20 + if (flash->mode =3D=3D QEMU_FIRMWARE_FLASH_MODE_SPLIT) { + if (!(nvram_template =3D qemuFirmwareFlashFileFormat(flash->nvram_= template))) + return -1; =20 - if (virJSONValueObjectAppend(mapping, + if (virJSONValueObjectAppend(mapping, "nvram-template", - &nvram_template) < 0) - return -1; + &nvram_template) < 0) + return -1; + } =20 return 0; } @@ -1070,6 +1117,12 @@ qemuFirmwareMatchDomain(const virDomainDef *def, return false; } =20 + if (fw->mapping.device =3D=3D QEMU_FIRMWARE_DEVICE_FLASH && + fw->mapping.data.flash.mode !=3D QEMU_FIRMWARE_FLASH_MODE_SPLIT) { + VIR_DEBUG("Discarding loader without split flash"); + return false; + } + if (def->sec) { switch ((virDomainLaunchSecurity) def->sec->sectype) { case VIR_DOMAIN_LAUNCH_SECURITY_SEV: diff --git a/tests/qemufirmwaredata/out/usr/share/qemu/firmware/50-ovmf-sb-= keys.json b/tests/qemufirmwaredata/out/usr/share/qemu/firmware/50-ovmf-sb-k= eys.json new file mode 100644 index 0000000000..c251682cd9 --- /dev/null +++ b/tests/qemufirmwaredata/out/usr/share/qemu/firmware/50-ovmf-sb-keys.js= on @@ -0,0 +1,33 @@ +{ + "interface-types": [ + "uefi" + ], + "mapping": { + "device": "flash", + "mode": "split", + "executable": { + "filename": "/usr/share/OVMF/OVMF_CODE.secboot.fd", + "format": "raw" + }, + "nvram-template": { + "filename": "/usr/share/OVMF/OVMF_VARS.secboot.fd", + "format": "raw" + } + }, + "targets": [ + { + "architecture": "x86_64", + "machines": [ + "pc-q35-*" + ] + } + ], + "features": [ + "acpi-s3", + "amd-sev", + "enrolled-keys", + "requires-smm", + "secure-boot", + "verbose-dynamic" + ] +} diff --git a/tests/qemufirmwaredata/out/usr/share/qemu/firmware/61-ovmf.jso= n b/tests/qemufirmwaredata/out/usr/share/qemu/firmware/61-ovmf.json new file mode 100644 index 0000000000..2a9aa23efb --- /dev/null +++ b/tests/qemufirmwaredata/out/usr/share/qemu/firmware/61-ovmf.json @@ -0,0 +1,31 @@ +{ + "interface-types": [ + "uefi" + ], + "mapping": { + "device": "flash", + "mode": "split", + "executable": { + "filename": "/usr/share/OVMF/OVMF_CODE.fd", + "format": "raw" + }, + "nvram-template": { + "filename": "/usr/share/OVMF/OVMF_VARS.fd", + "format": "raw" + } + }, + "targets": [ + { + "architecture": "x86_64", + "machines": [ + "pc-i440fx-*", + "pc-q35-*" + ] + } + ], + "features": [ + "acpi-s3", + "amd-sev", + "verbose-dynamic" + ] +} diff --git a/tests/qemufirmwaredata/out/usr/share/qemu/firmware/70-aavmf.js= on b/tests/qemufirmwaredata/out/usr/share/qemu/firmware/70-aavmf.json new file mode 100644 index 0000000000..9bd5ac2868 --- /dev/null +++ b/tests/qemufirmwaredata/out/usr/share/qemu/firmware/70-aavmf.json @@ -0,0 +1,28 @@ +{ + "interface-types": [ + "uefi" + ], + "mapping": { + "device": "flash", + "mode": "split", + "executable": { + "filename": "/usr/share/AAVMF/AAVMF_CODE.fd", + "format": "raw" + }, + "nvram-template": { + "filename": "/usr/share/AAVMF/AAVMF_VARS.fd", + "format": "raw" + } + }, + "targets": [ + { + "architecture": "aarch64", + "machines": [ + "virt-*" + ] + } + ], + "features": [ + + ] +} diff --git a/tests/qemufirmwaredata/usr/share/qemu/firmware/45-ovmf-sev-sta= teless.json b/tests/qemufirmwaredata/usr/share/qemu/firmware/45-ovmf-sev-st= ateless.json new file mode 100644 index 0000000000..5a619f3ab0 --- /dev/null +++ b/tests/qemufirmwaredata/usr/share/qemu/firmware/45-ovmf-sev-stateless.= json @@ -0,0 +1,31 @@ +{ + "description": "OVMF for x86_64, with SEV, without SB, without SMM, wi= th NO varstore", + "interface-types": [ + "uefi" + ], + "mapping": { + "device": "flash", + "mode": "stateless", + "executable": { + "filename": "/usr/share/OVMF/OVMF.sev.fd", + "format": "raw" + } + }, + "targets": [ + { + "architecture": "x86_64", + "machines": [ + "pc-q35-*" + ] + } + ], + "features": [ + "acpi-s3", + "amd-sev", + "amd-sev-es", + "verbose-dynamic" + ], + "tags": [ + + ] +} diff --git a/tests/qemufirmwaredata/usr/share/qemu/firmware/55-ovmf-sb-comb= ined.json b/tests/qemufirmwaredata/usr/share/qemu/firmware/55-ovmf-sb-combi= ned.json new file mode 100644 index 0000000000..eb3332e4ab --- /dev/null +++ b/tests/qemufirmwaredata/usr/share/qemu/firmware/55-ovmf-sb-combined.js= on @@ -0,0 +1,33 @@ +{ + "description": "OVMF with SB+SMM, SB enabled, MS certs enrolled", + "interface-types": [ + "uefi" + ], + "mapping": { + "device": "flash", + "mode": "combined", + "executable": { + "filename": "/usr/share/OVMF/OVMF.secboot.fd", + "format": "raw" + } + }, + "targets": [ + { + "architecture": "x86_64", + "machines": [ + "pc-q35-*" + ] + } + ], + "features": [ + "acpi-s3", + "amd-sev", + "enrolled-keys", + "requires-smm", + "secure-boot", + "verbose-dynamic" + ], + "tags": [ + + ] +} diff --git a/tests/qemufirmwaredata/usr/share/qemu/firmware/60-ovmf-sb.json= b/tests/qemufirmwaredata/usr/share/qemu/firmware/60-ovmf-sb.json index 5e8a94ae78..a5273a5e8b 100644 --- a/tests/qemufirmwaredata/usr/share/qemu/firmware/60-ovmf-sb.json +++ b/tests/qemufirmwaredata/usr/share/qemu/firmware/60-ovmf-sb.json @@ -5,6 +5,7 @@ ], "mapping": { "device": "flash", + "mode": "split", "executable": { "filename": "/usr/share/OVMF/OVMF_CODE.secboot.fd", "format": "raw" diff --git a/tests/qemufirmwaretest.c b/tests/qemufirmwaretest.c index cad4b6d383..fc3416b2ae 100644 --- a/tests/qemufirmwaretest.c +++ b/tests/qemufirmwaretest.c @@ -17,22 +17,31 @@ static int testParseFormatFW(const void *opaque) { const char *filename =3D opaque; - g_autofree char *path =3D NULL; + g_autofree char *inpath =3D NULL; + g_autofree char *outpath =3D NULL; g_autoptr(qemuFirmware) fw =3D NULL; - g_autofree char *buf =3D NULL; g_autoptr(virJSONValue) json =3D NULL; g_autofree char *expected =3D NULL; g_autofree char *actual =3D NULL; + g_autofree char *buf =3D NULL; =20 - path =3D g_strdup_printf("%s/qemufirmwaredata/%s", abs_srcdir, filenam= e); + inpath =3D g_strdup_printf("%s/qemufirmwaredata/%s", abs_srcdir, filen= ame); + outpath =3D g_strdup_printf("%s/qemufirmwaredata/out/%s", abs_srcdir, = filename); =20 - if (!(fw =3D qemuFirmwareParse(path))) + if (!(fw =3D qemuFirmwareParse(inpath))) return -1; =20 - if (virFileReadAll(path, - 1024 * 1024, /* 1MiB */ - &buf) < 0) - return -1; + if (virFileExists(outpath)) { + if (virFileReadAll(outpath, + 1024 * 1024, /* 1MiB */ + &buf) < 0) + return -1; + } else { + if (virFileReadAll(inpath, + 1024 * 1024, /* 1MiB */ + &buf) < 0) + return -1; + } =20 if (!(json =3D virJSONValueFromString(buf))) return -1; @@ -60,7 +69,9 @@ testFWPrecedence(const void *opaque G_GNUC_UNUSED) const char *expected[] =3D { PREFIX "/share/qemu/firmware/40-bios.json", SYSCONFDIR "/qemu/firmware/40-ovmf-sb-keys.json", + PREFIX "/share/qemu/firmware/45-ovmf-sev-stateless.json", PREFIX "/share/qemu/firmware/50-ovmf-sb-keys.json", + PREFIX "/share/qemu/firmware/55-ovmf-sb-combined.json", PREFIX "/share/qemu/firmware/61-ovmf.json", PREFIX "/share/qemu/firmware/70-aavmf.json", NULL @@ -218,7 +229,9 @@ mymain(void) } while (0) =20 DO_PARSE_TEST("usr/share/qemu/firmware/40-bios.json"); + DO_PARSE_TEST("usr/share/qemu/firmware/45-ovmf-sev-stateless.json"); DO_PARSE_TEST("usr/share/qemu/firmware/50-ovmf-sb-keys.json"); + DO_PARSE_TEST("usr/share/qemu/firmware/55-ovmf-sb-combined.json"); DO_PARSE_TEST("usr/share/qemu/firmware/60-ovmf-sb.json"); DO_PARSE_TEST("usr/share/qemu/firmware/61-ovmf.json"); DO_PARSE_TEST("usr/share/qemu/firmware/70-aavmf.json"); @@ -250,6 +263,8 @@ mymain(void) DO_SUPPORTED_TEST("pc-q35-3.1", VIR_ARCH_X86_64, true, "/usr/share/seabios/bios-256k.bin:NULL:" "/usr/share/OVMF/OVMF_CODE.secboot.fd:/usr/share/OVM= F/OVMF_VARS.secboot.fd:" + "/usr/share/OVMF/OVMF.sev.fd:NULL:" + "/usr/share/OVMF/OVMF.secboot.fd:NULL:" "/usr/share/OVMF/OVMF_CODE.fd:/usr/share/OVMF/OVMF_V= ARS.fd", VIR_DOMAIN_OS_DEF_FIRMWARE_BIOS, VIR_DOMAIN_OS_DEF_FIRMWARE_EFI); --=20 2.34.1