aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Hajnoczi <stefanha@redhat.com>2025-03-05 21:53:36 +0800
committerStefan Hajnoczi <stefanha@redhat.com>2025-03-05 21:53:36 +0800
commit50aa3d0984d8a4a9c39d34e2f81e8a70674462e4 (patch)
tree51589eac6f145fadea42f30583967a9f434342d9
parentb93c9dfd700ae91c0080694f53c281ef51b0d028 (diff)
parent2bc10b15deb4b29391628e10b18701bfbcf4be17 (diff)
downloadqemu-50aa3d0984d8a4a9c39d34e2f81e8a70674462e4.zip
qemu-50aa3d0984d8a4a9c39d34e2f81e8a70674462e4.tar.gz
qemu-50aa3d0984d8a4a9c39d34e2f81e8a70674462e4.tar.bz2
Merge tag 'firmware-20250304-pull-request' of https://gitlab.com/kraxel/qemu into staging
- add uefi variable store support # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmfG9m4ACgkQTLbY7tPo # cTgk8BAAn60ezSx2iet/JarkMugacOJ6C2UbVQho/Q3WCyrQ7K+F0NByczcfKLA6 # OZX84p93qxiK8KJ9tva41eOIYViyfsKf+wGUInNCbXbyGy7RZV3SjE5Yuk9BE9Ta # 8f/5dDGyCELQWliy4atLUWl2dL0rQ76twLZewYo9n2A+LijIzjuP+kVJeccK8U7A # qStio3rGZ0vul2OYhE3+veSXd2m2oU32Tce31MUoj9yCbTE1RZSKMXbwbUU/nul9 # RN3X0q4rvXKwbKMUdC+YI+oIzY/1nzrmy5zwwbJsAszsSKjAc2LZeoDqKdbOIynL # B01dorpg5pVxQUqHz1t+YTfGyuZaYDM6WsaGoU5/9QLW7ZbI857EULq7ptE3DVAS # YjHiBYqiiYYrCatV4UT1XjkRjX7W8lTdK2M+8Vh1E5b1pGpfPwuKE4YRGwMMK0Ac # 5LD9HMxnXIDOT9A6+tGc6GYLfT7YToFA3pHn6WdLlGSowB7sYVZy0/xGe3ABjvzt # WOl1WDWtHCpYIiROpEl+KkbRilwvbLF/IW7x0Ovfsjyh5ucBFu6ojxgRBcOee4Na # oeBz5GfpeIoelhWl1aSYIUrFCvN2Q/9EafHRsfTzPoKlD3t/7oLNYtMYloiQpsks # IPpD5OMMmWGaD2G76Nw24nS4+zUf4Gagg6+IAlYt6zjqnmxFWxY= # =HnUt # -----END PGP SIGNATURE----- # gpg: Signature made Tue 04 Mar 2025 20:47:42 HKT # gpg: using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138 # gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full] # gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" [full] # gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full] # Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138 * tag 'firmware-20250304-pull-request' of https://gitlab.com/kraxel/qemu: (24 commits) docs: add uefi variable service documentation hw/uefi: add MAINTAINERS entry hw/uefi-vars-sysbus: allow for pc and q35 hw/uefi-vars-sysbus: allow for arm virt hw/uefi-vars-sysbus: add x64 variant hw/uefi-vars-sysbus: qemu platform bus support hw/uefi: add uefi-vars-sysbus device hw/uefi: add to meson hw/uefi: add UEFI_VARS to Kconfig hw/uefi: add trace-events hw/uefi: add var-service-json.c + qapi for NV vars. hw/uefi: add var-service-siglist.c hw/uefi: add var-service-pkcs7-stub.c hw/uefi: add var-service-pkcs7.c hw/uefi: add var-service-core.c hw/uefi: add var-service-policy.c hw/uefi: add var-service-auth.c hw/uefi: add var-service-vars.c hw/uefi: add var-service-utils.c hw/uefi: add var-service-guid.c ... Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
-rw-r--r--MAINTAINERS6
-rw-r--r--docs/devel/index-internals.rst1
-rw-r--r--docs/devel/uefi-vars.rst68
-rw-r--r--hw/Kconfig1
-rw-r--r--hw/arm/virt.c2
-rw-r--r--hw/core/sysbus-fdt.c24
-rw-r--r--hw/i386/pc_piix.c2
-rw-r--r--hw/i386/pc_q35.c2
-rw-r--r--hw/meson.build1
-rw-r--r--hw/uefi/Kconfig3
-rw-r--r--hw/uefi/LIMITATIONS.md7
-rw-r--r--hw/uefi/hardware-info.c31
-rw-r--r--hw/uefi/meson.build21
-rw-r--r--hw/uefi/trace-events17
-rw-r--r--hw/uefi/var-service-auth.c361
-rw-r--r--hw/uefi/var-service-core.c321
-rw-r--r--hw/uefi/var-service-guid.c99
-rw-r--r--hw/uefi/var-service-json.c243
-rw-r--r--hw/uefi/var-service-pkcs7-stub.c16
-rw-r--r--hw/uefi/var-service-pkcs7.c436
-rw-r--r--hw/uefi/var-service-policy.c370
-rw-r--r--hw/uefi/var-service-siglist.c212
-rw-r--r--hw/uefi/var-service-sysbus.c124
-rw-r--r--hw/uefi/var-service-utils.c241
-rw-r--r--hw/uefi/var-service-vars.c725
-rw-r--r--include/hw/uefi/hardware-info.h35
-rw-r--r--include/hw/uefi/var-service-api.h48
-rw-r--r--include/hw/uefi/var-service-edk2.h227
-rw-r--r--include/hw/uefi/var-service.h191
-rw-r--r--meson.build1
-rw-r--r--qapi/meson.build1
-rw-r--r--qapi/qapi-schema.json1
-rw-r--r--qapi/uefi.json64
33 files changed, 3902 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index 2e7fc6f..27cdfbe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2820,6 +2820,12 @@ F: hw/misc/ivshmem-flat.c
F: include/hw/misc/ivshmem-flat.h
F: docs/system/devices/ivshmem-flat.rst
+UEFI variable service
+M: Gerd Hoffmann <kraxel@redhat.com>
+S: Maintained
+F: hw/uefi/
+F: include/hw/uefi/
+
Subsystems
----------
Overall Audio backends
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst
index bca597c..7a0678c 100644
--- a/docs/devel/index-internals.rst
+++ b/docs/devel/index-internals.rst
@@ -20,6 +20,7 @@ Details about QEMU's various subsystems including how to add features to them.
s390-cpu-topology
s390-dasd-ipl
tracing
+ uefi-vars
vfio-iommufd
writing-monitor-commands
virtio-backends
diff --git a/docs/devel/uefi-vars.rst b/docs/devel/uefi-vars.rst
new file mode 100644
index 0000000..0151a26
--- /dev/null
+++ b/docs/devel/uefi-vars.rst
@@ -0,0 +1,68 @@
+==============
+UEFI variables
+==============
+
+Guest UEFI variable management
+==============================
+
+The traditional approach for UEFI Variable storage in qemu guests is
+to work as close as possible to physical hardware. That means
+providing pflash as storage and leaving the management of variables
+and flash to the guest.
+
+Secure boot support comes with the requirement that the UEFI variable
+storage must be protected against direct access by the OS. All update
+requests must pass the sanity checks. (Parts of) the firmware must
+run with a higher privilege level than the OS so this can be enforced
+by the firmware. On x86 this has been implemented using System
+Management Mode (SMM) in qemu and kvm, which again is the same
+approach taken by physical hardware. Only privileged code running in
+SMM mode is allowed to access flash storage.
+
+Communication with the firmware code running in SMM mode works by
+serializing the requests to a shared buffer, then trapping into SMM
+mode via SMI. The SMM code processes the request, stores the reply in
+the same buffer and returns.
+
+Host UEFI variable service
+==========================
+
+Instead of running the privileged code inside the guest we can run it
+on the host. The serialization protocol can be reused. The
+communication with the host uses a virtual device, which essentially
+configures the shared buffer location and size, and traps to the host
+to process the requests.
+
+The ``uefi-vars`` device implements the UEFI virtual device. It comes
+in ``uefi-vars-x86`` and ``uefi-vars-sysbus`` flavours. The device
+reimplements the handlers needed, specifically
+``EfiSmmVariableProtocol`` and ``VarCheckPolicyLibMmiHandler``. It
+also consumes events (``EfiEndOfDxeEventGroup``,
+``EfiEventReadyToBoot`` and ``EfiEventExitBootServices``).
+
+The advantage of the approach is that we do not need a special
+privilege level for the firmware to protect itself, i.e. it does not
+depend on SMM emulation on x64, which allows the removal of a bunch of
+complex code for SMM emulation from the linux kernel
+(CONFIG_KVM_SMM=n). It also allows support for secure boot on arm
+without implementing secure world (el3) emulation in kvm.
+
+Of course there are also downsides. The added device increases the
+attack surface of the host, and we are adding some code duplication
+because we have to reimplement some edk2 functionality in qemu.
+
+usage on x86_64
+---------------
+
+.. code::
+
+ qemu-system-x86_64 \
+ -device uefi-vars-x86,jsonfile=/path/to/vars.json
+
+usage on aarch64
+----------------
+
+.. code::
+
+ qemu-system-aarch64 -M virt \
+ -device uefi-vars-sysbus,jsonfile=/path/to/vars.json
diff --git a/hw/Kconfig b/hw/Kconfig
index 1b4e9bb..c4dfe2e 100644
--- a/hw/Kconfig
+++ b/hw/Kconfig
@@ -37,6 +37,7 @@ source smbios/Kconfig
source ssi/Kconfig
source timer/Kconfig
source tpm/Kconfig
+source uefi/Kconfig
source ufs/Kconfig
source usb/Kconfig
source virtio/Kconfig
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index ee69081..904c698 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -82,6 +82,7 @@
#include "hw/mem/pc-dimm.h"
#include "hw/mem/nvdimm.h"
#include "hw/acpi/generic_event_device.h"
+#include "hw/uefi/var-service-api.h"
#include "hw/virtio/virtio-md-pci.h"
#include "hw/virtio/virtio-iommu.h"
#include "hw/char/pl011.h"
@@ -3162,6 +3163,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data)
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_AMD_XGBE);
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_PLATFORM);
+ machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS);
#ifdef CONFIG_TPM
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);
#endif
diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c
index 774c0ae..e85066b 100644
--- a/hw/core/sysbus-fdt.c
+++ b/hw/core/sysbus-fdt.c
@@ -36,6 +36,7 @@
#include "hw/vfio/vfio-calxeda-xgmac.h"
#include "hw/vfio/vfio-amd-xgbe.h"
#include "hw/display/ramfb.h"
+#include "hw/uefi/var-service-api.h"
#include "hw/arm/fdt.h"
/*
@@ -471,6 +472,28 @@ static int add_tpm_tis_fdt_node(SysBusDevice *sbdev, void *opaque)
}
#endif
+static int add_uefi_vars_node(SysBusDevice *sbdev, void *opaque)
+{
+ PlatformBusFDTData *data = opaque;
+ PlatformBusDevice *pbus = data->pbus;
+ const char *parent_node = data->pbus_node_name;
+ void *fdt = data->fdt;
+ uint64_t mmio_base;
+ char *nodename;
+
+ mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0);
+ nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node,
+ UEFI_VARS_FDT_NODE, mmio_base);
+ qemu_fdt_add_subnode(fdt, nodename);
+ qemu_fdt_setprop_string(fdt, nodename,
+ "compatible", UEFI_VARS_FDT_COMPAT);
+ qemu_fdt_setprop_sized_cells(fdt, nodename, "reg",
+ 1, mmio_base,
+ 1, UEFI_VARS_REGS_SIZE);
+ g_free(nodename);
+ return 0;
+}
+
static int no_fdt_node(SysBusDevice *sbdev, void *opaque)
{
return 0;
@@ -495,6 +518,7 @@ static const BindingEntry bindings[] = {
TYPE_BINDING(TYPE_TPM_TIS_SYSBUS, add_tpm_tis_fdt_node),
#endif
TYPE_BINDING(TYPE_RAMFB_DEVICE, no_fdt_node),
+ TYPE_BINDING(TYPE_UEFI_VARS_SYSBUS, add_uefi_vars_node),
TYPE_BINDING("", NULL), /* last element */
};
diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
index 04d2957..6c91e2d 100644
--- a/hw/i386/pc_piix.c
+++ b/hw/i386/pc_piix.c
@@ -65,6 +65,7 @@
#include "system/numa.h"
#include "hw/hyperv/vmbus-bridge.h"
#include "hw/mem/nvdimm.h"
+#include "hw/uefi/var-service-api.h"
#include "hw/i386/acpi-build.h"
#include "target/i386/cpu.h"
@@ -468,6 +469,7 @@ static void pc_i440fx_machine_options(MachineClass *m)
m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
+ machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64);
object_class_property_add_enum(oc, "x-south-bridge", "PCSouthBridgeOption",
&PCSouthBridgeOption_lookup,
diff --git a/hw/i386/pc_q35.c b/hw/i386/pc_q35.c
index 77536dd..fd96d03 100644
--- a/hw/i386/pc_q35.c
+++ b/hw/i386/pc_q35.c
@@ -58,6 +58,7 @@
#include "system/numa.h"
#include "hw/hyperv/vmbus-bridge.h"
#include "hw/mem/nvdimm.h"
+#include "hw/uefi/var-service-api.h"
#include "hw/i386/acpi-build.h"
#include "target/i386/cpu.h"
@@ -355,6 +356,7 @@ static void pc_q35_machine_options(MachineClass *m)
machine_class_allow_dynamic_sysbus_dev(m, TYPE_INTEL_IOMMU_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
+ machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64);
compat_props_add(m->compat_props,
pc_q35_compat_defaults, pc_q35_compat_defaults_len);
}
diff --git a/hw/meson.build b/hw/meson.build
index b827c82..138f5d5 100644
--- a/hw/meson.build
+++ b/hw/meson.build
@@ -35,6 +35,7 @@ subdir('smbios')
subdir('ssi')
subdir('timer')
subdir('tpm')
+subdir('uefi')
subdir('ufs')
subdir('usb')
subdir('vfio')
diff --git a/hw/uefi/Kconfig b/hw/uefi/Kconfig
new file mode 100644
index 0000000..ca6c2bc
--- /dev/null
+++ b/hw/uefi/Kconfig
@@ -0,0 +1,3 @@
+config UEFI_VARS
+ bool
+ default y if X86_64 || AARCH64
diff --git a/hw/uefi/LIMITATIONS.md b/hw/uefi/LIMITATIONS.md
new file mode 100644
index 0000000..29308bd
--- /dev/null
+++ b/hw/uefi/LIMITATIONS.md
@@ -0,0 +1,7 @@
+known issues and limitations
+----------------------------
+
+* works only on little endian hosts
+ - accessing structs in guest ram is done without endian conversion.
+* works only for 64-bit guests
+ - UINTN is mapped to uint64_t, for 32-bit guests that would be uint32_t
diff --git a/hw/uefi/hardware-info.c b/hw/uefi/hardware-info.c
new file mode 100644
index 0000000..930502a
--- /dev/null
+++ b/hw/uefi/hardware-info.c
@@ -0,0 +1,31 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * pass hardware information to uefi
+ *
+ * see OvmfPkg/Library/HardwareInfoLib/ in edk2
+ */
+
+#include "qemu/osdep.h"
+
+#include "hw/nvram/fw_cfg.h"
+#include "hw/uefi/hardware-info.h"
+
+static void *blob;
+static uint64_t blobsize;
+
+void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t infosize)
+{
+ HARDWARE_INFO_HEADER hdr = {
+ .type.value = cpu_to_le64(type),
+ .size = cpu_to_le64(infosize),
+ };
+
+ blob = g_realloc(blob, blobsize + sizeof(hdr) + infosize);
+ memcpy(blob + blobsize, &hdr, sizeof(hdr));
+ blobsize += sizeof(hdr);
+ memcpy(blob + blobsize, info, infosize);
+ blobsize += infosize;
+
+ fw_cfg_modify_file(fw_cfg_find(), "etc/hardware-info", blob, blobsize);
+}
diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build
new file mode 100644
index 0000000..91eb95f
--- /dev/null
+++ b/hw/uefi/meson.build
@@ -0,0 +1,21 @@
+system_ss.add(files('hardware-info.c'))
+
+uefi_vars_ss = ss.source_set()
+if (config_all_devices.has_key('CONFIG_UEFI_VARS'))
+ uefi_vars_ss.add(files('var-service-core.c',
+ 'var-service-json.c',
+ 'var-service-vars.c',
+ 'var-service-auth.c',
+ 'var-service-guid.c',
+ 'var-service-utils.c',
+ 'var-service-policy.c',
+ 'var-service-sysbus.c'))
+ uefi_vars_ss.add(when: gnutls,
+ if_true: files('var-service-pkcs7.c'),
+ if_false: files('var-service-pkcs7-stub.c'))
+ uefi_vars_ss.add(files('var-service-siglist.c'))
+endif
+
+modules += { 'hw-uefi' : {
+ 'vars' : uefi_vars_ss,
+}}
diff --git a/hw/uefi/trace-events b/hw/uefi/trace-events
new file mode 100644
index 0000000..3694712
--- /dev/null
+++ b/hw/uefi/trace-events
@@ -0,0 +1,17 @@
+# device
+uefi_reg_read(uint64_t addr, unsigned size) "addr 0x%" PRIx64 ", size %u"
+uefi_reg_write(uint64_t addr, uint64_t val, unsigned size) "addr 0x%" PRIx64 ", val 0x%" PRIx64 ", size %d"
+uefi_hard_reset(void) ""
+
+# generic uefi
+uefi_variable(const char *context, const char *name, uint64_t size, const char *uuid) "context %s, name %s, size %" PRIu64 ", uuid %s"
+uefi_status(const char *context, const char *name) "context %s, status %s"
+uefi_event(const char *name) "event %s"
+
+# variable protocol
+uefi_vars_proto_cmd(const char *cmd) "cmd %s"
+uefi_vars_security_violation(const char *reason) "reason %s"
+
+# variable policy protocol
+uefi_vars_policy_cmd(const char *cmd) "cmd %s"
+uefi_vars_policy_deny(const char *reason) "reason %s"
diff --git a/hw/uefi/var-service-auth.c b/hw/uefi/var-service-auth.c
new file mode 100644
index 0000000..fba5a09
--- /dev/null
+++ b/hw/uefi/var-service-auth.c
@@ -0,0 +1,361 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - AuthVariableLib
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "system/dma.h"
+
+#include "hw/uefi/var-service.h"
+
+static const uint16_t name_pk[] = u"PK";
+static const uint16_t name_kek[] = u"KEK";
+static const uint16_t name_db[] = u"db";
+static const uint16_t name_dbx[] = u"dbx";
+static const uint16_t name_setup_mode[] = u"SetupMode";
+static const uint16_t name_sigs_support[] = u"SignatureSupport";
+static const uint16_t name_sb[] = u"SecureBoot";
+static const uint16_t name_sb_enable[] = u"SecureBootEnable";
+static const uint16_t name_custom_mode[] = u"CustomMode";
+static const uint16_t name_vk[] = u"VendorKeys";
+static const uint16_t name_vk_nv[] = u"VendorKeysNv";
+
+static const uint32_t sigdb_attrs =
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS |
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS;
+
+static void set_secure_boot(uefi_vars_state *uv, uint8_t sb)
+{
+ uefi_vars_set_variable(uv, EfiGlobalVariable,
+ name_sb, sizeof(name_sb),
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS,
+ &sb, sizeof(sb));
+}
+
+static void set_secure_boot_enable(uefi_vars_state *uv, uint8_t sbe)
+{
+ uefi_vars_set_variable(uv, EfiSecureBootEnableDisable,
+ name_sb_enable, sizeof(name_sb_enable),
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS,
+ &sbe, sizeof(sbe));
+}
+
+static void set_setup_mode(uefi_vars_state *uv, uint8_t sm)
+{
+ uefi_vars_set_variable(uv, EfiGlobalVariable,
+ name_setup_mode, sizeof(name_setup_mode),
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS,
+ &sm, sizeof(sm));
+}
+
+static void set_custom_mode(uefi_vars_state *uv, uint8_t cm)
+{
+ uefi_vars_set_variable(uv, EfiCustomModeEnable,
+ name_custom_mode, sizeof(name_custom_mode),
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS,
+ &cm, sizeof(cm));
+}
+
+static void set_signature_support(uefi_vars_state *uv)
+{
+ QemuUUID sigs_support[5];
+
+ sigs_support[0] = EfiCertSha256Guid;
+ sigs_support[1] = EfiCertSha384Guid;
+ sigs_support[2] = EfiCertSha512Guid;
+ sigs_support[3] = EfiCertRsa2048Guid;
+ sigs_support[4] = EfiCertX509Guid;
+
+ uefi_vars_set_variable(uv, EfiGlobalVariable,
+ name_sigs_support, sizeof(name_sigs_support),
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS,
+ sigs_support, sizeof(sigs_support));
+}
+
+static bool setup_mode_is_active(uefi_vars_state *uv)
+{
+ uefi_variable *var;
+ uint8_t *value;
+
+ var = uefi_vars_find_variable(uv, EfiGlobalVariable,
+ name_setup_mode, sizeof(name_setup_mode));
+ if (var) {
+ value = var->data;
+ if (value[0] == SETUP_MODE) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool custom_mode_is_active(uefi_vars_state *uv)
+{
+ uefi_variable *var;
+ uint8_t *value;
+
+ var = uefi_vars_find_variable(uv, EfiCustomModeEnable,
+ name_custom_mode, sizeof(name_custom_mode));
+ if (var) {
+ value = var->data;
+ if (value[0] == CUSTOM_SECURE_BOOT_MODE) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool uefi_vars_is_sb_pk(uefi_variable *var)
+{
+ if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) &&
+ uefi_str_equal(var->name, var->name_size, name_pk, sizeof(name_pk))) {
+ return true;
+ }
+ return false;
+}
+
+static bool uefi_vars_is_sb_kek(uefi_variable *var)
+{
+ if (qemu_uuid_is_equal(&var->guid, &EfiGlobalVariable) &&
+ uefi_str_equal(var->name, var->name_size, name_kek, sizeof(name_kek))) {
+ return true;
+ }
+ return false;
+}
+
+static bool uefi_vars_is_sb_db(uefi_variable *var)
+{
+ if (!qemu_uuid_is_equal(&var->guid, &EfiImageSecurityDatabase)) {
+ return false;
+ }
+ if (uefi_str_equal(var->name, var->name_size, name_db, sizeof(name_db))) {
+ return true;
+ }
+ if (uefi_str_equal(var->name, var->name_size, name_dbx, sizeof(name_dbx))) {
+ return true;
+ }
+ return false;
+}
+
+bool uefi_vars_is_sb_any(uefi_variable *var)
+{
+ if (uefi_vars_is_sb_pk(var) ||
+ uefi_vars_is_sb_kek(var) ||
+ uefi_vars_is_sb_db(var)) {
+ return true;
+ }
+ return false;
+}
+
+static uefi_variable *uefi_vars_find_siglist(uefi_vars_state *uv,
+ uefi_variable *var)
+{
+ if (uefi_vars_is_sb_pk(var)) {
+ return uefi_vars_find_variable(uv, EfiGlobalVariable,
+ name_pk, sizeof(name_pk));
+ }
+ if (uefi_vars_is_sb_kek(var)) {
+ return uefi_vars_find_variable(uv, EfiGlobalVariable,
+ name_pk, sizeof(name_pk));
+ }
+ if (uefi_vars_is_sb_db(var)) {
+ return uefi_vars_find_variable(uv, EfiGlobalVariable,
+ name_kek, sizeof(name_kek));
+ }
+
+ return NULL;
+}
+
+static efi_status uefi_vars_check_auth_2_sb(uefi_vars_state *uv,
+ uefi_variable *var,
+ mm_variable_access *va,
+ void *data,
+ uint64_t data_offset)
+{
+ variable_auth_2 *auth = data;
+ uefi_variable *siglist;
+
+ if (custom_mode_is_active(uv)) {
+ /* no authentication in custom mode */
+ return EFI_SUCCESS;
+ }
+
+ if (setup_mode_is_active(uv) && !uefi_vars_is_sb_pk(var)) {
+ /* no authentication in setup mode (except PK) */
+ return EFI_SUCCESS;
+ }
+
+ if (auth->hdr_length == 24) {
+ /* no signature (auth->cert_data is empty) */
+ return EFI_SECURITY_VIOLATION;
+ }
+
+ siglist = uefi_vars_find_siglist(uv, var);
+ if (!siglist && setup_mode_is_active(uv) && uefi_vars_is_sb_pk(var)) {
+ /* check PK is self-signed */
+ uefi_variable tmp = {
+ .guid = EfiGlobalVariable,
+ .name = (uint16_t *)name_pk,
+ .name_size = sizeof(name_pk),
+ .attributes = sigdb_attrs,
+ .data = data + data_offset,
+ .data_size = va->data_size - data_offset,
+ };
+ return uefi_vars_check_pkcs7_2(&tmp, NULL, NULL, va, data);
+ }
+
+ return uefi_vars_check_pkcs7_2(siglist, NULL, NULL, va, data);
+}
+
+efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var,
+ mm_variable_access *va, void *data)
+{
+ variable_auth_2 *auth = data;
+ uint64_t data_offset;
+ efi_status status;
+
+ if (va->data_size < sizeof(*auth)) {
+ return EFI_SECURITY_VIOLATION;
+ }
+ if (uadd64_overflow(sizeof(efi_time), auth->hdr_length, &data_offset)) {
+ return EFI_SECURITY_VIOLATION;
+ }
+ if (va->data_size < data_offset) {
+ return EFI_SECURITY_VIOLATION;
+ }
+
+ if (auth->hdr_revision != 0x0200 ||
+ auth->hdr_cert_type != WIN_CERT_TYPE_EFI_GUID ||
+ !qemu_uuid_is_equal(&auth->guid_cert_type, &EfiCertTypePkcs7Guid)) {
+ return EFI_UNSUPPORTED;
+ }
+
+ if (uefi_vars_is_sb_any(var)) {
+ /* secure boot variables */
+ status = uefi_vars_check_auth_2_sb(uv, var, va, data, data_offset);
+ if (status != EFI_SUCCESS) {
+ return status;
+ }
+ } else {
+ /* other authenticated variables */
+ status = uefi_vars_check_pkcs7_2(NULL,
+ &var->digest, &var->digest_size,
+ va, data);
+ if (status != EFI_SUCCESS) {
+ return status;
+ }
+ }
+
+ /* checks passed, set variable data */
+ var->time = auth->timestamp;
+ if (va->data_size - data_offset > 0) {
+ var->data = g_malloc(va->data_size - data_offset);
+ memcpy(var->data, data + data_offset, va->data_size - data_offset);
+ var->data_size = va->data_size - data_offset;
+ }
+
+ return EFI_SUCCESS;
+}
+
+efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var)
+{
+ uint8_t *value = var->data;
+
+ if (uefi_vars_is_sb_any(var)) {
+ if (var->attributes != sigdb_attrs) {
+ return EFI_INVALID_PARAMETER;
+ }
+ }
+
+ /* reject SecureBootEnable updates if force_secure_boot is set */
+ if (qemu_uuid_is_equal(&var->guid, &EfiSecureBootEnableDisable) &&
+ uefi_str_equal(var->name, var->name_size,
+ name_sb_enable, sizeof(name_sb_enable)) &&
+ uv->force_secure_boot &&
+ value[0] != SECURE_BOOT_ENABLE) {
+ return EFI_WRITE_PROTECTED;
+ }
+
+ /* reject CustomMode updates if disable_custom_mode is set */
+ if (qemu_uuid_is_equal(&var->guid, &EfiCustomModeEnable) &&
+ uefi_str_equal(var->name, var->name_size,
+ name_custom_mode, sizeof(name_custom_mode)) &&
+ uv->disable_custom_mode) {
+ return EFI_WRITE_PROTECTED;
+ }
+
+ return EFI_SUCCESS;
+}
+
+/* AuthVariableLibInitialize */
+void uefi_vars_auth_init(uefi_vars_state *uv)
+{
+ uefi_variable *pk_var, *sbe_var;
+ uint8_t platform_mode, sb, sbe, vk;
+
+ /* SetupMode */
+ pk_var = uefi_vars_find_variable(uv, EfiGlobalVariable,
+ name_pk, sizeof(name_pk));
+ if (!pk_var) {
+ platform_mode = SETUP_MODE;
+ } else {
+ platform_mode = USER_MODE;
+ }
+ set_setup_mode(uv, platform_mode);
+
+ /* SignatureSupport */
+ set_signature_support(uv);
+
+ /* SecureBootEnable */
+ sbe = SECURE_BOOT_DISABLE;
+ sbe_var = uefi_vars_find_variable(uv, EfiSecureBootEnableDisable,
+ name_sb_enable, sizeof(name_sb_enable));
+ if (sbe_var) {
+ if (platform_mode == USER_MODE) {
+ sbe = ((uint8_t *)sbe_var->data)[0];
+ }
+ } else if (platform_mode == USER_MODE) {
+ sbe = SECURE_BOOT_ENABLE;
+ set_secure_boot_enable(uv, sbe);
+ }
+
+ if (uv->force_secure_boot && sbe != SECURE_BOOT_ENABLE) {
+ sbe = SECURE_BOOT_ENABLE;
+ set_secure_boot_enable(uv, sbe);
+ }
+
+ /* SecureBoot */
+ if ((sbe == SECURE_BOOT_ENABLE) && (platform_mode == USER_MODE)) {
+ sb = SECURE_BOOT_MODE_ENABLE;
+ } else {
+ sb = SECURE_BOOT_MODE_DISABLE;
+ }
+ set_secure_boot(uv, sb);
+
+ /* CustomMode */
+ set_custom_mode(uv, STANDARD_SECURE_BOOT_MODE);
+
+ vk = 0;
+ uefi_vars_set_variable(uv, EfiGlobalVariable,
+ name_vk_nv, sizeof(name_vk_nv),
+ EFI_VARIABLE_NON_VOLATILE |
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS,
+ &vk, sizeof(vk));
+ uefi_vars_set_variable(uv, EfiGlobalVariable,
+ name_vk, sizeof(name_vk),
+ EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ EFI_VARIABLE_RUNTIME_ACCESS,
+ &vk, sizeof(vk));
+
+ /* flush to disk */
+ uefi_vars_json_save(uv);
+}
diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c
new file mode 100644
index 0000000..8ed8378
--- /dev/null
+++ b/hw/uefi/var-service-core.c
@@ -0,0 +1,321 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device
+ */
+#include "qemu/osdep.h"
+#include "qemu/crc32c.h"
+#include "system/dma.h"
+#include "migration/vmstate.h"
+
+#include "hw/uefi/var-service.h"
+#include "hw/uefi/var-service-api.h"
+#include "hw/uefi/var-service-edk2.h"
+
+#include "trace/trace-hw_uefi.h"
+
+static int uefi_vars_pre_load(void *opaque)
+{
+ uefi_vars_state *uv = opaque;
+
+ uefi_vars_clear_all(uv);
+ uefi_vars_policies_clear(uv);
+ g_free(uv->buffer);
+ return 0;
+}
+
+static int uefi_vars_post_load(void *opaque, int version_id)
+{
+ uefi_vars_state *uv = opaque;
+
+ uefi_vars_update_storage(uv);
+ uv->buffer = g_malloc(uv->buf_size);
+ return 0;
+}
+
+const VMStateDescription vmstate_uefi_vars = {
+ .name = "uefi-vars",
+ .pre_load = uefi_vars_pre_load,
+ .post_load = uefi_vars_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(sts, uefi_vars_state),
+ VMSTATE_UINT32(buf_size, uefi_vars_state),
+ VMSTATE_UINT32(buf_addr_lo, uefi_vars_state),
+ VMSTATE_UINT32(buf_addr_hi, uefi_vars_state),
+ VMSTATE_UINT32(pio_xfer_offset, uefi_vars_state),
+ VMSTATE_VBUFFER_ALLOC_UINT32(pio_xfer_buffer, uefi_vars_state,
+ 0, NULL, buf_size),
+ VMSTATE_BOOL(end_of_dxe, uefi_vars_state),
+ VMSTATE_BOOL(ready_to_boot, uefi_vars_state),
+ VMSTATE_BOOL(exit_boot_service, uefi_vars_state),
+ VMSTATE_BOOL(policy_locked, uefi_vars_state),
+ VMSTATE_UINT64(used_storage, uefi_vars_state),
+ VMSTATE_QTAILQ_V(variables, uefi_vars_state, 0,
+ vmstate_uefi_variable, uefi_variable, next),
+ VMSTATE_QTAILQ_V(var_policies, uefi_vars_state, 0,
+ vmstate_uefi_var_policy, uefi_var_policy, next),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv, bool dma_mode)
+{
+ hwaddr dma;
+ mm_header *mhdr;
+ uint64_t size;
+ uint32_t retval;
+
+ dma = uv->buf_addr_lo | ((hwaddr)uv->buf_addr_hi << 32);
+ mhdr = (mm_header *) uv->buffer;
+
+ if (!uv->buffer || uv->buf_size < sizeof(*mhdr)) {
+ return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
+ }
+
+ /* read header */
+ if (dma_mode) {
+ dma_memory_read(&address_space_memory, dma,
+ uv->buffer, sizeof(*mhdr),
+ MEMTXATTRS_UNSPECIFIED);
+ } else {
+ memcpy(uv->buffer, uv->pio_xfer_buffer, sizeof(*mhdr));
+ }
+
+ if (uadd64_overflow(sizeof(*mhdr), mhdr->length, &size)) {
+ return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
+ }
+ if (uv->buf_size < size) {
+ return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
+ }
+
+ /* read buffer (excl header) */
+ if (dma_mode) {
+ dma_memory_read(&address_space_memory, dma + sizeof(*mhdr),
+ uv->buffer + sizeof(*mhdr), mhdr->length,
+ MEMTXATTRS_UNSPECIFIED);
+ } else {
+ memcpy(uv->buffer + sizeof(*mhdr),
+ uv->pio_xfer_buffer + sizeof(*mhdr),
+ mhdr->length);
+ }
+ memset(uv->buffer + size, 0, uv->buf_size - size);
+
+ /* dispatch */
+ if (qemu_uuid_is_equal(&mhdr->guid, &EfiSmmVariableProtocolGuid)) {
+ retval = uefi_vars_mm_vars_proto(uv);
+
+ } else if (qemu_uuid_is_equal(&mhdr->guid, &VarCheckPolicyLibMmiHandlerGuid)) {
+ retval = uefi_vars_mm_check_policy_proto(uv);
+
+ } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEndOfDxeEventGroupGuid)) {
+ trace_uefi_event("end-of-dxe");
+ uv->end_of_dxe = true;
+ retval = UEFI_VARS_STS_SUCCESS;
+
+ } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventReadyToBootGuid)) {
+ trace_uefi_event("ready-to-boot");
+ uv->ready_to_boot = true;
+ retval = UEFI_VARS_STS_SUCCESS;
+
+ } else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventExitBootServicesGuid)) {
+ trace_uefi_event("exit-boot-service");
+ uv->exit_boot_service = true;
+ retval = UEFI_VARS_STS_SUCCESS;
+
+ } else {
+ retval = UEFI_VARS_STS_ERR_NOT_SUPPORTED;
+ }
+
+ /* write buffer */
+ if (dma_mode) {
+ dma_memory_write(&address_space_memory, dma,
+ uv->buffer, sizeof(*mhdr) + mhdr->length,
+ MEMTXATTRS_UNSPECIFIED);
+ } else {
+ memcpy(uv->pio_xfer_buffer + sizeof(*mhdr),
+ uv->buffer + sizeof(*mhdr),
+ sizeof(*mhdr) + mhdr->length);
+ }
+
+ return retval;
+}
+
+static void uefi_vars_soft_reset(uefi_vars_state *uv)
+{
+ g_free(uv->buffer);
+ uv->buffer = NULL;
+ uv->buf_size = 0;
+ uv->buf_addr_lo = 0;
+ uv->buf_addr_hi = 0;
+}
+
+void uefi_vars_hard_reset(uefi_vars_state *uv)
+{
+ trace_uefi_hard_reset();
+ uefi_vars_soft_reset(uv);
+
+ uv->end_of_dxe = false;
+ uv->ready_to_boot = false;
+ uv->exit_boot_service = false;
+ uv->policy_locked = false;
+
+ uefi_vars_clear_volatile(uv);
+ uefi_vars_policies_clear(uv);
+ uefi_vars_auth_init(uv);
+}
+
+static uint32_t uefi_vars_cmd(uefi_vars_state *uv, uint32_t cmd)
+{
+ switch (cmd) {
+ case UEFI_VARS_CMD_RESET:
+ uefi_vars_soft_reset(uv);
+ return UEFI_VARS_STS_SUCCESS;
+ case UEFI_VARS_CMD_DMA_MM:
+ return uefi_vars_cmd_mm(uv, true);
+ case UEFI_VARS_CMD_PIO_MM:
+ return uefi_vars_cmd_mm(uv, false);
+ case UEFI_VARS_CMD_PIO_ZERO_OFFSET:
+ uv->pio_xfer_offset = 0;
+ return UEFI_VARS_STS_SUCCESS;
+ default:
+ return UEFI_VARS_STS_ERR_NOT_SUPPORTED;
+ }
+}
+
+static uint64_t uefi_vars_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uefi_vars_state *uv = opaque;
+ uint64_t retval = -1;
+ void *xfer_ptr;
+
+ trace_uefi_reg_read(addr, size);
+
+ switch (addr) {
+ case UEFI_VARS_REG_MAGIC:
+ retval = UEFI_VARS_MAGIC_VALUE;
+ break;
+ case UEFI_VARS_REG_CMD_STS:
+ retval = uv->sts;
+ break;
+ case UEFI_VARS_REG_BUFFER_SIZE:
+ retval = uv->buf_size;
+ break;
+ case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO:
+ retval = uv->buf_addr_lo;
+ break;
+ case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI:
+ retval = uv->buf_addr_hi;
+ break;
+ case UEFI_VARS_REG_PIO_BUFFER_TRANSFER:
+ if (uv->pio_xfer_offset + size > uv->buf_size) {
+ retval = 0;
+ break;
+ }
+ xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset;
+ switch (size) {
+ case 1:
+ retval = *(uint8_t *)xfer_ptr;
+ break;
+ case 2:
+ retval = *(uint16_t *)xfer_ptr;
+ break;
+ case 4:
+ retval = *(uint32_t *)xfer_ptr;
+ break;
+ case 8:
+ retval = *(uint64_t *)xfer_ptr;
+ break;
+ }
+ uv->pio_xfer_offset += size;
+ break;
+ case UEFI_VARS_REG_PIO_BUFFER_CRC32C:
+ retval = crc32c(0xffffffff, uv->pio_xfer_buffer, uv->pio_xfer_offset);
+ break;
+ case UEFI_VARS_REG_FLAGS:
+ retval = 0;
+ if (uv->use_pio) {
+ retval |= UEFI_VARS_FLAG_USE_PIO;
+ }
+ }
+ return retval;
+}
+
+static void uefi_vars_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+ uefi_vars_state *uv = opaque;
+ void *xfer_ptr;
+
+ trace_uefi_reg_write(addr, val, size);
+
+ switch (addr) {
+ case UEFI_VARS_REG_CMD_STS:
+ uv->sts = uefi_vars_cmd(uv, val);
+ break;
+ case UEFI_VARS_REG_BUFFER_SIZE:
+ if (val > MAX_BUFFER_SIZE) {
+ val = MAX_BUFFER_SIZE;
+ }
+ uv->buf_size = val;
+ g_free(uv->buffer);
+ g_free(uv->pio_xfer_buffer);
+ uv->buffer = g_malloc(uv->buf_size);
+ uv->pio_xfer_buffer = g_malloc(uv->buf_size);
+ break;
+ case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO:
+ uv->buf_addr_lo = val;
+ break;
+ case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI:
+ uv->buf_addr_hi = val;
+ break;
+ case UEFI_VARS_REG_PIO_BUFFER_TRANSFER:
+ if (uv->pio_xfer_offset + size > uv->buf_size) {
+ break;
+ }
+ xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset;
+ switch (size) {
+ case 1:
+ *(uint8_t *)xfer_ptr = val;
+ break;
+ case 2:
+ *(uint16_t *)xfer_ptr = val;
+ break;
+ case 4:
+ *(uint32_t *)xfer_ptr = val;
+ break;
+ case 8:
+ *(uint64_t *)xfer_ptr = val;
+ break;
+ }
+ uv->pio_xfer_offset += size;
+ break;
+ case UEFI_VARS_REG_PIO_BUFFER_CRC32C:
+ case UEFI_VARS_REG_FLAGS:
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps uefi_vars_ops = {
+ .read = uefi_vars_read,
+ .write = uefi_vars_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 2,
+ .max_access_size = 4,
+ },
+};
+
+void uefi_vars_init(Object *obj, uefi_vars_state *uv)
+{
+ QTAILQ_INIT(&uv->variables);
+ QTAILQ_INIT(&uv->var_policies);
+ uv->jsonfd = -1;
+ memory_region_init_io(&uv->mr, obj, &uefi_vars_ops, uv,
+ "uefi-vars", UEFI_VARS_REGS_SIZE);
+}
+
+void uefi_vars_realize(uefi_vars_state *uv, Error **errp)
+{
+ uefi_vars_json_init(uv, errp);
+ uefi_vars_json_load(uv, errp);
+}
diff --git a/hw/uefi/var-service-guid.c b/hw/uefi/var-service-guid.c
new file mode 100644
index 0000000..eba3655
--- /dev/null
+++ b/hw/uefi/var-service-guid.c
@@ -0,0 +1,99 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - GUIDs
+ */
+
+#include "qemu/osdep.h"
+#include "system/dma.h"
+
+#include "hw/uefi/var-service.h"
+
+/* variable namespaces */
+
+const QemuUUID EfiGlobalVariable = {
+ .data = UUID_LE(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d,
+ 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c)
+};
+
+const QemuUUID EfiImageSecurityDatabase = {
+ .data = UUID_LE(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc,
+ 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f)
+};
+
+const QemuUUID EfiCustomModeEnable = {
+ .data = UUID_LE(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72,
+ 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f)
+};
+
+const QemuUUID EfiSecureBootEnableDisable = {
+ .data = UUID_LE(0xf0a30bc7, 0xaf08, 0x4556, 0x99, 0xc4,
+ 0x0, 0x10, 0x9, 0xc9, 0x3a, 0x44)
+};
+
+/* signatures */
+
+const QemuUUID EfiCertSha256Guid = {
+ .data = UUID_LE(0xc1c41626, 0x504c, 0x4092, 0xac, 0xa9,
+ 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28)
+};
+
+const QemuUUID EfiCertSha384Guid = {
+ .data = UUID_LE(0xff3e5307, 0x9fd0, 0x48c9, 0x85, 0xf1,
+ 0x8a, 0xd5, 0x6c, 0x70, 0x1e, 0x1)
+};
+
+const QemuUUID EfiCertSha512Guid = {
+ .data = UUID_LE(0x93e0fae, 0xa6c4, 0x4f50, 0x9f, 0x1b,
+ 0xd4, 0x1e, 0x2b, 0x89, 0xc1, 0x9a)
+};
+
+const QemuUUID EfiCertRsa2048Guid = {
+ .data = UUID_LE(0x3c5766e8, 0x269c, 0x4e34, 0xaa, 0x14,
+ 0xed, 0x77, 0x6e, 0x85, 0xb3, 0xb6)
+};
+
+const QemuUUID EfiCertX509Guid = {
+ .data = UUID_LE(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5,
+ 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72)
+};
+
+const QemuUUID EfiCertTypePkcs7Guid = {
+ .data = UUID_LE(0x4aafd29d, 0x68df, 0x49ee, 0x8a, 0xa9,
+ 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7)
+};
+
+/*
+ * mm_header.guid values that the guest DXE/BDS phases use for
+ * sending requests to management mode
+ */
+
+const QemuUUID EfiSmmVariableProtocolGuid = {
+ .data = UUID_LE(0xed32d533, 0x99e6, 0x4209, 0x9c, 0xc0,
+ 0x2d, 0x72, 0xcd, 0xd9, 0x98, 0xa7)
+};
+
+const QemuUUID VarCheckPolicyLibMmiHandlerGuid = {
+ .data = UUID_LE(0xda1b0d11, 0xd1a7, 0x46c4, 0x9d, 0xc9,
+ 0xf3, 0x71, 0x48, 0x75, 0xc6, 0xeb)
+};
+
+/*
+ * mm_header.guid values that the guest DXE/BDS phases use for
+ * reporting event groups being signaled to management mode
+ */
+
+const QemuUUID EfiEndOfDxeEventGroupGuid = {
+ .data = UUID_LE(0x02ce967a, 0xdd7e, 0x4FFc, 0x9e, 0xe7,
+ 0x81, 0x0c, 0xF0, 0x47, 0x08, 0x80)
+};
+
+const QemuUUID EfiEventReadyToBootGuid = {
+ .data = UUID_LE(0x7ce88Fb3, 0x4bd7, 0x4679, 0x87, 0xa8,
+ 0xa8, 0xd8, 0xde, 0xe5, 0x0d, 0x2b)
+};
+
+const QemuUUID EfiEventExitBootServicesGuid = {
+ .data = UUID_LE(0x27abF055, 0xb1b8, 0x4c26, 0x80, 0x48,
+ 0x74, 0x8F, 0x37, 0xba, 0xa2, 0xdF)
+};
diff --git a/hw/uefi/var-service-json.c b/hw/uefi/var-service-json.c
new file mode 100644
index 0000000..761082c
--- /dev/null
+++ b/hw/uefi/var-service-json.c
@@ -0,0 +1,243 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - serialize non-volatile varstore from/to json,
+ * using qapi
+ *
+ * tools which can read/write these json files:
+ * - https://gitlab.com/kraxel/virt-firmware
+ * - https://github.com/awslabs/python-uefivars
+ */
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "qemu/error-report.h"
+#include "system/dma.h"
+
+#include "hw/uefi/var-service.h"
+
+#include "qobject/qobject.h"
+#include "qobject/qjson.h"
+
+#include "qapi/dealloc-visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qobject-output-visitor.h"
+#include "qapi/qapi-types-uefi.h"
+#include "qapi/qapi-visit-uefi.h"
+
+static char *generate_hexstr(void *data, size_t len)
+{
+ static const char hex[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+ };
+ uint8_t *src = data;
+ char *dest;
+ size_t i;
+
+ dest = g_malloc(len * 2 + 1);
+ for (i = 0; i < len * 2;) {
+ dest[i++] = hex[*src >> 4];
+ dest[i++] = hex[*src & 15];
+ src++;
+ }
+ dest[i++] = 0;
+
+ return dest;
+}
+
+static UefiVarStore *uefi_vars_to_qapi(uefi_vars_state *uv)
+{
+ UefiVarStore *vs;
+ UefiVariableList **tail;
+ UefiVariable *v;
+ QemuUUID be;
+ uefi_variable *var;
+
+ vs = g_new0(UefiVarStore, 1);
+ vs->version = 2;
+ tail = &vs->variables;
+
+ QTAILQ_FOREACH(var, &uv->variables, next) {
+ if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) {
+ continue;
+ }
+
+ v = g_new0(UefiVariable, 1);
+ be = qemu_uuid_bswap(var->guid);
+ v->guid = qemu_uuid_unparse_strdup(&be);
+ v->name = uefi_ucs2_to_ascii(var->name, var->name_size);
+ v->attr = var->attributes;
+
+ v->data = generate_hexstr(var->data, var->data_size);
+
+ if (var->attributes &
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
+ v->time = generate_hexstr(&var->time, sizeof(var->time));
+ if (var->digest && var->digest_size) {
+ v->digest = generate_hexstr(var->digest, var->digest_size);
+ }
+ }
+
+ QAPI_LIST_APPEND(tail, v);
+ }
+ return vs;
+}
+
+static unsigned parse_hexchar(char c)
+{
+ switch (c) {
+ case '0' ... '9': return c - '0';
+ case 'a' ... 'f': return c - 'a' + 0xa;
+ case 'A' ... 'F': return c - 'A' + 0xA;
+ default: return 0;
+ }
+}
+
+static void parse_hexstr(void *dest, char *src, int len)
+{
+ uint8_t *data = dest;
+ size_t i;
+
+ for (i = 0; i < len; i += 2) {
+ *(data++) =
+ parse_hexchar(src[i]) << 4 |
+ parse_hexchar(src[i + 1]);
+ }
+}
+
+static void uefi_vars_from_qapi(uefi_vars_state *uv, UefiVarStore *vs)
+{
+ UefiVariableList *item;
+ UefiVariable *v;
+ QemuUUID be;
+ uefi_variable *var;
+ uint8_t *data;
+ size_t i, len;
+
+ for (item = vs->variables; item != NULL; item = item->next) {
+ v = item->value;
+
+ var = g_new0(uefi_variable, 1);
+ var->attributes = v->attr;
+ qemu_uuid_parse(v->guid, &be);
+ var->guid = qemu_uuid_bswap(be);
+
+ len = strlen(v->name);
+ var->name_size = len * 2 + 2;
+ var->name = g_malloc(var->name_size);
+ for (i = 0; i <= len; i++) {
+ var->name[i] = v->name[i];
+ }
+
+ len = strlen(v->data);
+ var->data_size = len / 2;
+ var->data = data = g_malloc(var->data_size);
+ parse_hexstr(var->data, v->data, len);
+
+ if (v->time && strlen(v->time) == 32) {
+ parse_hexstr(&var->time, v->time, 32);
+ }
+
+ if (v->digest) {
+ len = strlen(v->digest);
+ var->digest_size = len / 2;
+ var->digest = g_malloc(var->digest_size);
+ parse_hexstr(var->digest, v->digest, len);
+ }
+
+ QTAILQ_INSERT_TAIL(&uv->variables, var, next);
+ }
+}
+
+static GString *uefi_vars_to_json(uefi_vars_state *uv)
+{
+ UefiVarStore *vs = uefi_vars_to_qapi(uv);
+ QObject *qobj = NULL;
+ Visitor *v;
+ GString *gstr;
+
+ v = qobject_output_visitor_new(&qobj);
+ if (visit_type_UefiVarStore(v, NULL, &vs, NULL)) {
+ visit_complete(v, &qobj);
+ }
+ visit_free(v);
+ qapi_free_UefiVarStore(vs);
+
+ gstr = qobject_to_json_pretty(qobj, true);
+ qobject_unref(qobj);
+
+ return gstr;
+}
+
+void uefi_vars_json_init(uefi_vars_state *uv, Error **errp)
+{
+ if (uv->jsonfile) {
+ uv->jsonfd = qemu_create(uv->jsonfile, O_RDWR, 0666, errp);
+ }
+}
+
+void uefi_vars_json_save(uefi_vars_state *uv)
+{
+ GString *gstr;
+ int rc;
+
+ if (uv->jsonfd == -1) {
+ return;
+ }
+
+ gstr = uefi_vars_to_json(uv);
+
+ lseek(uv->jsonfd, 0, SEEK_SET);
+ rc = ftruncate(uv->jsonfd, 0);
+ if (rc != 0) {
+ warn_report("%s: ftruncate error", __func__);
+ }
+ rc = write(uv->jsonfd, gstr->str, gstr->len);
+ if (rc != gstr->len) {
+ warn_report("%s: write error", __func__);
+ }
+ fsync(uv->jsonfd);
+
+ g_string_free(gstr, true);
+}
+
+void uefi_vars_json_load(uefi_vars_state *uv, Error **errp)
+{
+ UefiVarStore *vs;
+ QObject *qobj;
+ Visitor *v;
+ char *str;
+ size_t len;
+ int rc;
+
+ if (uv->jsonfd == -1) {
+ return;
+ }
+
+ len = lseek(uv->jsonfd, 0, SEEK_END);
+ if (len == 0) {
+ return;
+ }
+
+ str = g_malloc(len + 1);
+ lseek(uv->jsonfd, 0, SEEK_SET);
+ rc = read(uv->jsonfd, str, len);
+ if (rc != len) {
+ warn_report("%s: read error", __func__);
+ }
+ str[len] = 0;
+
+ qobj = qobject_from_json(str, errp);
+ v = qobject_input_visitor_new(qobj);
+ visit_type_UefiVarStore(v, NULL, &vs, errp);
+ visit_free(v);
+
+ if (!(*errp)) {
+ uefi_vars_from_qapi(uv, vs);
+ uefi_vars_update_storage(uv);
+ }
+
+ qapi_free_UefiVarStore(vs);
+ qobject_unref(qobj);
+ g_free(str);
+}
diff --git a/hw/uefi/var-service-pkcs7-stub.c b/hw/uefi/var-service-pkcs7-stub.c
new file mode 100644
index 0000000..118cba4
--- /dev/null
+++ b/hw/uefi/var-service-pkcs7-stub.c
@@ -0,0 +1,16 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - pkcs7 stubs
+ */
+#include "qemu/osdep.h"
+#include "system/dma.h"
+
+#include "hw/uefi/var-service.h"
+
+efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist,
+ void **digest, uint32_t *digest_size,
+ mm_variable_access *va, void *data)
+{
+ return EFI_WRITE_PROTECTED;
+}
diff --git a/hw/uefi/var-service-pkcs7.c b/hw/uefi/var-service-pkcs7.c
new file mode 100644
index 0000000..32accf4
--- /dev/null
+++ b/hw/uefi/var-service-pkcs7.c
@@ -0,0 +1,436 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - pkcs7 verification
+ */
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "system/dma.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/pkcs7.h>
+#include <gnutls/crypto.h>
+
+#include "hw/uefi/var-service.h"
+
+#define AUTHVAR_DIGEST_ALGO GNUTLS_DIG_SHA256
+#define AUTHVAR_DIGEST_SIZE 32
+
+/*
+ * Replicate the signed data for signature verification.
+ */
+static gnutls_datum_t *build_signed_data(mm_variable_access *va, void *data)
+{
+ variable_auth_2 *auth = data;
+ uint64_t data_offset = sizeof(efi_time) + auth->hdr_length;
+ uint16_t *name = (void *)va + sizeof(mm_variable_access);
+ gnutls_datum_t *sdata;
+ uint64_t pos = 0;
+
+ sdata = g_new(gnutls_datum_t, 1);
+ sdata->size = (va->name_size - 2
+ + sizeof(QemuUUID)
+ + sizeof(va->attributes)
+ + sizeof(auth->timestamp)
+ + va->data_size - data_offset);
+ sdata->data = g_malloc(sdata->size);
+
+ /* Variable Name (without terminating \0) */
+ memcpy(sdata->data + pos, name, va->name_size - 2);
+ pos += va->name_size - 2;
+
+ /* Variable Namespace Guid */
+ memcpy(sdata->data + pos, &va->guid, sizeof(va->guid));
+ pos += sizeof(va->guid);
+
+ /* Attributes */
+ memcpy(sdata->data + pos, &va->attributes, sizeof(va->attributes));
+ pos += sizeof(va->attributes);
+
+ /* TimeStamp */
+ memcpy(sdata->data + pos, &auth->timestamp, sizeof(auth->timestamp));
+ pos += sizeof(auth->timestamp);
+
+ /* Variable Content */
+ memcpy(sdata->data + pos, data + data_offset, va->data_size - data_offset);
+ pos += va->data_size - data_offset;
+
+ assert(pos == sdata->size);
+ return sdata;
+}
+
+/*
+ * See WrapPkcs7Data() in edk2.
+ *
+ * UEFI spec allows pkcs7 signatures being used without the envelope which
+ * identifies them as pkcs7 signatures. openssl and gnutls will not parse them
+ * without the envelope though. So add it if needed.
+ */
+static void wrap_pkcs7(gnutls_datum_t *pkcs7)
+{
+ static uint8_t signed_data_oid[9] = {
+ 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02
+ };
+ gnutls_datum_t wrap;
+
+ if (pkcs7->data[4] == 0x06 &&
+ pkcs7->data[5] == 0x09 &&
+ memcmp(pkcs7->data + 6, signed_data_oid, sizeof(signed_data_oid)) == 0 &&
+ pkcs7->data[15] == 0x0a &&
+ pkcs7->data[16] == 0x82) {
+ return;
+ }
+
+ wrap.size = pkcs7->size + 19;
+ wrap.data = g_malloc(wrap.size);
+
+ wrap.data[0] = 0x30;
+ wrap.data[1] = 0x82;
+ wrap.data[2] = (wrap.size - 4) >> 8;
+ wrap.data[3] = (wrap.size - 4) & 0xff;
+ wrap.data[4] = 0x06;
+ wrap.data[5] = 0x09;
+ memcpy(wrap.data + 6, signed_data_oid, sizeof(signed_data_oid));
+
+ wrap.data[15] = 0xa0;
+ wrap.data[16] = 0x82;
+ wrap.data[17] = pkcs7->size >> 8;
+ wrap.data[18] = pkcs7->size & 0xff;
+ memcpy(wrap.data + 19, pkcs7->data, pkcs7->size);
+
+ g_free(pkcs7->data);
+ *pkcs7 = wrap;
+}
+
+static gnutls_datum_t *build_pkcs7(void *data)
+{
+ variable_auth_2 *auth = data;
+ gnutls_datum_t *pkcs7;
+
+ pkcs7 = g_new(gnutls_datum_t, 1);
+ pkcs7->size = auth->hdr_length - 24;
+ pkcs7->data = g_malloc(pkcs7->size);
+ memcpy(pkcs7->data, data + 16 + 24, pkcs7->size);
+
+ wrap_pkcs7(pkcs7);
+
+ return pkcs7;
+}
+
+/*
+ * Read UEFI signature database, store x509 all certificates found in
+ * gnutls_x509_trust_list_t.
+ */
+static gnutls_x509_trust_list_t build_trust_list_sb(uefi_variable *var)
+{
+ gnutls_x509_trust_list_t tlist;
+ gnutls_datum_t cert_data;
+ gnutls_x509_crt_t cert;
+ uefi_vars_siglist siglist;
+ uefi_vars_cert *c;
+ int rc;
+
+ rc = gnutls_x509_trust_list_init(&tlist, 0);
+ if (rc < 0) {
+ warn_report("gnutls_x509_trust_list_init error: %s",
+ gnutls_strerror(rc));
+ return NULL;
+ }
+
+ uefi_vars_siglist_init(&siglist);
+ uefi_vars_siglist_parse(&siglist, var->data, var->data_size);
+
+ QTAILQ_FOREACH(c, &siglist.x509, next) {
+ cert_data.size = c->size;
+ cert_data.data = c->data;
+
+ rc = gnutls_x509_crt_init(&cert);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc));
+ break;
+ }
+ rc = gnutls_x509_crt_import(cert, &cert_data, GNUTLS_X509_FMT_DER);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_import error: %s",
+ gnutls_strerror(rc));
+ gnutls_x509_crt_deinit(cert);
+ break;
+ }
+ rc = gnutls_x509_trust_list_add_cas(tlist, &cert, 1, 0);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_import error: %s",
+ gnutls_strerror(rc));
+ gnutls_x509_crt_deinit(cert);
+ break;
+ }
+ }
+
+ uefi_vars_siglist_free(&siglist);
+
+ return tlist;
+}
+
+static int build_digest_authvar(gnutls_x509_crt_t signer,
+ gnutls_x509_crt_t root,
+ uint8_t *hash_digest)
+{
+ char *cn;
+ size_t cn_size = 0;
+ uint8_t fp[AUTHVAR_DIGEST_SIZE];
+ size_t fp_size = sizeof(fp);
+ gnutls_hash_hd_t hash;
+ int rc;
+
+ /* get signer CN */
+ rc = gnutls_x509_crt_get_dn_by_oid(signer, GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, NULL, &cn_size);
+ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ warn_report("gnutls_x509_crt_get_dn_by_oid error #1: %s",
+ gnutls_strerror(rc));
+ return rc;
+ }
+
+ cn = g_malloc(cn_size);
+ rc = gnutls_x509_crt_get_dn_by_oid(signer, GNUTLS_OID_X520_COMMON_NAME,
+ 0, 0, cn, &cn_size);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_get_dn_by_oid error #2: %s",
+ gnutls_strerror(rc));
+ goto err;
+ }
+
+ /* get root certificate fingerprint */
+ rc = gnutls_x509_crt_get_fingerprint(root, AUTHVAR_DIGEST_ALGO,
+ fp, &fp_size);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_get_fingerprint error: %s",
+ gnutls_strerror(rc));
+ goto err;
+ }
+
+ /* digest both items */
+ rc = gnutls_hash_init(&hash, AUTHVAR_DIGEST_ALGO);
+ if (rc < 0) {
+ warn_report("gnutls_hash_init error: %s",
+ gnutls_strerror(rc));
+ goto err;
+ }
+ rc = gnutls_hash(hash, cn, cn_size);
+ if (rc < 0) {
+ warn_report("gnutls_hash error: %s",
+ gnutls_strerror(rc));
+ goto err;
+ }
+ rc = gnutls_hash(hash, fp, fp_size);
+ if (rc < 0) {
+ warn_report("gnutls_hash error: %s",
+ gnutls_strerror(rc));
+ goto err;
+ }
+ gnutls_hash_deinit(hash, hash_digest);
+
+ return 0;
+
+err:
+ g_free(cn);
+ return rc;
+}
+
+/*
+ * uefi spec 2.9, section 8.2.2
+ *
+ * For EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS variables which are
+ * NOT secure boot variables we should track the root certificate of the trust
+ * chain, and the subject CN of the signer certificate.
+ *
+ * So we'll go store a digest of these two items so we can verify this. Also
+ * create a gnutls_x509_trust_list_t with the root certificate, so
+ * gnutls_pkcs7_verify() will pass (assuming the signature is otherwise
+ * correct).
+ */
+static gnutls_x509_trust_list_t build_trust_list_authvar(gnutls_pkcs7_t pkcs7,
+ uint8_t *hash_digest)
+{
+ gnutls_datum_t signer_data = { 0 };
+ gnutls_datum_t root_data = { 0 };
+ gnutls_x509_crt_t signer = NULL;
+ gnutls_x509_crt_t root = NULL;
+ gnutls_x509_trust_list_t tlist = NULL;
+ int n, rc;
+
+ n = gnutls_pkcs7_get_crt_count(pkcs7);
+
+ /* first is signer certificate */
+ rc = gnutls_pkcs7_get_crt_raw2(pkcs7, 0, &signer_data);
+ if (rc < 0) {
+ warn_report("gnutls_pkcs7_get_crt_raw2(0) error: %s",
+ gnutls_strerror(rc));
+ goto done;
+ }
+ rc = gnutls_x509_crt_init(&signer);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc));
+ goto done;
+ }
+ rc = gnutls_x509_crt_import(signer, &signer_data, GNUTLS_X509_FMT_DER);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_import error: %s",
+ gnutls_strerror(rc));
+ gnutls_x509_crt_deinit(signer);
+ goto done;
+ }
+
+ /* last is root-of-trust certificate (can be identical to signer) */
+ rc = gnutls_pkcs7_get_crt_raw2(pkcs7, n - 1, &root_data);
+ if (rc < 0) {
+ warn_report("gnutls_pkcs7_get_crt_raw2(%d) error: %s",
+ n - 1, gnutls_strerror(rc));
+ goto done;
+ }
+ rc = gnutls_x509_crt_init(&root);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_init error: %s", gnutls_strerror(rc));
+ goto done;
+ }
+ rc = gnutls_x509_crt_import(root, &root_data, GNUTLS_X509_FMT_DER);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_import error: %s",
+ gnutls_strerror(rc));
+ goto done;
+ }
+
+ /* calc digest for signer CN + root cert */
+ rc = build_digest_authvar(signer, root, hash_digest);
+ if (rc < 0) {
+ goto done;
+ }
+
+ /* add root to trust list */
+ rc = gnutls_x509_trust_list_init(&tlist, 0);
+ if (rc < 0) {
+ warn_report("gnutls_x509_trust_list_init error: %s",
+ gnutls_strerror(rc));
+ goto done;
+ }
+ rc = gnutls_x509_trust_list_add_cas(tlist, &root, 1, 0);
+ if (rc < 0) {
+ warn_report("gnutls_x509_crt_import error: %s",
+ gnutls_strerror(rc));
+ gnutls_x509_trust_list_deinit(tlist, 1);
+ tlist = NULL;
+ goto done;
+ } else {
+ /* ownership passed to tlist */
+ root = NULL;
+ }
+
+done:
+ if (signer_data.data) {
+ gnutls_free(signer_data.data);
+ }
+ if (root_data.data) {
+ gnutls_free(root_data.data);
+ }
+ if (signer) {
+ gnutls_x509_crt_deinit(signer);
+ }
+ if (root) {
+ gnutls_x509_crt_deinit(root);
+ }
+ return tlist;
+}
+
+static void free_datum(gnutls_datum_t *ptr)
+{
+ if (!ptr) {
+ return;
+ }
+ g_free(ptr->data);
+ g_free(ptr);
+}
+
+static void gnutls_log_stderr(int level, const char *msg)
+{
+ if (strncmp(msg, "ASSERT:", 7) == 0) {
+ return;
+ }
+ fprintf(stderr, " %d: %s", level, msg);
+}
+
+/*
+ * pkcs7 signature verification (EFI_VARIABLE_AUTHENTICATION_2).
+ */
+efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist,
+ void **digest, uint32_t *digest_size,
+ mm_variable_access *va, void *data)
+{
+ gnutls_x509_trust_list_t tlist = NULL;
+ gnutls_datum_t *signed_data = NULL;
+ gnutls_datum_t *pkcs7_data = NULL;
+ gnutls_pkcs7_t pkcs7 = NULL;
+ efi_status status = EFI_SECURITY_VIOLATION;
+ int rc;
+
+ if (0) {
+ /* gnutls debug logging */
+ static bool first = true;
+
+ if (first) {
+ first = false;
+ gnutls_global_set_log_function(gnutls_log_stderr);
+ gnutls_global_set_log_level(99);
+ }
+ }
+
+ signed_data = build_signed_data(va, data);
+ pkcs7_data = build_pkcs7(data);
+
+ rc = gnutls_pkcs7_init(&pkcs7);
+ if (rc < 0) {
+ warn_report("gnutls_pkcs7_init error: %s", gnutls_strerror(rc));
+ goto out;
+ }
+
+ rc = gnutls_pkcs7_import(pkcs7, pkcs7_data, GNUTLS_X509_FMT_DER);
+ if (rc < 0) {
+ warn_report("gnutls_pkcs7_import error: %s", gnutls_strerror(rc));
+ goto out;
+ }
+
+ if (siglist) {
+ /* secure boot variables */
+ tlist = build_trust_list_sb(siglist);
+ } else if (digest && digest_size) {
+ /* other authenticated variables */
+ *digest_size = AUTHVAR_DIGEST_SIZE;
+ *digest = g_malloc(*digest_size);
+ tlist = build_trust_list_authvar(pkcs7, *digest);
+ } else {
+ /* should not happen */
+ goto out;
+ }
+
+ rc = gnutls_pkcs7_verify(pkcs7, tlist,
+ NULL, 0,
+ 0, signed_data,
+ GNUTLS_VERIFY_DISABLE_TIME_CHECKS |
+ GNUTLS_VERIFY_DISABLE_TRUSTED_TIME_CHECKS);
+ if (rc < 0) {
+ warn_report("gnutls_pkcs7_verify error: %s", gnutls_strerror(rc));
+ goto out;
+ }
+
+ /* check passed */
+ status = EFI_SUCCESS;
+
+out:
+ free_datum(signed_data);
+ free_datum(pkcs7_data);
+ if (tlist) {
+ gnutls_x509_trust_list_deinit(tlist, 1);
+ }
+ if (pkcs7) {
+ gnutls_pkcs7_deinit(pkcs7);
+ }
+ return status;
+}
diff --git a/hw/uefi/var-service-policy.c b/hw/uefi/var-service-policy.c
new file mode 100644
index 0000000..3b1155f
--- /dev/null
+++ b/hw/uefi/var-service-policy.c
@@ -0,0 +1,370 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - VarCheckPolicyLibMmiHandler implementation
+ *
+ * variable policy specs:
+ * https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md
+ */
+#include "qemu/osdep.h"
+#include "system/dma.h"
+#include "migration/vmstate.h"
+
+#include "hw/uefi/var-service.h"
+#include "hw/uefi/var-service-api.h"
+#include "hw/uefi/var-service-edk2.h"
+
+#include "trace/trace-hw_uefi.h"
+
+static void calc_policy(uefi_var_policy *pol);
+
+static int uefi_var_policy_post_load(void *opaque, int version_id)
+{
+ uefi_var_policy *pol = opaque;
+
+ calc_policy(pol);
+ return 0;
+}
+
+const VMStateDescription vmstate_uefi_var_policy = {
+ .name = "uefi-var-policy",
+ .post_load = uefi_var_policy_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(entry_size, uefi_var_policy),
+ VMSTATE_VBUFFER_ALLOC_UINT32(entry, uefi_var_policy,
+ 0, NULL, entry_size),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void print_policy_entry(variable_policy_entry *pe)
+{
+ uint16_t *name = (void *)pe + pe->offset_to_name;
+
+ fprintf(stderr, "%s:\n", __func__);
+
+ fprintf(stderr, " name ´");
+ while (*name) {
+ fprintf(stderr, "%c", *name);
+ name++;
+ }
+ fprintf(stderr, "', version=%d.%d, size=%d\n",
+ pe->version >> 16, pe->version & 0xffff, pe->size);
+
+ if (pe->min_size) {
+ fprintf(stderr, " size min=%d\n", pe->min_size);
+ }
+ if (pe->max_size != UINT32_MAX) {
+ fprintf(stderr, " size max=%u\n", pe->max_size);
+ }
+ if (pe->attributes_must_have) {
+ fprintf(stderr, " attr must=0x%x\n", pe->attributes_must_have);
+ }
+ if (pe->attributes_cant_have) {
+ fprintf(stderr, " attr cant=0x%x\n", pe->attributes_cant_have);
+ }
+ if (pe->lock_policy_type) {
+ fprintf(stderr, " lock policy type %d\n", pe->lock_policy_type);
+ }
+}
+
+static gboolean wildcard_str_equal(uefi_var_policy *pol,
+ uefi_variable *var)
+{
+ return uefi_str_equal_ex(pol->name, pol->name_size,
+ var->name, var->name_size,
+ true);
+}
+
+static uefi_var_policy *find_policy(uefi_vars_state *uv, QemuUUID guid,
+ uint16_t *name, uint64_t name_size)
+{
+ uefi_var_policy *pol;
+
+ QTAILQ_FOREACH(pol, &uv->var_policies, next) {
+ if (!qemu_uuid_is_equal(&pol->entry->namespace, &guid)) {
+ continue;
+ }
+ if (!uefi_str_equal(pol->name, pol->name_size,
+ name, name_size)) {
+ continue;
+ }
+ return pol;
+ }
+ return NULL;
+}
+
+static uefi_var_policy *wildcard_find_policy(uefi_vars_state *uv,
+ uefi_variable *var)
+{
+ uefi_var_policy *pol;
+
+ QTAILQ_FOREACH(pol, &uv->var_policies, next) {
+ if (!qemu_uuid_is_equal(&pol->entry->namespace, &var->guid)) {
+ continue;
+ }
+ if (!wildcard_str_equal(pol, var)) {
+ continue;
+ }
+ return pol;
+ }
+ return NULL;
+}
+
+static void calc_policy(uefi_var_policy *pol)
+{
+ variable_policy_entry *pe = pol->entry;
+ unsigned int i;
+
+ pol->name = (void *)pol->entry + pe->offset_to_name;
+ pol->name_size = pe->size - pe->offset_to_name;
+
+ for (i = 0; i < pol->name_size / 2; i++) {
+ if (pol->name[i] == '#') {
+ pol->hashmarks++;
+ }
+ }
+}
+
+uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv,
+ variable_policy_entry *pe)
+{
+ uefi_var_policy *pol, *p;
+
+ pol = g_new0(uefi_var_policy, 1);
+ pol->entry = g_malloc(pe->size);
+ memcpy(pol->entry, pe, pe->size);
+ pol->entry_size = pe->size;
+
+ calc_policy(pol);
+
+ /* keep list sorted by priority, add to tail of priority group */
+ QTAILQ_FOREACH(p, &uv->var_policies, next) {
+ if ((p->hashmarks > pol->hashmarks) ||
+ (!p->name_size && pol->name_size)) {
+ QTAILQ_INSERT_BEFORE(p, pol, next);
+ return pol;
+ }
+ }
+
+ QTAILQ_INSERT_TAIL(&uv->var_policies, pol, next);
+ return pol;
+}
+
+efi_status uefi_vars_policy_check(uefi_vars_state *uv,
+ uefi_variable *var,
+ gboolean is_newvar)
+{
+ uefi_var_policy *pol;
+ variable_policy_entry *pe;
+ variable_lock_on_var_state *lvarstate;
+ uint16_t *lvarname;
+ size_t lvarnamesize;
+ uefi_variable *lvar;
+
+ if (!uv->end_of_dxe) {
+ return EFI_SUCCESS;
+ }
+
+ pol = wildcard_find_policy(uv, var);
+ if (!pol) {
+ return EFI_SUCCESS;
+ }
+ pe = pol->entry;
+
+ uefi_trace_variable(__func__, var->guid, var->name, var->name_size);
+ print_policy_entry(pe);
+
+ if ((var->attributes & pe->attributes_must_have) != pe->attributes_must_have) {
+ trace_uefi_vars_policy_deny("must-have-attr");
+ return EFI_INVALID_PARAMETER;
+ }
+ if ((var->attributes & pe->attributes_cant_have) != 0) {
+ trace_uefi_vars_policy_deny("cant-have-attr");
+ return EFI_INVALID_PARAMETER;
+ }
+
+ if (var->data_size < pe->min_size) {
+ trace_uefi_vars_policy_deny("min-size");
+ return EFI_INVALID_PARAMETER;
+ }
+ if (var->data_size > pe->max_size) {
+ trace_uefi_vars_policy_deny("max-size");
+ return EFI_INVALID_PARAMETER;
+ }
+
+ switch (pe->lock_policy_type) {
+ case VARIABLE_POLICY_TYPE_NO_LOCK:
+ break;
+
+ case VARIABLE_POLICY_TYPE_LOCK_NOW:
+ trace_uefi_vars_policy_deny("lock-now");
+ return EFI_WRITE_PROTECTED;
+
+ case VARIABLE_POLICY_TYPE_LOCK_ON_CREATE:
+ if (!is_newvar) {
+ trace_uefi_vars_policy_deny("lock-on-create");
+ return EFI_WRITE_PROTECTED;
+ }
+ break;
+
+ case VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE:
+ lvarstate = (void *)pol->entry + sizeof(*pe);
+ lvarname = (void *)pol->entry + sizeof(*pe) + sizeof(*lvarstate);
+ lvarnamesize = pe->offset_to_name - sizeof(*pe) - sizeof(*lvarstate);
+
+ uefi_trace_variable(__func__, lvarstate->namespace,
+ lvarname, lvarnamesize);
+ lvar = uefi_vars_find_variable(uv, lvarstate->namespace,
+ lvarname, lvarnamesize);
+ if (lvar && lvar->data_size == 1) {
+ uint8_t *value = lvar->data;
+ if (lvarstate->value == *value) {
+ return EFI_WRITE_PROTECTED;
+ }
+ }
+ break;
+ }
+
+ return EFI_SUCCESS;
+}
+
+void uefi_vars_policies_clear(uefi_vars_state *uv)
+{
+ uefi_var_policy *pol;
+
+ while (!QTAILQ_EMPTY(&uv->var_policies)) {
+ pol = QTAILQ_FIRST(&uv->var_policies);
+ QTAILQ_REMOVE(&uv->var_policies, pol, next);
+ g_free(pol->entry);
+ g_free(pol);
+ }
+}
+
+static size_t uefi_vars_mm_policy_error(mm_header *mhdr,
+ mm_check_policy *mchk,
+ uint64_t status)
+{
+ mchk->result = status;
+ return sizeof(*mchk);
+}
+
+static uint32_t uefi_vars_mm_check_policy_is_enabled(uefi_vars_state *uv,
+ mm_header *mhdr,
+ mm_check_policy *mchk,
+ void *func)
+{
+ mm_check_policy_is_enabled *mpar = func;
+ size_t length;
+
+ length = sizeof(*mchk) + sizeof(*mpar);
+ if (mhdr->length < length) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+
+ mpar->state = TRUE;
+ mchk->result = EFI_SUCCESS;
+ return sizeof(*mchk);
+}
+
+static uint32_t uefi_vars_mm_check_policy_register(uefi_vars_state *uv,
+ mm_header *mhdr,
+ mm_check_policy *mchk,
+ void *func)
+{
+ variable_policy_entry *pe = func;
+ uefi_var_policy *pol;
+ uint64_t length;
+
+ if (uadd64_overflow(sizeof(*mchk), pe->size, &length)) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+ if (mhdr->length < length) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+ if (pe->size < sizeof(*pe)) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+ if (pe->offset_to_name < sizeof(*pe)) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (pe->lock_policy_type == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE &&
+ pe->offset_to_name < sizeof(*pe) + sizeof(variable_lock_on_var_state)) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+
+ /* check space for minimum string length */
+ if (pe->size < (size_t)pe->offset_to_name) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (!uefi_str_is_valid((void *)pe + pe->offset_to_name,
+ pe->size - pe->offset_to_name,
+ false)) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_INVALID_PARAMETER);
+ }
+
+ pol = find_policy(uv, pe->namespace,
+ (void *)pe + pe->offset_to_name,
+ pe->size - pe->offset_to_name);
+ if (pol) {
+ return uefi_vars_mm_policy_error(mhdr, mchk, EFI_ALREADY_STARTED);
+ }
+
+ uefi_vars_add_policy(uv, pe);
+
+ mchk->result = EFI_SUCCESS;
+ return sizeof(*mchk);
+}
+
+uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv)
+{
+ static const char *fnames[] = {
+ "zero",
+ "disable",
+ "is-enabled",
+ "register",
+ "dump",
+ "lock",
+ };
+ const char *fname;
+ mm_header *mhdr = (mm_header *) uv->buffer;
+ mm_check_policy *mchk = (mm_check_policy *) (uv->buffer + sizeof(*mhdr));
+ void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mchk));
+
+ if (mhdr->length < sizeof(*mchk)) {
+ return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
+ }
+
+ fname = mchk->command < ARRAY_SIZE(fnames)
+ ? fnames[mchk->command]
+ : "unknown";
+ trace_uefi_vars_policy_cmd(fname);
+
+ switch (mchk->command) {
+ case VAR_CHECK_POLICY_COMMAND_DISABLE:
+ mchk->result = EFI_UNSUPPORTED;
+ break;
+ case VAR_CHECK_POLICY_COMMAND_IS_ENABLED:
+ uefi_vars_mm_check_policy_is_enabled(uv, mhdr, mchk, func);
+ break;
+ case VAR_CHECK_POLICY_COMMAND_REGISTER:
+ if (uv->policy_locked) {
+ mchk->result = EFI_WRITE_PROTECTED;
+ } else {
+ uefi_vars_mm_check_policy_register(uv, mhdr, mchk, func);
+ }
+ break;
+ case VAR_CHECK_POLICY_COMMAND_LOCK:
+ uv->policy_locked = true;
+ mchk->result = EFI_SUCCESS;
+ break;
+ default:
+ mchk->result = EFI_UNSUPPORTED;
+ break;
+ }
+
+ uefi_trace_status(__func__, mchk->result);
+ return UEFI_VARS_STS_SUCCESS;
+}
diff --git a/hw/uefi/var-service-siglist.c b/hw/uefi/var-service-siglist.c
new file mode 100644
index 0000000..8948f1b
--- /dev/null
+++ b/hw/uefi/var-service-siglist.c
@@ -0,0 +1,212 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - parse and generate efi signature databases
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "system/dma.h"
+
+#include "hw/uefi/var-service.h"
+
+/*
+ * Add x509 certificate to list (with duplicate check).
+ */
+static void uefi_vars_siglist_add_x509(uefi_vars_siglist *siglist,
+ QemuUUID *owner,
+ void *data, uint64_t size)
+{
+ uefi_vars_cert *c;
+
+ QTAILQ_FOREACH(c, &siglist->x509, next) {
+ if (c->size != size) {
+ continue;
+ }
+ if (memcmp(c->data, data, size) != 0) {
+ continue;
+ }
+ return;
+ }
+
+ c = g_malloc(sizeof(*c) + size);
+ c->owner = *owner;
+ c->size = size;
+ memcpy(c->data, data, size);
+ QTAILQ_INSERT_TAIL(&siglist->x509, c, next);
+}
+
+/*
+ * Add sha256 hash to list (with duplicate check).
+ */
+static void uefi_vars_siglist_add_sha256(uefi_vars_siglist *siglist,
+ QemuUUID *owner,
+ void *data)
+{
+ uefi_vars_hash *h;
+
+ QTAILQ_FOREACH(h, &siglist->sha256, next) {
+ if (memcmp(h->data, data, 32) != 0) {
+ continue;
+ }
+ return;
+ }
+
+ h = g_malloc(sizeof(*h) + 32);
+ h->owner = *owner;
+ memcpy(h->data, data, 32);
+ QTAILQ_INSERT_TAIL(&siglist->sha256, h, next);
+}
+
+void uefi_vars_siglist_init(uefi_vars_siglist *siglist)
+{
+ memset(siglist, 0, sizeof(*siglist));
+ QTAILQ_INIT(&siglist->x509);
+ QTAILQ_INIT(&siglist->sha256);
+}
+
+void uefi_vars_siglist_free(uefi_vars_siglist *siglist)
+{
+ uefi_vars_cert *c, *cs;
+ uefi_vars_hash *h, *hs;
+
+ QTAILQ_FOREACH_SAFE(c, &siglist->x509, next, cs) {
+ QTAILQ_REMOVE(&siglist->x509, c, next);
+ g_free(c);
+ }
+ QTAILQ_FOREACH_SAFE(h, &siglist->sha256, next, hs) {
+ QTAILQ_REMOVE(&siglist->sha256, h, next);
+ g_free(h);
+ }
+}
+
+/*
+ * Parse UEFI signature list.
+ */
+void uefi_vars_siglist_parse(uefi_vars_siglist *siglist,
+ void *data, uint64_t size)
+{
+ efi_siglist *efilist;
+ uint64_t start;
+
+ while (size) {
+ if (size < sizeof(*efilist)) {
+ break;
+ }
+ efilist = data;
+ if (size < efilist->siglist_size) {
+ break;
+ }
+
+ if (uadd64_overflow(sizeof(*efilist), efilist->header_size, &start)) {
+ break;
+ }
+ if (efilist->sig_size <= sizeof(QemuUUID)) {
+ break;
+ }
+
+ if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertX509Guid)) {
+ if (start + efilist->sig_size != efilist->siglist_size) {
+ break;
+ }
+ uefi_vars_siglist_add_x509(siglist,
+ (QemuUUID *)(data + start),
+ data + start + sizeof(QemuUUID),
+ efilist->sig_size - sizeof(QemuUUID));
+
+ } else if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertSha256Guid)) {
+ if (efilist->sig_size != sizeof(QemuUUID) + 32) {
+ break;
+ }
+ if (start + efilist->sig_size > efilist->siglist_size) {
+ break;
+ }
+ while (start <= efilist->siglist_size - efilist->sig_size) {
+ uefi_vars_siglist_add_sha256(siglist,
+ (QemuUUID *)(data + start),
+ data + start + sizeof(QemuUUID));
+ start += efilist->sig_size;
+ }
+
+ } else {
+ QemuUUID be = qemu_uuid_bswap(efilist->guid_type);
+ char *str_uuid = qemu_uuid_unparse_strdup(&be);
+ warn_report("%s: unknown type (%s)", __func__, str_uuid);
+ g_free(str_uuid);
+ }
+
+ data += efilist->siglist_size;
+ size -= efilist->siglist_size;
+ }
+}
+
+uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist)
+{
+ uefi_vars_cert *c;
+ uefi_vars_hash *h;
+ uint64_t size = 0;
+
+ QTAILQ_FOREACH(c, &siglist->x509, next) {
+ size += sizeof(efi_siglist) + sizeof(QemuUUID) + c->size;
+ }
+
+ if (!QTAILQ_EMPTY(&siglist->sha256)) {
+ size += sizeof(efi_siglist);
+ QTAILQ_FOREACH(h, &siglist->sha256, next) {
+ size += sizeof(QemuUUID) + 32;
+ }
+ }
+
+ return size;
+}
+
+/*
+ * Generate UEFI signature list.
+ */
+void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist,
+ void *data, uint64_t size)
+{
+ uefi_vars_cert *c;
+ uefi_vars_hash *h;
+ efi_siglist *efilist;
+ uint64_t pos = 0, start;
+ uint32_t i;
+
+ QTAILQ_FOREACH(c, &siglist->x509, next) {
+ efilist = data + pos;
+ efilist->guid_type = EfiCertX509Guid;
+ efilist->sig_size = sizeof(QemuUUID) + c->size;
+ efilist->header_size = 0;
+
+ start = pos + sizeof(efi_siglist);
+ memcpy(data + start,
+ &c->owner, sizeof(QemuUUID));
+ memcpy(data + start + sizeof(QemuUUID),
+ c->data, c->size);
+
+ efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size;
+ pos += efilist->siglist_size;
+ }
+
+ if (!QTAILQ_EMPTY(&siglist->sha256)) {
+ efilist = data + pos;
+ efilist->guid_type = EfiCertSha256Guid;
+ efilist->sig_size = sizeof(QemuUUID) + 32;
+ efilist->header_size = 0;
+
+ i = 0;
+ start = pos + sizeof(efi_siglist);
+ QTAILQ_FOREACH(h, &siglist->sha256, next) {
+ memcpy(data + start + efilist->sig_size * i,
+ &h->owner, sizeof(QemuUUID));
+ memcpy(data + start + efilist->sig_size * i + sizeof(QemuUUID),
+ h->data, 32);
+ i++;
+ }
+
+ efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size * i;
+ pos += efilist->siglist_size;
+ }
+
+ assert(pos == size);
+}
diff --git a/hw/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c
new file mode 100644
index 0000000..97da867
--- /dev/null
+++ b/hw/uefi/var-service-sysbus.c
@@ -0,0 +1,124 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - sysbus variant.
+ */
+#include "qemu/osdep.h"
+#include "migration/vmstate.h"
+
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+
+#include "hw/uefi/hardware-info.h"
+#include "hw/uefi/var-service.h"
+#include "hw/uefi/var-service-api.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(uefi_vars_sysbus_state, UEFI_VARS_SYSBUS)
+
+struct uefi_vars_sysbus_state {
+ SysBusDevice parent_obj;
+ struct uefi_vars_state state;
+};
+
+static const VMStateDescription vmstate_uefi_vars_sysbus = {
+ .name = TYPE_UEFI_VARS_SYSBUS,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, uefi_vars_sysbus_state, 0,
+ vmstate_uefi_vars, uefi_vars_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property uefi_vars_sysbus_properties[] = {
+ DEFINE_PROP_SIZE("size", uefi_vars_sysbus_state, state.max_storage,
+ 256 * 1024),
+ DEFINE_PROP_STRING("jsonfile", uefi_vars_sysbus_state, state.jsonfile),
+ DEFINE_PROP_BOOL("force-secure-boot", uefi_vars_sysbus_state,
+ state.force_secure_boot, false),
+ DEFINE_PROP_BOOL("disable-custom-mode", uefi_vars_sysbus_state,
+ state.disable_custom_mode, false),
+ DEFINE_PROP_BOOL("use-pio", uefi_vars_sysbus_state,
+ state.use_pio, false),
+};
+
+static void uefi_vars_sysbus_init(Object *obj)
+{
+ uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(obj);
+
+ uefi_vars_init(obj, &uv->state);
+}
+
+static void uefi_vars_sysbus_reset(DeviceState *dev)
+{
+ uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev);
+
+ uefi_vars_hard_reset(&uv->state);
+}
+
+static void uefi_vars_sysbus_realize(DeviceState *dev, Error **errp)
+{
+ uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev);
+ SysBusDevice *sysbus = SYS_BUS_DEVICE(dev);
+
+ sysbus_init_mmio(sysbus, &uv->state.mr);
+ uefi_vars_realize(&uv->state, errp);
+}
+
+static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = uefi_vars_sysbus_realize;
+ dc->vmsd = &vmstate_uefi_vars_sysbus;
+ dc->user_creatable = true;
+ device_class_set_legacy_reset(dc, uefi_vars_sysbus_reset);
+ device_class_set_props(dc, uefi_vars_sysbus_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+/* generic: hardware discovery via FDT */
+static const TypeInfo uefi_vars_sysbus_info = {
+ .name = TYPE_UEFI_VARS_SYSBUS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(uefi_vars_sysbus_state),
+ .instance_init = uefi_vars_sysbus_init,
+ .class_init = uefi_vars_sysbus_class_init,
+};
+module_obj(TYPE_UEFI_VARS_SYSBUS);
+
+static void uefi_vars_x64_realize(DeviceState *dev, Error **errp)
+{
+ HARDWARE_INFO_SIMPLE_DEVICE hwinfo = {
+ .mmio_address = cpu_to_le64(0xfef10000),
+ };
+ SysBusDevice *sysbus = SYS_BUS_DEVICE(dev);
+
+ uefi_vars_sysbus_realize(dev, errp);
+
+ hardware_info_register(HardwareInfoQemuUefiVars,
+ &hwinfo, sizeof(hwinfo));
+ sysbus_mmio_map(sysbus, 0, hwinfo.mmio_address);
+}
+
+static void uefi_vars_x64_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = uefi_vars_x64_realize;
+}
+
+/* x64: hardware discovery via etc/hardware-info fw_cfg */
+static const TypeInfo uefi_vars_x64_info = {
+ .name = TYPE_UEFI_VARS_X64,
+ .parent = TYPE_UEFI_VARS_SYSBUS,
+ .class_init = uefi_vars_x64_class_init,
+};
+module_obj(TYPE_UEFI_VARS_X64);
+
+static void uefi_vars_sysbus_register_types(void)
+{
+ type_register_static(&uefi_vars_sysbus_info);
+ type_register_static(&uefi_vars_x64_info);
+}
+
+type_init(uefi_vars_sysbus_register_types)
diff --git a/hw/uefi/var-service-utils.c b/hw/uefi/var-service-utils.c
new file mode 100644
index 0000000..c9ef465
--- /dev/null
+++ b/hw/uefi/var-service-utils.c
@@ -0,0 +1,241 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - helper functions for ucs2 strings and tracing
+ */
+#include "qemu/osdep.h"
+#include "system/dma.h"
+
+#include "hw/uefi/var-service.h"
+
+#include "trace/trace-hw_uefi.h"
+
+/* ------------------------------------------------------------------ */
+
+/*
+ * string helper functions.
+ *
+ * Most of the time uefi ucs2 strings are NULL-terminated, except
+ * sometimes when they are not (for example in variable policies).
+ */
+
+gboolean uefi_str_is_valid(const uint16_t *str, size_t len,
+ gboolean must_be_null_terminated)
+{
+ size_t pos = 0;
+
+ for (;;) {
+ if (pos == len) {
+ if (must_be_null_terminated) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ switch (str[pos]) {
+ case 0:
+ /* end of string */
+ return true;
+ case 0xd800 ... 0xdfff:
+ /* reject surrogates */
+ return false;
+ default:
+ /* char is good, check next */
+ break;
+ }
+ pos++;
+ }
+}
+
+size_t uefi_strlen(const uint16_t *str, size_t len)
+{
+ size_t pos = 0;
+
+ for (;;) {
+ if (pos == len) {
+ return pos;
+ }
+ if (str[pos] == 0) {
+ return pos;
+ }
+ pos++;
+ }
+}
+
+gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen,
+ const uint16_t *b, size_t blen,
+ gboolean wildcards_in_a)
+{
+ size_t pos = 0;
+
+ alen = alen / 2;
+ blen = blen / 2;
+ for (;;) {
+ if (pos == alen && pos == blen) {
+ return true;
+ }
+ if (pos == alen && b[pos] == 0) {
+ return true;
+ }
+ if (pos == blen && a[pos] == 0) {
+ return true;
+ }
+ if (pos == alen || pos == blen) {
+ return false;
+ }
+ if (a[pos] == 0 && b[pos] == 0) {
+ return true;
+ }
+
+ if (wildcards_in_a && a[pos] == '#') {
+ if (!isxdigit(b[pos])) {
+ return false;
+ }
+ } else {
+ if (a[pos] != b[pos]) {
+ return false;
+ }
+ }
+ pos++;
+ }
+}
+
+gboolean uefi_str_equal(const uint16_t *a, size_t alen,
+ const uint16_t *b, size_t blen)
+{
+ return uefi_str_equal_ex(a, alen, b, blen, false);
+}
+
+char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size)
+{
+ char *str = g_malloc0(ucs2_size / 2 + 1);
+ int i;
+
+ for (i = 0; i * 2 < ucs2_size; i++) {
+ if (ucs2[i] == 0) {
+ break;
+ }
+ if (ucs2[i] < 128) {
+ str[i] = ucs2[i];
+ } else {
+ str[i] = '?';
+ }
+ }
+ str[i] = 0;
+ return str;
+}
+
+/* ------------------------------------------------------------------ */
+/* time helper functions */
+
+int uefi_time_compare(efi_time *a, efi_time *b)
+{
+ if (a->year < b->year) {
+ return -1;
+ }
+ if (a->year > b->year) {
+ return 1;
+ }
+
+ if (a->month < b->month) {
+ return -1;
+ }
+ if (a->month > b->month) {
+ return 1;
+ }
+
+ if (a->day < b->day) {
+ return -1;
+ }
+ if (a->day > b->day) {
+ return 1;
+ }
+
+ if (a->hour < b->hour) {
+ return -1;
+ }
+ if (a->hour > b->hour) {
+ return 1;
+ }
+
+ if (a->minute < b->minute) {
+ return -1;
+ }
+ if (a->minute > b->minute) {
+ return 1;
+ }
+
+ if (a->second < b->second) {
+ return -1;
+ }
+ if (a->second > b->second) {
+ return 1;
+ }
+
+ if (a->nanosecond < b->nanosecond) {
+ return -1;
+ }
+ if (a->nanosecond > b->nanosecond) {
+ return 1;
+ }
+
+ return 0;
+}
+
+/* ------------------------------------------------------------------ */
+/* tracing helper functions */
+
+void uefi_trace_variable(const char *action, QemuUUID guid,
+ const uint16_t *name, uint64_t name_size)
+{
+ QemuUUID be = qemu_uuid_bswap(guid);
+ char *str_uuid = qemu_uuid_unparse_strdup(&be);
+ char *str_name = uefi_ucs2_to_ascii(name, name_size);
+
+ trace_uefi_variable(action, str_name, name_size, str_uuid);
+
+ g_free(str_name);
+ g_free(str_uuid);
+}
+
+void uefi_trace_status(const char *action, efi_status status)
+{
+ switch (status) {
+ case EFI_SUCCESS:
+ trace_uefi_status(action, "success");
+ break;
+ case EFI_INVALID_PARAMETER:
+ trace_uefi_status(action, "invalid parameter");
+ break;
+ case EFI_UNSUPPORTED:
+ trace_uefi_status(action, "unsupported");
+ break;
+ case EFI_BAD_BUFFER_SIZE:
+ trace_uefi_status(action, "bad buffer size");
+ break;
+ case EFI_BUFFER_TOO_SMALL:
+ trace_uefi_status(action, "buffer too small");
+ break;
+ case EFI_WRITE_PROTECTED:
+ trace_uefi_status(action, "write protected");
+ break;
+ case EFI_OUT_OF_RESOURCES:
+ trace_uefi_status(action, "out of resources");
+ break;
+ case EFI_NOT_FOUND:
+ trace_uefi_status(action, "not found");
+ break;
+ case EFI_ACCESS_DENIED:
+ trace_uefi_status(action, "access denied");
+ break;
+ case EFI_ALREADY_STARTED:
+ trace_uefi_status(action, "already started");
+ break;
+ case EFI_SECURITY_VIOLATION:
+ trace_uefi_status(action, "security violation");
+ break;
+ default:
+ trace_uefi_status(action, "unknown error");
+ break;
+ }
+}
diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c
new file mode 100644
index 0000000..7f98d77
--- /dev/null
+++ b/hw/uefi/var-service-vars.c
@@ -0,0 +1,725 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - EfiSmmVariableProtocol implementation
+ */
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "system/dma.h"
+#include "migration/vmstate.h"
+
+#include "hw/uefi/var-service.h"
+#include "hw/uefi/var-service-api.h"
+#include "hw/uefi/var-service-edk2.h"
+
+#include "trace/trace-hw_uefi.h"
+
+#define EFI_VARIABLE_ATTRIBUTE_SUPPORTED \
+ (EFI_VARIABLE_NON_VOLATILE | \
+ EFI_VARIABLE_BOOTSERVICE_ACCESS | \
+ EFI_VARIABLE_RUNTIME_ACCESS | \
+ EFI_VARIABLE_HARDWARE_ERROR_RECORD | \
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \
+ EFI_VARIABLE_APPEND_WRITE)
+
+
+const VMStateDescription vmstate_uefi_time = {
+ .name = "uefi-time",
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(year, efi_time),
+ VMSTATE_UINT8(month, efi_time),
+ VMSTATE_UINT8(day, efi_time),
+ VMSTATE_UINT8(hour, efi_time),
+ VMSTATE_UINT8(minute, efi_time),
+ VMSTATE_UINT8(second, efi_time),
+ VMSTATE_UINT32(nanosecond, efi_time),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+const VMStateDescription vmstate_uefi_variable = {
+ .name = "uefi-variable",
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0),
+ VMSTATE_UINT32(name_size, uefi_variable),
+ VMSTATE_UINT32(data_size, uefi_variable),
+ VMSTATE_UINT32(attributes, uefi_variable),
+ VMSTATE_VBUFFER_ALLOC_UINT32(name, uefi_variable, 0, NULL, name_size),
+ VMSTATE_VBUFFER_ALLOC_UINT32(data, uefi_variable, 0, NULL, data_size),
+ VMSTATE_STRUCT(time, uefi_variable, 0, vmstate_uefi_time, efi_time),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid,
+ const uint16_t *name, uint64_t name_size)
+{
+ uefi_variable *var;
+
+ QTAILQ_FOREACH(var, &uv->variables, next) {
+ if (!uefi_str_equal(var->name, var->name_size,
+ name, name_size)) {
+ continue;
+ }
+ if (!qemu_uuid_is_equal(&var->guid, &guid)) {
+ continue;
+ }
+ if (!var->data_size) {
+ /* in process of being created/updated */
+ continue;
+ }
+ return var;
+ }
+ return NULL;
+}
+
+static uefi_variable *add_variable(uefi_vars_state *uv, QemuUUID guid,
+ const uint16_t *name, uint64_t name_size,
+ uint32_t attributes)
+{
+ uefi_variable *var;
+
+ var = g_new0(uefi_variable, 1);
+ var->guid = guid;
+ var->name = g_malloc(name_size);
+ memcpy(var->name, name, name_size);
+ var->name_size = name_size;
+ var->attributes = attributes;
+
+ var->attributes &= ~EFI_VARIABLE_APPEND_WRITE;
+
+ QTAILQ_INSERT_TAIL(&uv->variables, var, next);
+ return var;
+}
+
+static void del_variable(uefi_vars_state *uv, uefi_variable *var)
+{
+ if (!var) {
+ return;
+ }
+
+ QTAILQ_REMOVE(&uv->variables, var, next);
+ g_free(var->data);
+ g_free(var->name);
+ g_free(var->digest);
+ g_free(var);
+}
+
+static size_t variable_size(uefi_variable *var)
+{
+ size_t size;
+
+ size = sizeof(*var);
+ size += var->name_size;
+ size += var->data_size;
+ size += var->digest_size;
+ return size;
+}
+
+void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid,
+ const uint16_t *name, uint64_t name_size,
+ uint32_t attributes,
+ void *data, uint64_t data_size)
+{
+ uefi_variable *old_var, *new_var;
+
+ uefi_trace_variable(__func__, guid, name, name_size);
+
+ old_var = uefi_vars_find_variable(uv, guid, name, name_size);
+ if (old_var) {
+ uv->used_storage -= variable_size(old_var);
+ del_variable(uv, old_var);
+ }
+
+ new_var = add_variable(uv, guid, name, name_size, attributes);
+ new_var->data = g_malloc(data_size);
+ new_var->data_size = data_size;
+ memcpy(new_var->data, data, data_size);
+ uv->used_storage += variable_size(new_var);
+}
+
+void uefi_vars_clear_volatile(uefi_vars_state *uv)
+{
+ uefi_variable *var, *n;
+
+ QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) {
+ if (var->attributes & EFI_VARIABLE_NON_VOLATILE) {
+ continue;
+ }
+ uv->used_storage -= variable_size(var);
+ del_variable(uv, var);
+ }
+}
+
+void uefi_vars_clear_all(uefi_vars_state *uv)
+{
+ uefi_variable *var, *n;
+
+ QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) {
+ del_variable(uv, var);
+ }
+ uv->used_storage = 0;
+}
+
+void uefi_vars_update_storage(uefi_vars_state *uv)
+{
+ uefi_variable *var;
+
+ uv->used_storage = 0;
+ QTAILQ_FOREACH(var, &uv->variables, next) {
+ uv->used_storage += variable_size(var);
+ }
+}
+
+static gboolean check_access(uefi_vars_state *uv, uefi_variable *var)
+{
+ if (!uv->exit_boot_service) {
+ if (!(var->attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)) {
+ return false;
+ }
+ } else {
+ if (!(var->attributes & EFI_VARIABLE_RUNTIME_ACCESS)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static efi_status check_update(uefi_vars_state *uv, uefi_variable *old_var,
+ uefi_variable *new_var)
+{
+ efi_status status;
+
+ if (old_var) {
+ if (!check_access(uv, old_var)) {
+ return EFI_ACCESS_DENIED;
+ }
+ }
+
+ if (new_var) {
+ if (new_var->attributes & ~EFI_VARIABLE_ATTRIBUTE_SUPPORTED) {
+ return EFI_UNSUPPORTED;
+ }
+ if (!check_access(uv, new_var)) {
+ return EFI_ACCESS_DENIED;
+ }
+ }
+
+ if (old_var && new_var) {
+ if (old_var->attributes != new_var->attributes) {
+ return EFI_INVALID_PARAMETER;
+ }
+ }
+
+ if (new_var) {
+ /* create + update */
+ status = uefi_vars_policy_check(uv, new_var, old_var == NULL);
+ } else {
+ /* delete */
+ g_assert(old_var);
+ status = uefi_vars_policy_check(uv, old_var, false);
+ }
+ if (status != EFI_SUCCESS) {
+ return status;
+ }
+
+ status = uefi_vars_check_secure_boot(uv, new_var ?: old_var);
+ if (status != EFI_SUCCESS) {
+ return status;
+ }
+
+ return EFI_SUCCESS;
+}
+
+static void append_write(uefi_variable *old_var,
+ uefi_variable *new_var)
+{
+ uefi_vars_siglist siglist;
+ uint64_t size;
+ void *data;
+
+ uefi_vars_siglist_init(&siglist);
+ uefi_vars_siglist_parse(&siglist, old_var->data, old_var->data_size);
+ uefi_vars_siglist_parse(&siglist, new_var->data, new_var->data_size);
+
+ size = uefi_vars_siglist_blob_size(&siglist);
+ data = g_malloc(size);
+ uefi_vars_siglist_blob_generate(&siglist, data, size);
+
+ g_free(new_var->data);
+ new_var->data = data;
+ new_var->data_size = size;
+
+ uefi_vars_siglist_free(&siglist);
+}
+
+static size_t uefi_vars_mm_error(mm_header *mhdr, mm_variable *mvar,
+ uint64_t status)
+{
+ mvar->status = status;
+ return sizeof(*mvar);
+}
+
+static size_t uefi_vars_mm_get_variable(uefi_vars_state *uv, mm_header *mhdr,
+ mm_variable *mvar, void *func)
+{
+ mm_variable_access *va = func;
+ uint16_t *name;
+ void *data;
+ uefi_variable *var;
+ uint64_t length;
+
+ length = sizeof(*mvar) + sizeof(*va);
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (va->name_size > uv->max_storage ||
+ va->data_size > uv->max_storage) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES);
+ }
+
+ name = func + sizeof(*va);
+ if (uadd64_overflow(length, va->name_size, &length)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (!uefi_str_is_valid(name, va->name_size, true)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
+ }
+
+ uefi_trace_variable(__func__, va->guid, name, va->name_size);
+
+ var = uefi_vars_find_variable(uv, va->guid, name, va->name_size);
+ if (!var) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND);
+ }
+
+ /* check permissions etc. */
+ if (!check_access(uv, var)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_ACCESS_DENIED);
+ }
+
+ data = func + sizeof(*va) + va->name_size;
+ if (uadd64_overflow(length, va->data_size, &length)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+ if (uv->buf_size < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ va->attributes = var->attributes;
+ if (va->data_size < var->data_size) {
+ va->data_size = var->data_size;
+ length -= va->data_size;
+ mvar->status = EFI_BUFFER_TOO_SMALL;
+ } else {
+ va->data_size = var->data_size;
+ memcpy(data, var->data, var->data_size);
+ mvar->status = EFI_SUCCESS;
+ }
+ return length;
+}
+
+static size_t
+uefi_vars_mm_get_next_variable(uefi_vars_state *uv, mm_header *mhdr,
+ mm_variable *mvar, void *func)
+{
+ mm_next_variable *nv = func;
+ uefi_variable *var;
+ uint16_t *name;
+ uint64_t length;
+
+ length = sizeof(*mvar) + sizeof(*nv);
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (nv->name_size > uv->max_storage) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES);
+ }
+
+ name = func + sizeof(*nv);
+ if (uadd64_overflow(length, nv->name_size, &length)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (!uefi_str_is_valid(name, nv->name_size, true)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
+ }
+
+ if (uefi_strlen(name, nv->name_size) == 0) {
+ /* empty string -> first */
+ var = QTAILQ_FIRST(&uv->variables);
+ if (!var) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND);
+ }
+ } else {
+ var = uefi_vars_find_variable(uv, nv->guid, name, nv->name_size);
+ if (!var) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
+ }
+ do {
+ var = QTAILQ_NEXT(var, next);
+ } while (var && !check_access(uv, var));
+ if (!var) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND);
+ }
+ }
+
+ length = sizeof(*mvar) + sizeof(*nv) + var->name_size;
+ if (uv->buf_size < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ nv->guid = var->guid;
+ nv->name_size = var->name_size;
+ memcpy(name, var->name, var->name_size);
+ mvar->status = EFI_SUCCESS;
+ return length;
+}
+
+static bool uefi_vars_mm_digest_compare(uefi_variable *old_var,
+ uefi_variable *new_var)
+{
+ if (!old_var->digest ||
+ !new_var->digest ||
+ !old_var->digest_size ||
+ !new_var->digest_size) {
+ /* should not happen */
+ trace_uefi_vars_security_violation("inconsistent authvar digest state");
+ return false;
+ }
+ if (old_var->digest_size != new_var->digest_size) {
+ trace_uefi_vars_security_violation("authvar digest size mismatch");
+ return false;
+ }
+ if (memcmp(old_var->digest, new_var->digest,
+ old_var->digest_size) != 0) {
+ trace_uefi_vars_security_violation("authvar digest data mismatch");
+ return false;
+ }
+ return true;
+}
+
+static size_t uefi_vars_mm_set_variable(uefi_vars_state *uv, mm_header *mhdr,
+ mm_variable *mvar, void *func)
+{
+ mm_variable_access *va = func;
+ uint32_t attributes = 0;
+ uint16_t *name;
+ void *data;
+ uefi_variable *old_var, *new_var;
+ uint64_t length;
+ size_t new_storage;
+ efi_status status;
+
+ length = sizeof(*mvar) + sizeof(*va);
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ if (va->name_size > uv->max_storage ||
+ va->data_size > uv->max_storage) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES);
+ }
+
+ name = func + sizeof(*va);
+ if (uadd64_overflow(length, va->name_size, &length)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ data = func + sizeof(*va) + va->name_size;
+ if (uadd64_overflow(length, va->data_size, &length)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ g_assert(va->name_size < G_MAXUINT32);
+ g_assert(va->data_size < G_MAXUINT32);
+
+ if (!uefi_str_is_valid(name, va->name_size, true)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
+ }
+
+ uefi_trace_variable(__func__, va->guid, name, va->name_size);
+
+ old_var = uefi_vars_find_variable(uv, va->guid, name, va->name_size);
+ if (va->data_size) {
+ new_var = add_variable(uv, va->guid, name, va->name_size,
+ va->attributes);
+ if (va->attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) {
+ /* not implemented (deprecated in uefi spec) */
+ warn_report("%s: AUTHENTICATED_WRITE_ACCESS", __func__);
+ mvar->status = EFI_UNSUPPORTED;
+ goto rollback;
+ } else if (va->attributes &
+ EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
+ status = uefi_vars_check_auth_2(uv, new_var, va, data);
+ if (status != EFI_SUCCESS) {
+ mvar->status = status;
+ goto rollback;
+ }
+ if (old_var && new_var) {
+ if (uefi_time_compare(&old_var->time, &new_var->time) > 0) {
+ trace_uefi_vars_security_violation("time check failed");
+ mvar->status = EFI_SECURITY_VIOLATION;
+ goto rollback;
+ }
+ if (old_var->digest_size || new_var->digest_size) {
+ if (!uefi_vars_mm_digest_compare(old_var, new_var)) {
+ mvar->status = EFI_SECURITY_VIOLATION;
+ goto rollback;
+ }
+ }
+ }
+ } else {
+ new_var->data = g_malloc(va->data_size);
+ memcpy(new_var->data, data, va->data_size);
+ new_var->data_size = va->data_size;
+ }
+ if (!new_var->data) {
+ /* we land here when deleting authenticated variables */
+ del_variable(uv, new_var);
+ new_var = NULL;
+ }
+ } else {
+ new_var = NULL;
+ }
+
+ if (!old_var && !new_var) {
+ /* delete non-existing variable -> nothing to do */
+ mvar->status = EFI_SUCCESS;
+ return sizeof(*mvar);
+ }
+
+ /* check permissions etc. */
+ status = check_update(uv, old_var, new_var);
+ if (status != EFI_SUCCESS) {
+ mvar->status = status;
+ goto rollback;
+ }
+
+ if (va->attributes & EFI_VARIABLE_APPEND_WRITE && old_var && new_var) {
+ /* merge signature databases */
+ if (!uefi_vars_is_sb_any(new_var)) {
+ mvar->status = EFI_UNSUPPORTED;
+ goto rollback;
+ }
+ append_write(old_var, new_var);
+ }
+
+ /* check storage space */
+ new_storage = uv->used_storage;
+ if (old_var) {
+ new_storage -= variable_size(old_var);
+ }
+ if (new_var) {
+ new_storage += variable_size(new_var);
+ }
+ if (new_storage > uv->max_storage) {
+ mvar->status = EFI_OUT_OF_RESOURCES;
+ goto rollback;
+ }
+
+ attributes = new_var
+ ? new_var->attributes
+ : old_var->attributes;
+
+ /* all good, commit */
+ del_variable(uv, old_var);
+ uv->used_storage = new_storage;
+
+ if (attributes & EFI_VARIABLE_NON_VOLATILE) {
+ uefi_vars_json_save(uv);
+ }
+
+ if (new_var && uefi_vars_is_sb_pk(new_var)) {
+ uefi_vars_auth_init(uv);
+ }
+
+ mvar->status = EFI_SUCCESS;
+ return sizeof(*mvar);
+
+rollback:
+ del_variable(uv, new_var);
+ return sizeof(*mvar);
+}
+
+static size_t uefi_vars_mm_variable_info(uefi_vars_state *uv, mm_header *mhdr,
+ mm_variable *mvar, void *func)
+{
+ mm_variable_info *vi = func;
+ uint64_t length;
+
+ length = sizeof(*mvar) + sizeof(*vi);
+ if (uv->buf_size < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ vi->max_storage_size = uv->max_storage;
+ vi->free_storage_size = uv->max_storage - uv->used_storage;
+ vi->max_variable_size = uv->max_storage >> 2;
+ vi->attributes = 0;
+
+ mvar->status = EFI_SUCCESS;
+ return length;
+}
+
+static size_t
+uefi_vars_mm_get_payload_size(uefi_vars_state *uv, mm_header *mhdr,
+ mm_variable *mvar, void *func)
+{
+ mm_get_payload_size *ps = func;
+ uint64_t length;
+
+ length = sizeof(*mvar) + sizeof(*ps);
+ if (uv->buf_size < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ ps->payload_size = uv->buf_size;
+ mvar->status = EFI_SUCCESS;
+ return length;
+}
+
+static size_t
+uefi_vars_mm_lock_variable(uefi_vars_state *uv, mm_header *mhdr,
+ mm_variable *mvar, void *func)
+{
+ mm_lock_variable *lv = func;
+ variable_policy_entry *pe;
+ uint16_t *name, *dest;
+ uint64_t length;
+
+ length = sizeof(*mvar) + sizeof(*lv);
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ name = func + sizeof(*lv);
+ if (uadd64_overflow(length, lv->name_size, &length)) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+ if (mhdr->length < length) {
+ return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
+ }
+
+ uefi_trace_variable(__func__, lv->guid, name, lv->name_size);
+
+ pe = g_malloc0(sizeof(*pe) + lv->name_size);
+ pe->version = VARIABLE_POLICY_ENTRY_REVISION;
+ pe->size = sizeof(*pe) + lv->name_size;
+ pe->offset_to_name = sizeof(*pe);
+ pe->namespace = lv->guid;
+ pe->min_size = 0;
+ pe->max_size = UINT32_MAX;
+ pe->attributes_must_have = 0;
+ pe->attributes_cant_have = 0;
+ pe->lock_policy_type = VARIABLE_POLICY_TYPE_LOCK_NOW;
+
+ dest = (void *)pe + pe->offset_to_name;
+ memcpy(dest, name, lv->name_size);
+
+ uefi_vars_add_policy(uv, pe);
+ g_free(pe);
+
+ mvar->status = EFI_SUCCESS;
+ return length;
+}
+
+uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv)
+{
+ static const char *fnames[] = {
+ "zero",
+ "get-variable",
+ "get-next-variable-name",
+ "set-variable",
+ "query-variable-info",
+ "ready-to-boot",
+ "exit-boot-service",
+ "get-statistics",
+ "lock-variable",
+ "var-check-prop-set",
+ "var-check-prop-get",
+ "get-payload-size",
+ "init-runtime-cache-contect",
+ "sync-runtime-cache",
+ "get-runtime-cache-info",
+ };
+ const char *fname;
+ uint64_t length;
+
+ mm_header *mhdr = (mm_header *) uv->buffer;
+ mm_variable *mvar = (mm_variable *) (uv->buffer + sizeof(*mhdr));
+ void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mvar));
+
+ if (mhdr->length < sizeof(*mvar)) {
+ return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
+ }
+
+ fname = mvar->function < ARRAY_SIZE(fnames)
+ ? fnames[mvar->function]
+ : "unknown";
+ trace_uefi_vars_proto_cmd(fname);
+
+ switch (mvar->function) {
+ case SMM_VARIABLE_FUNCTION_GET_VARIABLE:
+ length = uefi_vars_mm_get_variable(uv, mhdr, mvar, func);
+ break;
+
+ case SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME:
+ length = uefi_vars_mm_get_next_variable(uv, mhdr, mvar, func);
+ break;
+
+ case SMM_VARIABLE_FUNCTION_SET_VARIABLE:
+ length = uefi_vars_mm_set_variable(uv, mhdr, mvar, func);
+ break;
+
+ case SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO:
+ length = uefi_vars_mm_variable_info(uv, mhdr, mvar, func);
+ break;
+
+ case SMM_VARIABLE_FUNCTION_LOCK_VARIABLE:
+ length = uefi_vars_mm_lock_variable(uv, mhdr, mvar, func);
+ break;
+
+ case SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE:
+ length = uefi_vars_mm_get_payload_size(uv, mhdr, mvar, func);
+ break;
+
+ case SMM_VARIABLE_FUNCTION_READY_TO_BOOT:
+ trace_uefi_event("ready-to-boot");
+ uv->ready_to_boot = true;
+ length = 0;
+ break;
+
+ case SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE:
+ trace_uefi_event("exit-boot-service");
+ uv->exit_boot_service = true;
+ length = 0;
+ break;
+
+ default:
+ length = uefi_vars_mm_error(mhdr, mvar, EFI_UNSUPPORTED);
+ break;
+ }
+
+ if (mhdr->length < length) {
+ mvar->status = EFI_BUFFER_TOO_SMALL;
+ }
+
+ uefi_trace_status(__func__, mvar->status);
+ return UEFI_VARS_STS_SUCCESS;
+}
diff --git a/include/hw/uefi/hardware-info.h b/include/hw/uefi/hardware-info.h
new file mode 100644
index 0000000..94c38cf
--- /dev/null
+++ b/include/hw/uefi/hardware-info.h
@@ -0,0 +1,35 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * pass hardware information to uefi
+ *
+ * see OvmfPkg/Library/HardwareInfoLib/ in edk2
+ */
+#ifndef QEMU_UEFI_HARDWARE_INFO_H
+#define QEMU_UEFI_HARDWARE_INFO_H
+
+/* data structures */
+
+typedef enum {
+ HardwareInfoTypeUndefined = 0,
+ HardwareInfoTypeHostBridge = 1,
+ HardwareInfoQemuUefiVars = 2,
+} HARDWARE_INFO_TYPE;
+
+typedef struct {
+ union {
+ uint64_t uint64;
+ HARDWARE_INFO_TYPE value;
+ } type;
+ uint64_t size;
+} HARDWARE_INFO_HEADER;
+
+typedef struct {
+ uint64_t mmio_address;
+} HARDWARE_INFO_SIMPLE_DEVICE;
+
+/* qemu functions */
+
+void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t size);
+
+#endif /* QEMU_UEFI_HARDWARE_INFO_H */
diff --git a/include/hw/uefi/var-service-api.h b/include/hw/uefi/var-service-api.h
new file mode 100644
index 0000000..0d71638
--- /dev/null
+++ b/include/hw/uefi/var-service-api.h
@@ -0,0 +1,48 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi-vars device - API of the virtual device for guest/host communication.
+ */
+#ifndef QEMU_UEFI_VAR_SERVICE_API_H
+#define QEMU_UEFI_VAR_SERVICE_API_H
+
+/* qom: device names */
+#define TYPE_UEFI_VARS_X64 "uefi-vars-x64"
+#define TYPE_UEFI_VARS_SYSBUS "uefi-vars-sysbus"
+
+/* sysbus: fdt node path */
+#define UEFI_VARS_FDT_NODE "qemu-uefi-vars"
+#define UEFI_VARS_FDT_COMPAT "qemu,uefi-vars"
+
+/* registers */
+#define UEFI_VARS_REG_MAGIC 0x00 /* 16 bit */
+#define UEFI_VARS_REG_CMD_STS 0x02 /* 16 bit */
+#define UEFI_VARS_REG_BUFFER_SIZE 0x04 /* 32 bit */
+#define UEFI_VARS_REG_DMA_BUFFER_ADDR_LO 0x08 /* 32 bit */
+#define UEFI_VARS_REG_DMA_BUFFER_ADDR_HI 0x0c /* 32 bit */
+#define UEFI_VARS_REG_PIO_BUFFER_TRANSFER 0x10 /* 8-64 bit */
+#define UEFI_VARS_REG_PIO_BUFFER_CRC32C 0x18 /* 32 bit (read-only) */
+#define UEFI_VARS_REG_FLAGS 0x1c /* 32 bit */
+#define UEFI_VARS_REGS_SIZE 0x20
+
+/* flags register */
+#define UEFI_VARS_FLAG_USE_PIO (1 << 0)
+
+/* magic value */
+#define UEFI_VARS_MAGIC_VALUE 0xef1
+
+/* command values */
+#define UEFI_VARS_CMD_RESET 0x01
+#define UEFI_VARS_CMD_DMA_MM 0x02
+#define UEFI_VARS_CMD_PIO_MM 0x03
+#define UEFI_VARS_CMD_PIO_ZERO_OFFSET 0x04
+
+/* status values */
+#define UEFI_VARS_STS_SUCCESS 0x00
+#define UEFI_VARS_STS_BUSY 0x01
+#define UEFI_VARS_STS_ERR_UNKNOWN 0x10
+#define UEFI_VARS_STS_ERR_NOT_SUPPORTED 0x11
+#define UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE 0x12
+
+
+#endif /* QEMU_UEFI_VAR_SERVICE_API_H */
diff --git a/include/hw/uefi/var-service-edk2.h b/include/hw/uefi/var-service-edk2.h
new file mode 100644
index 0000000..c743a8d
--- /dev/null
+++ b/include/hw/uefi/var-service-edk2.h
@@ -0,0 +1,227 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi-vars device - structs and defines from edk2
+ *
+ * Note: The edk2 UINTN type has been mapped to uint64_t,
+ * so the structs are compatible with 64bit edk2 builds.
+ */
+#ifndef QEMU_UEFI_VAR_SERVICE_EDK2_H
+#define QEMU_UEFI_VAR_SERVICE_EDK2_H
+
+#include "qemu/uuid.h"
+
+#define MAX_BIT 0x8000000000000000ULL
+#define ENCODE_ERROR(StatusCode) (MAX_BIT | (StatusCode))
+#define EFI_SUCCESS 0
+#define EFI_INVALID_PARAMETER ENCODE_ERROR(2)
+#define EFI_UNSUPPORTED ENCODE_ERROR(3)
+#define EFI_BAD_BUFFER_SIZE ENCODE_ERROR(4)
+#define EFI_BUFFER_TOO_SMALL ENCODE_ERROR(5)
+#define EFI_WRITE_PROTECTED ENCODE_ERROR(8)
+#define EFI_OUT_OF_RESOURCES ENCODE_ERROR(9)
+#define EFI_NOT_FOUND ENCODE_ERROR(14)
+#define EFI_ACCESS_DENIED ENCODE_ERROR(15)
+#define EFI_ALREADY_STARTED ENCODE_ERROR(20)
+#define EFI_SECURITY_VIOLATION ENCODE_ERROR(26)
+
+#define EFI_VARIABLE_NON_VOLATILE 0x01
+#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x02
+#define EFI_VARIABLE_RUNTIME_ACCESS 0x04
+#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x08
+#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x10 /* deprecated */
+#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x20
+#define EFI_VARIABLE_APPEND_WRITE 0x40
+
+/* SecureBootEnable */
+#define SECURE_BOOT_ENABLE 1
+#define SECURE_BOOT_DISABLE 0
+
+/* SecureBoot */
+#define SECURE_BOOT_MODE_ENABLE 1
+#define SECURE_BOOT_MODE_DISABLE 0
+
+/* CustomMode */
+#define CUSTOM_SECURE_BOOT_MODE 1
+#define STANDARD_SECURE_BOOT_MODE 0
+
+/* SetupMode */
+#define SETUP_MODE 1
+#define USER_MODE 0
+
+typedef uint64_t efi_status;
+typedef struct mm_header mm_header;
+
+/* EFI_MM_COMMUNICATE_HEADER */
+struct mm_header {
+ QemuUUID guid;
+ uint64_t length;
+};
+
+/* --- EfiSmmVariableProtocol ---------------------------------------- */
+
+#define SMM_VARIABLE_FUNCTION_GET_VARIABLE 1
+#define SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME 2
+#define SMM_VARIABLE_FUNCTION_SET_VARIABLE 3
+#define SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO 4
+#define SMM_VARIABLE_FUNCTION_READY_TO_BOOT 5
+#define SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE 6
+#define SMM_VARIABLE_FUNCTION_LOCK_VARIABLE 8
+#define SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE 11
+
+typedef struct mm_variable mm_variable;
+typedef struct mm_variable_access mm_variable_access;
+typedef struct mm_next_variable mm_next_variable;
+typedef struct mm_next_variable mm_lock_variable;
+typedef struct mm_variable_info mm_variable_info;
+typedef struct mm_get_payload_size mm_get_payload_size;
+
+/* SMM_VARIABLE_COMMUNICATE_HEADER */
+struct mm_variable {
+ uint64_t function;
+ uint64_t status;
+};
+
+/* SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE */
+struct QEMU_PACKED mm_variable_access {
+ QemuUUID guid;
+ uint64_t data_size;
+ uint64_t name_size;
+ uint32_t attributes;
+ /* Name */
+ /* Data */
+};
+
+/* SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME */
+struct mm_next_variable {
+ QemuUUID guid;
+ uint64_t name_size;
+ /* Name */
+};
+
+/* SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO */
+struct QEMU_PACKED mm_variable_info {
+ uint64_t max_storage_size;
+ uint64_t free_storage_size;
+ uint64_t max_variable_size;
+ uint32_t attributes;
+};
+
+/* SMM_VARIABLE_COMMUNICATE_GET_PAYLOAD_SIZE */
+struct mm_get_payload_size {
+ uint64_t payload_size;
+};
+
+/* --- VarCheckPolicyLibMmiHandler ----------------------------------- */
+
+#define VAR_CHECK_POLICY_COMMAND_DISABLE 0x01
+#define VAR_CHECK_POLICY_COMMAND_IS_ENABLED 0x02
+#define VAR_CHECK_POLICY_COMMAND_REGISTER 0x03
+#define VAR_CHECK_POLICY_COMMAND_DUMP 0x04
+#define VAR_CHECK_POLICY_COMMAND_LOCK 0x05
+
+typedef struct mm_check_policy mm_check_policy;
+typedef struct mm_check_policy_is_enabled mm_check_policy_is_enabled;
+typedef struct mm_check_policy_dump_params mm_check_policy_dump_params;
+
+/* VAR_CHECK_POLICY_COMM_HEADER */
+struct QEMU_PACKED mm_check_policy {
+ uint32_t signature;
+ uint32_t revision;
+ uint32_t command;
+ uint64_t result;
+};
+
+/* VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS */
+struct QEMU_PACKED mm_check_policy_is_enabled {
+ uint8_t state;
+};
+
+/* VAR_CHECK_POLICY_COMM_DUMP_PARAMS */
+struct QEMU_PACKED mm_check_policy_dump_params {
+ uint32_t page_requested;
+ uint32_t total_size;
+ uint32_t page_size;
+ uint8_t has_more;
+};
+
+/* --- Edk2VariablePolicyProtocol ------------------------------------ */
+
+#define VARIABLE_POLICY_ENTRY_REVISION 0x00010000
+
+#define VARIABLE_POLICY_TYPE_NO_LOCK 0
+#define VARIABLE_POLICY_TYPE_LOCK_NOW 1
+#define VARIABLE_POLICY_TYPE_LOCK_ON_CREATE 2
+#define VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE 3
+
+typedef struct variable_policy_entry variable_policy_entry;
+typedef struct variable_lock_on_var_state variable_lock_on_var_state;
+
+/* VARIABLE_POLICY_ENTRY */
+struct variable_policy_entry {
+ uint32_t version;
+ uint16_t size;
+ uint16_t offset_to_name;
+ QemuUUID namespace;
+ uint32_t min_size;
+ uint32_t max_size;
+ uint32_t attributes_must_have;
+ uint32_t attributes_cant_have;
+ uint8_t lock_policy_type;
+ uint8_t padding[3];
+ /* LockPolicy */
+ /* Name */
+};
+
+/* VARIABLE_LOCK_ON_VAR_STATE_POLICY */
+struct variable_lock_on_var_state {
+ QemuUUID namespace;
+ uint8_t value;
+ uint8_t padding;
+ /* Name */
+};
+
+/* --- variable authentication --------------------------------------- */
+
+#define WIN_CERT_TYPE_EFI_GUID 0x0EF1
+
+typedef struct efi_time efi_time;
+typedef struct efi_siglist efi_siglist;
+typedef struct variable_auth_2 variable_auth_2;
+
+/* EFI_TIME */
+struct efi_time {
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+ uint8_t pad1;
+ uint32_t nanosecond;
+ int16_t timezone;
+ uint8_t daylight;
+ uint8_t pad2;
+};
+
+/* EFI_SIGNATURE_LIST */
+struct efi_siglist {
+ QemuUUID guid_type;
+ uint32_t siglist_size;
+ uint32_t header_size;
+ uint32_t sig_size;
+};
+
+/* EFI_VARIABLE_AUTHENTICATION_2 */
+struct variable_auth_2 {
+ struct efi_time timestamp;
+
+ /* WIN_CERTIFICATE_UEFI_GUID */
+ uint32_t hdr_length;
+ uint16_t hdr_revision;
+ uint16_t hdr_cert_type;
+ QemuUUID guid_cert_type;
+ uint8_t cert_data[];
+};
+
+#endif /* QEMU_UEFI_VAR_SERVICE_EDK2_H */
diff --git a/include/hw/uefi/var-service.h b/include/hw/uefi/var-service.h
new file mode 100644
index 0000000..f7ceac4
--- /dev/null
+++ b/include/hw/uefi/var-service.h
@@ -0,0 +1,191 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi-vars device - state struct and function prototypes
+ */
+#ifndef QEMU_UEFI_VAR_SERVICE_H
+#define QEMU_UEFI_VAR_SERVICE_H
+
+#include "qemu/uuid.h"
+#include "qemu/queue.h"
+
+#include "hw/uefi/var-service-edk2.h"
+
+#define MAX_BUFFER_SIZE (64 * 1024)
+
+typedef struct uefi_variable uefi_variable;
+typedef struct uefi_var_policy uefi_var_policy;
+typedef struct uefi_vars_state uefi_vars_state;
+
+typedef struct uefi_vars_cert uefi_vars_cert;
+typedef struct uefi_vars_hash uefi_vars_hash;
+typedef struct uefi_vars_siglist uefi_vars_siglist;
+
+struct uefi_variable {
+ QemuUUID guid;
+ uint16_t *name;
+ uint32_t name_size;
+ uint32_t attributes;
+ void *data;
+ uint32_t data_size;
+ efi_time time;
+ void *digest;
+ uint32_t digest_size;
+ QTAILQ_ENTRY(uefi_variable) next;
+};
+
+struct uefi_var_policy {
+ variable_policy_entry *entry;
+ uint32_t entry_size;
+ uint16_t *name;
+ uint32_t name_size;
+
+ /* number of hashmarks (wildcard character) in name */
+ uint32_t hashmarks;
+
+ QTAILQ_ENTRY(uefi_var_policy) next;
+};
+
+struct uefi_vars_state {
+ MemoryRegion mr;
+ uint16_t sts;
+ uint32_t buf_size;
+ uint32_t buf_addr_lo;
+ uint32_t buf_addr_hi;
+ uint8_t *buffer;
+ QTAILQ_HEAD(, uefi_variable) variables;
+ QTAILQ_HEAD(, uefi_var_policy) var_policies;
+
+ /* pio transfer buffer */
+ uint32_t pio_xfer_offset;
+ uint8_t *pio_xfer_buffer;
+
+ /* boot phases */
+ bool end_of_dxe;
+ bool ready_to_boot;
+ bool exit_boot_service;
+ bool policy_locked;
+
+ /* storage accounting */
+ uint64_t max_storage;
+ uint64_t used_storage;
+
+ /* config options */
+ char *jsonfile;
+ int jsonfd;
+ bool force_secure_boot;
+ bool disable_custom_mode;
+ bool use_pio;
+};
+
+struct uefi_vars_cert {
+ QTAILQ_ENTRY(uefi_vars_cert) next;
+ QemuUUID owner;
+ uint64_t size;
+ uint8_t data[];
+};
+
+struct uefi_vars_hash {
+ QTAILQ_ENTRY(uefi_vars_hash) next;
+ QemuUUID owner;
+ uint8_t data[];
+};
+
+struct uefi_vars_siglist {
+ QTAILQ_HEAD(, uefi_vars_cert) x509;
+ QTAILQ_HEAD(, uefi_vars_hash) sha256;
+};
+
+/* vars-service-guid.c */
+extern const QemuUUID EfiGlobalVariable;
+extern const QemuUUID EfiImageSecurityDatabase;
+extern const QemuUUID EfiCustomModeEnable;
+extern const QemuUUID EfiSecureBootEnableDisable;
+
+extern const QemuUUID EfiCertSha256Guid;
+extern const QemuUUID EfiCertSha384Guid;
+extern const QemuUUID EfiCertSha512Guid;
+extern const QemuUUID EfiCertRsa2048Guid;
+extern const QemuUUID EfiCertX509Guid;
+extern const QemuUUID EfiCertTypePkcs7Guid;
+
+extern const QemuUUID EfiSmmVariableProtocolGuid;
+extern const QemuUUID VarCheckPolicyLibMmiHandlerGuid;
+
+extern const QemuUUID EfiEndOfDxeEventGroupGuid;
+extern const QemuUUID EfiEventReadyToBootGuid;
+extern const QemuUUID EfiEventExitBootServicesGuid;
+
+/* vars-service-utils.c */
+gboolean uefi_str_is_valid(const uint16_t *str, size_t len,
+ gboolean must_be_null_terminated);
+size_t uefi_strlen(const uint16_t *str, size_t len);
+gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen,
+ const uint16_t *b, size_t blen,
+ gboolean wildcards_in_a);
+gboolean uefi_str_equal(const uint16_t *a, size_t alen,
+ const uint16_t *b, size_t blen);
+char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size);
+int uefi_time_compare(efi_time *a, efi_time *b);
+void uefi_trace_variable(const char *action, QemuUUID guid,
+ const uint16_t *name, uint64_t name_size);
+void uefi_trace_status(const char *action, efi_status status);
+
+/* vars-service-core.c */
+extern const VMStateDescription vmstate_uefi_vars;
+void uefi_vars_init(Object *obj, uefi_vars_state *uv);
+void uefi_vars_realize(uefi_vars_state *uv, Error **errp);
+void uefi_vars_hard_reset(uefi_vars_state *uv);
+
+/* vars-service-json.c */
+void uefi_vars_json_init(uefi_vars_state *uv, Error **errp);
+void uefi_vars_json_save(uefi_vars_state *uv);
+void uefi_vars_json_load(uefi_vars_state *uv, Error **errp);
+
+/* vars-service-vars.c */
+extern const VMStateDescription vmstate_uefi_variable;
+uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid,
+ const uint16_t *name,
+ uint64_t name_size);
+void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid,
+ const uint16_t *name, uint64_t name_size,
+ uint32_t attributes,
+ void *data, uint64_t data_size);
+void uefi_vars_clear_volatile(uefi_vars_state *uv);
+void uefi_vars_clear_all(uefi_vars_state *uv);
+void uefi_vars_update_storage(uefi_vars_state *uv);
+uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv);
+
+/* vars-service-auth.c */
+bool uefi_vars_is_sb_pk(uefi_variable *var);
+bool uefi_vars_is_sb_any(uefi_variable *var);
+efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var,
+ mm_variable_access *va, void *data);
+efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var);
+void uefi_vars_auth_init(uefi_vars_state *uv);
+
+/* vars-service-pkcs7.c */
+efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist,
+ void **digest, uint32_t *digest_size,
+ mm_variable_access *va, void *data);
+
+/* vars-service-siglist.c */
+void uefi_vars_siglist_init(uefi_vars_siglist *siglist);
+void uefi_vars_siglist_free(uefi_vars_siglist *siglist);
+void uefi_vars_siglist_parse(uefi_vars_siglist *siglist,
+ void *data, uint64_t size);
+uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist);
+void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist,
+ void *data, uint64_t size);
+
+/* vars-service-policy.c */
+extern const VMStateDescription vmstate_uefi_var_policy;
+efi_status uefi_vars_policy_check(uefi_vars_state *uv,
+ uefi_variable *var,
+ gboolean is_newvar);
+void uefi_vars_policies_clear(uefi_vars_state *uv);
+uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv,
+ variable_policy_entry *pe);
+uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv);
+
+#endif /* QEMU_UEFI_VAR_SERVICE_H */
diff --git a/meson.build b/meson.build
index 0a2c61d..1c1982d 100644
--- a/meson.build
+++ b/meson.build
@@ -3601,6 +3601,7 @@ if have_system
'hw/ssi',
'hw/timer',
'hw/tpm',
+ 'hw/uefi',
'hw/ufs',
'hw/usb',
'hw/vfio',
diff --git a/qapi/meson.build b/qapi/meson.build
index e7bc54e..eadde4d 100644
--- a/qapi/meson.build
+++ b/qapi/meson.build
@@ -65,6 +65,7 @@ if have_system
'pci',
'rocker',
'tpm',
+ 'uefi',
]
endif
if have_system or have_tools
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index b158198..2877aff 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -81,3 +81,4 @@
{ 'include': 'vfio.json' }
{ 'include': 'cryptodev.json' }
{ 'include': 'cxl.json' }
+{ 'include': 'uefi.json' }
diff --git a/qapi/uefi.json b/qapi/uefi.json
new file mode 100644
index 0000000..bdfcabe
--- /dev/null
+++ b/qapi/uefi.json
@@ -0,0 +1,64 @@
+# -*- Mode: Python -*-
+# vim: filetype=python
+#
+
+##
+# = UEFI Variable Store
+#
+# The qemu efi variable store implementation (hw/uefi/) uses this to
+# store non-volatile variables in json format on disk.
+#
+# This is an existing format already supported by (at least) two other
+# projects, specifically https://gitlab.com/kraxel/virt-firmware and
+# https://github.com/awslabs/python-uefivars.
+##
+
+##
+# @UefiVariable:
+#
+# UEFI Variable. Check the UEFI specifification for more detailed
+# information on the fields.
+#
+# @guid: variable namespace GUID
+#
+# @name: variable name, in UTF-8 encoding.
+#
+# @attr: variable attributes.
+#
+# @data: variable value, encoded as hex string.
+#
+# @time: variable modification time. EFI_TIME struct, encoded as hex
+# string. Used only for authenticated variables, where the
+# EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute bit
+# is set.
+#
+# @digest: variable certificate digest. Used to verify the signature
+# of updates for authenticated variables. UEFI has two kinds of
+# authenticated variables. The secure boot variables ('PK',
+# 'KEK', 'db' and 'dbx') have hard coded signature checking rules.
+# For other authenticated variables the firmware stores a digest
+# of the signing certificate at variable creation time, and any
+# updates must be signed with the same certificate.
+#
+# Since: 10.0
+##
+{ 'struct' : 'UefiVariable',
+ 'data' : { 'guid' : 'str',
+ 'name' : 'str',
+ 'attr' : 'int',
+ 'data' : 'str',
+ '*time' : 'str',
+ '*digest' : 'str'}}
+
+##
+# @UefiVarStore:
+#
+# @version: currently always 2
+#
+# @variables: list of UEFI variables
+#
+# Since: 10.0
+##
+{ 'struct' : 'UefiVarStore',
+ 'data' : { 'version' : 'int',
+ 'variables' : [ 'UefiVariable' ] }}