aboutsummaryrefslogtreecommitdiff
path: root/hw/uefi
diff options
context:
space:
mode:
Diffstat (limited to 'hw/uefi')
-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.c322
-rw-r--r--hw/uefi/var-service-guid.c99
-rw-r--r--hw/uefi/var-service-json.c257
-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
16 files changed, 3242 insertions, 0 deletions
diff --git a/hw/uefi/Kconfig b/hw/uefi/Kconfig
new file mode 100644
index 0000000..046d553
--- /dev/null
+++ b/hw/uefi/Kconfig
@@ -0,0 +1,3 @@
+config UEFI_VARS
+ bool
+ default y if X86_64 || AARCH64 || RISCV64 || LOONGARCH64
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..4836a0c
--- /dev/null
+++ b/hw/uefi/var-service-core.c
@@ -0,0 +1,322 @@
+/*
+ * 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);
+ uefi_vars_json_save(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..ad3462c
--- /dev/null
+++ b/hw/uefi/var-service-json.c
@@ -0,0 +1,257 @@
+/*
+ * 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)
+{
+ g_autoptr(GString) gstr = NULL;
+ int rc;
+
+ if (uv->jsonfd == -1) {
+ return;
+ }
+
+ gstr = uefi_vars_to_json(uv);
+
+ rc = lseek(uv->jsonfd, 0, SEEK_SET);
+ if (rc < 0) {
+ warn_report("%s: lseek error", __func__);
+ return;
+ }
+
+ rc = ftruncate(uv->jsonfd, 0);
+ if (rc != 0) {
+ warn_report("%s: ftruncate error", __func__);
+ return;
+ }
+
+ rc = write(uv->jsonfd, gstr->str, gstr->len);
+ if (rc != gstr->len) {
+ warn_report("%s: write error", __func__);
+ return;
+ }
+
+ fsync(uv->jsonfd);
+}
+
+void uefi_vars_json_load(uefi_vars_state *uv, Error **errp)
+{
+ UefiVarStore *vs;
+ QObject *qobj;
+ Visitor *v;
+ char *str;
+ ssize_t len;
+ int rc;
+
+ if (uv->jsonfd == -1) {
+ return;
+ }
+
+ len = lseek(uv->jsonfd, 0, SEEK_END);
+ if (len < 0) {
+ warn_report("%s: lseek error", __func__);
+ return;
+ }
+ if (len == 0) {
+ /* empty file */
+ 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__);
+ g_free(str);
+ return;
+ }
+ 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..a5aa218
--- /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, const 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, const 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;
+}