* [PATCH v3 01/23] hw/uefi: add include/hw/uefi/var-service-api.h
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
@ 2025-02-11 9:22 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 02/23] hw/uefi: add include/hw/uefi/var-service-edk2.h Gerd Hoffmann
` (22 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:22 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel, Laszlo Ersek
This file defines the register interface of the uefi-vars device.
It's only a handful of registers: magic value, command and status
registers, location and size of the communication buffer.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
---
include/hw/uefi/var-service-api.h | 43 +++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 include/hw/uefi/var-service-api.h
diff --git a/include/hw/uefi/var-service-api.h b/include/hw/uefi/var-service-api.h
new file mode 100644
index 000000000000..6765925d9ed0
--- /dev/null
+++ b/include/hw/uefi/var-service-api.h
@@ -0,0 +1,43 @@
+/*
+ * 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
+
+/* isa: io range */
+#define UEFI_VARS_IO_BASE 0x520
+
+/* qom: device names */
+#define TYPE_UEFI_VARS_ISA "uefi-vars-isa"
+#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_BUFFER_ADDR_LO 0x08 /* 32 bit */
+#define UEFI_VARS_REG_BUFFER_ADDR_HI 0x0c /* 32 bit */
+#define UEFI_VARS_REGS_SIZE 0x10
+
+/* magic value */
+#define UEFI_VARS_MAGIC_VALUE 0xef1
+
+/* command values */
+#define UEFI_VARS_CMD_RESET 0x01
+#define UEFI_VARS_CMD_MM 0x02
+
+/* 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 */
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 02/23] hw/uefi: add include/hw/uefi/var-service-edk2.h
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
2025-02-11 9:22 ` [PATCH v3 01/23] hw/uefi: add include/hw/uefi/var-service-api.h Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 03/23] hw/uefi: add include/hw/uefi/var-service.h Gerd Hoffmann
` (21 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
A bunch of #defines and structs copied over from edk2,
mostly needed to decode and encode the messages in the
communication buffer.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
include/hw/uefi/var-service-edk2.h | 227 +++++++++++++++++++++++++++++
1 file changed, 227 insertions(+)
create mode 100644 include/hw/uefi/var-service-edk2.h
diff --git a/include/hw/uefi/var-service-edk2.h b/include/hw/uefi/var-service-edk2.h
new file mode 100644
index 000000000000..c743a8df948d
--- /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 */
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 03/23] hw/uefi: add include/hw/uefi/var-service.h
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
2025-02-11 9:22 ` [PATCH v3 01/23] hw/uefi: add include/hw/uefi/var-service-api.h Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 02/23] hw/uefi: add include/hw/uefi/var-service-edk2.h Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 04/23] hw/uefi: add var-service-guid.c Gerd Hoffmann
` (20 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Add state structs and function declarations for the uefi-vars device.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
include/hw/uefi/var-service.h | 186 ++++++++++++++++++++++++++++++++++
1 file changed, 186 insertions(+)
create mode 100644 include/hw/uefi/var-service.h
diff --git a/include/hw/uefi/var-service.h b/include/hw/uefi/var-service.h
new file mode 100644
index 000000000000..e078d2b0e68f
--- /dev/null
+++ b/include/hw/uefi/var-service.h
@@ -0,0 +1,186 @@
+/*
+ * 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;
+
+ /* 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;
+};
+
+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 */
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 04/23] hw/uefi: add var-service-guid.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (2 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 03/23] hw/uefi: add include/hw/uefi/var-service.h Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 05/23] hw/uefi: add var-service-utils.c Gerd Hoffmann
` (19 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Add variables for a bunch of UEFI GUIDs we will need.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-guid.c | 99 ++++++++++++++++++++++++++++++++++++++
1 file changed, 99 insertions(+)
create mode 100644 hw/uefi/var-service-guid.c
diff --git a/hw/uefi/var-service-guid.c b/hw/uefi/var-service-guid.c
new file mode 100644
index 000000000000..eba3655c8d30
--- /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)
+};
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 05/23] hw/uefi: add var-service-utils.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (3 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 04/23] hw/uefi: add var-service-guid.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 06/23] hw/uefi: add var-service-vars.c Gerd Hoffmann
` (18 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Add utility functions. Helpers for UEFI (ucs2) string handling.
Helpers for readable trace messages. Compare UEFI time stamps.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-utils.c | 241 ++++++++++++++++++++++++++++++++++++
1 file changed, 241 insertions(+)
create mode 100644 hw/uefi/var-service-utils.c
diff --git a/hw/uefi/var-service-utils.c b/hw/uefi/var-service-utils.c
new file mode 100644
index 000000000000..c9ef46570f48
--- /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;
+ }
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 06/23] hw/uefi: add var-service-vars.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (4 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 05/23] hw/uefi: add var-service-utils.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 07/23] hw/uefi: add var-service-auth.c Gerd Hoffmann
` (17 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
This is the uefi variable service (EfiSmmVariableProtocol), providing
functions for listing, reading and updating variables.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-vars.c | 725 +++++++++++++++++++++++++++++++++++++
1 file changed, 725 insertions(+)
create mode 100644 hw/uefi/var-service-vars.c
diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c
new file mode 100644
index 000000000000..7f98d77a38d1
--- /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;
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 07/23] hw/uefi: add var-service-auth.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (5 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 06/23] hw/uefi: add var-service-vars.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 08/23] hw/uefi: add var-service-policy.c Gerd Hoffmann
` (16 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
This implements authenticated variable handling (see AuthVariableLib in
edk2).
The by far most common use case for auth variables is secure boot. The
secure boot certificate databases ('PK', 'KEK', 'db' and 'dbx') are
authenticated variables, with update rules being specified in the UEFI
specification.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-auth.c | 361 +++++++++++++++++++++++++++++++++++++
1 file changed, 361 insertions(+)
create mode 100644 hw/uefi/var-service-auth.c
diff --git a/hw/uefi/var-service-auth.c b/hw/uefi/var-service-auth.c
new file mode 100644
index 000000000000..fba5a0956a57
--- /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);
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 08/23] hw/uefi: add var-service-policy.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (6 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 07/23] hw/uefi: add var-service-auth.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 09/23] hw/uefi: add var-service-core.c Gerd Hoffmann
` (15 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Implement variable policies (Edk2VariablePolicyProtocol).
This EFI protocol allows to define restrictions for variables.
It also allows to lock down variables (disallow write access).
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-policy.c | 370 +++++++++++++++++++++++++++++++++++
1 file changed, 370 insertions(+)
create mode 100644 hw/uefi/var-service-policy.c
diff --git a/hw/uefi/var-service-policy.c b/hw/uefi/var-service-policy.c
new file mode 100644
index 000000000000..3b1155fe4ea1
--- /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;
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (7 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 08/23] hw/uefi: add var-service-policy.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:45 ` Alexander Graf
2025-02-11 9:23 ` [PATCH v3 10/23] hw/uefi: add var-service-pkcs7.c Gerd Hoffmann
` (14 subsequent siblings)
23 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
This is the core code for guest <-> host communication. This accepts
request messages from the guest, dispatches them to the service called,
and sends back the response message.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-core.c | 237 +++++++++++++++++++++++++++++++++++++
1 file changed, 237 insertions(+)
create mode 100644 hw/uefi/var-service-core.c
diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c
new file mode 100644
index 000000000000..78a668e68fa2
--- /dev/null
+++ b/hw/uefi/var-service-core.c
@@ -0,0 +1,237 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device
+ */
+#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 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_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)
+{
+ 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 */
+ dma_memory_read(&address_space_memory, dma,
+ uv->buffer, sizeof(*mhdr),
+ MEMTXATTRS_UNSPECIFIED);
+
+ 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) */
+ dma_memory_read(&address_space_memory, dma + sizeof(*mhdr),
+ uv->buffer + sizeof(*mhdr), mhdr->length,
+ MEMTXATTRS_UNSPECIFIED);
+ 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 */
+ dma_memory_write(&address_space_memory, dma,
+ uv->buffer, sizeof(*mhdr) + mhdr->length,
+ MEMTXATTRS_UNSPECIFIED);
+
+ 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_MM:
+ return uefi_vars_cmd_mm(uv);
+ 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;
+
+ 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_BUFFER_ADDR_LO:
+ retval = uv->buf_addr_lo;
+ break;
+ case UEFI_VARS_REG_BUFFER_ADDR_HI:
+ retval = uv->buf_addr_hi;
+ break;
+ }
+ return retval;
+}
+
+static void uefi_vars_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+ uefi_vars_state *uv = opaque;
+
+ 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);
+ uv->buffer = g_malloc(uv->buf_size);
+ break;
+ case UEFI_VARS_REG_BUFFER_ADDR_LO:
+ uv->buf_addr_lo = val;
+ break;
+ case UEFI_VARS_REG_BUFFER_ADDR_HI:
+ uv->buf_addr_hi = val;
+ 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);
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-11 9:23 ` [PATCH v3 09/23] hw/uefi: add var-service-core.c Gerd Hoffmann
@ 2025-02-11 9:45 ` Alexander Graf
2025-02-12 10:24 ` Gerd Hoffmann
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-11 9:45 UTC (permalink / raw)
To: Gerd Hoffmann, qemu-devel
Cc: Eric Blake, Peter Maydell, Paolo Bonzini, Daniel P. Berrangé,
Thomas Huth, Marc-André Lureau, qemu-arm, Michael Roth,
Markus Armbruster, Philippe Mathieu-Daudé, Ard Biesheuvel
On 11.02.25 10:23, Gerd Hoffmann wrote:
> This is the core code for guest <-> host communication. This accepts
> request messages from the guest, dispatches them to the service called,
> and sends back the response message.
>
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
> hw/uefi/var-service-core.c | 237 +++++++++++++++++++++++++++++++++++++
> 1 file changed, 237 insertions(+)
> create mode 100644 hw/uefi/var-service-core.c
>
> diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c
> new file mode 100644
> index 000000000000..78a668e68fa2
> --- /dev/null
> +++ b/hw/uefi/var-service-core.c
> @@ -0,0 +1,237 @@
> +/*
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + *
> + * uefi vars device
> + */
> +#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 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_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)
> +{
> + 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 */
> + dma_memory_read(&address_space_memory, dma,
> + uv->buffer, sizeof(*mhdr),
> + MEMTXATTRS_UNSPECIFIED);
Depending on DMA sounds appealing at first, but can fall apart in corner
cases. I know of 2 cases where DMA failed for me in the EC2 equivalent
of this:
1) SEV-SNP. If you want the hypervisor to implement UEFI variable
services for you, the buffer region must always be in shared state.
Ensuring that during boot time is tricky but doable. At runtime you no
longer really have control over the sharability of pages.
2) Mac OS X. MacOS is the only OS I'm aware of that really makes use of
relocation. They move your physical pages to random locations, give you
a non-1:1 mapping to that and once you're in real OS land, you have no
more knowledge at all about the physical location of anything. Maybe you
can work around that by declaring the buffer region as MMIO space? But
then it really should be a memory region in the device.
To address the 2 cases above, I ended up implementing a special "PIO
mode" which does not rely on DMA at all:
https://github.com/aws/uefi/blob/main/edk2-stable202211/0023-edk2-stable202211-ExtVarStore-Add-support-for-PIO-transfer.patch
Also, I'm surprised you cut the variable service off at the SMM boundary
instead of the RTS callback boundary. Why is that cleaner/better than
implementing variables completely in QEMU? In the EC2 version, we just
built a separate variable store implementation that completely replaces
the edk2 variable store:
https://github.com/aws/uefi/blob/main/edk2-stable202211/0012-edk2-stable202211-nitro-Add-ExtVarStore-for-vmm-based-variable-storage.patch
It would be nice to agree on a single external variable store
implementation :).
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-11 9:45 ` Alexander Graf
@ 2025-02-12 10:24 ` Gerd Hoffmann
2025-02-12 11:30 ` Alexander Graf
0 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-12 10:24 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Hi,
> > + /* read header */
> > + dma_memory_read(&address_space_memory, dma,
> > + uv->buffer, sizeof(*mhdr),
> > + MEMTXATTRS_UNSPECIFIED);
>
> Depending on DMA sounds appealing at first, but can fall apart in corner
> cases. I know of 2 cases where DMA failed for me in the EC2 equivalent of
> this:
>
> 1) SEV-SNP. If you want the hypervisor to implement UEFI variable services
> for you, the buffer region must always be in shared state. Ensuring that
> during boot time is tricky but doable. At runtime you no longer really have
> control over the sharability of pages.
With SEV-SNP I don't see the point in using this.
Why do you use confidential computing in the first place if you trust
the host with your EFI variables? I'd rather see something simliar
running under guest control, in svsm context.
> 2) Mac OS X. MacOS is the only OS I'm aware of that really makes use of
> relocation. They move your physical pages to random locations, give you a
> non-1:1 mapping to that and once you're in real OS land, you have no more
> knowledge at all about the physical location of anything.
On the host side you have no insight into this indeed.
The firmware knows all this very well though. The OS passes a mapping
table to the firmware, efi runtime drivers can subscribe to mapping
updates and can use RT->ConvertPointer to translate addresses from
physical to virtual.
The edk2 code (https://github.com/tianocore/edk2/pull/10695) does
exactly that.
I see your driver does that too, so in theory it should work just fine.
I'm wondering what exactly the problem with macOS is?
> Also, I'm surprised you cut the variable service off at the SMM boundary
> instead of the RTS callback boundary. Why is that cleaner/better than
> implementing variables completely in QEMU?
Well, the variable service /is/ completely in qemu. See patch #6 which
implements getvariable & friends. edk2 serializes the variable calls
into a buffer and sends them over to the SMM side (or to qemu with the
patches).
I didn't feel like inventing a new serialization protocol if we already
have a proven one in the edk2 code base. Also it is possible to send
over more than just the variable call. There is a variable policy
protocol implementation (patch #8), and we also get some events
forwarded. More can easily be added should the need for that arise.
> It would be nice to agree on a single external variable store implementation
> :).
It would be nice to have nitro support merged upstream,
especially with BYOF coming.
take care,
Gerd
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 10:24 ` Gerd Hoffmann
@ 2025-02-12 11:30 ` Alexander Graf
2025-02-12 12:28 ` Gerd Hoffmann
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-12 11:30 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 12.02.25 11:24, Gerd Hoffmann wrote:
> Hi,
>
>>> + /* read header */
>>> + dma_memory_read(&address_space_memory, dma,
>>> + uv->buffer, sizeof(*mhdr),
>>> + MEMTXATTRS_UNSPECIFIED);
>> Depending on DMA sounds appealing at first, but can fall apart in corner
>> cases. I know of 2 cases where DMA failed for me in the EC2 equivalent of
>> this:
>>
>> 1) SEV-SNP. If you want the hypervisor to implement UEFI variable services
>> for you, the buffer region must always be in shared state. Ensuring that
>> during boot time is tricky but doable. At runtime you no longer really have
>> control over the sharability of pages.
> With SEV-SNP I don't see the point in using this.
>
> Why do you use confidential computing in the first place if you trust
> the host with your EFI variables? I'd rather see something simliar
> running under guest control, in svsm context.
That depends heavily on your threat model. You can use a host provided
variable store to gain variable persistence for things like boot
variables and then have an ephemeral SVSM based TPM that you use to
measure the loaded payloads. A malicious host can already replace your
root volume, so extending the threat to variables is not the end of the
world.
>
>> 2) Mac OS X. MacOS is the only OS I'm aware of that really makes use of
>> relocation. They move your physical pages to random locations, give you a
>> non-1:1 mapping to that and once you're in real OS land, you have no more
>> knowledge at all about the physical location of anything.
> On the host side you have no insight into this indeed.
>
> The firmware knows all this very well though. The OS passes a mapping
> table to the firmware, efi runtime drivers can subscribe to mapping
> updates and can use RT->ConvertPointer to translate addresses from
> physical to virtual.
>
> The edk2 code (https://github.com/tianocore/edk2/pull/10695) does
> exactly that.
>
> I see your driver does that too, so in theory it should work just fine.
> I'm wondering what exactly the problem with macOS is?
You get to know the new virtual address, but ConvertPointer never tells
you what the new *physical* address is. That means you have no idea
where to DMA from once you're in virtual land. Most OSs just keep a 1:1
map of virtual to physical, but MacOS does not.
>> Also, I'm surprised you cut the variable service off at the SMM boundary
>> instead of the RTS callback boundary. Why is that cleaner/better than
>> implementing variables completely in QEMU?
> Well, the variable service /is/ completely in qemu. See patch #6 which
> implements getvariable & friends. edk2 serializes the variable calls
> into a buffer and sends them over to the SMM side (or to qemu with the
> patches).
>
> I didn't feel like inventing a new serialization protocol if we already
> have a proven one in the edk2 code base. Also it is possible to send
> over more than just the variable call. There is a variable policy
> protocol implementation (patch #8), and we also get some events
> forwarded. More can easily be added should the need for that arise.
>
>> It would be nice to agree on a single external variable store implementation
>> :).
> It would be nice to have nitro support merged upstream,
> especially with BYOF coming.
Yes. Or converge on this protocol instead to simplify the firmware
implementation so we don't create needless work if someone wants to do
an actually trivial (and reusable?) UEFI firmware for BYOF.
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 11:30 ` Alexander Graf
@ 2025-02-12 12:28 ` Gerd Hoffmann
2025-02-12 13:45 ` Alexander Graf
0 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-12 12:28 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On Wed, Feb 12, 2025 at 12:30:20PM +0100, Alexander Graf wrote:
>
> On 12.02.25 11:24, Gerd Hoffmann wrote:
> >
> > Why do you use confidential computing in the first place if you trust
> > the host with your EFI variables? I'd rather see something simliar
> > running under guest control, in svsm context.
>
> That depends heavily on your threat model. You can use a host provided
> variable store to gain variable persistence for things like boot variables
> and then have an ephemeral SVSM based TPM that you use to measure the loaded
> payloads. A malicious host can already replace your root volume, so
> extending the threat to variables is not the end of the world.
If you go depend on measured boot instead of secure boot then yes, this
might be a workable model. Should be doable with a small svsm driver
which forwards requests to the host via svsm-controlled bounce buffer
(where the svsm has control over page properties).
> > The firmware knows all this very well though. The OS passes a mapping
> > table to the firmware, efi runtime drivers can subscribe to mapping
> > updates and can use RT->ConvertPointer to translate addresses from
> > physical to virtual.
> >
> > The edk2 code (https://github.com/tianocore/edk2/pull/10695) does
> > exactly that.
> >
> > I see your driver does that too, so in theory it should work just fine.
> > I'm wondering what exactly the problem with macOS is?
>
> You get to know the new virtual address, but ConvertPointer never tells you
> what the new *physical* address is. That means you have no idea where to DMA
> from once you're in virtual land.
Yes. Knowing both physical and virtual address works only for memory
you allocated yourself before ExitBootServices. So you can't pass on
pointers from the OS, you have to copy the data to a buffer where you
know the physical address instead. Yes, some overhead. Should still
be much faster than going to pio transfer mode ...
> > It would be nice to have nitro support merged upstream,
> > especially with BYOF coming.
>
> Yes. Or converge on this protocol instead to simplify the firmware
> implementation
Yes, that works too and would reduce your stack of unmerged patches a
bit.
take care,
Gerd
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 12:28 ` Gerd Hoffmann
@ 2025-02-12 13:45 ` Alexander Graf
2025-02-12 15:18 ` Gerd Hoffmann
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-12 13:45 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 12.02.25 13:28, Gerd Hoffmann wrote:
> On Wed, Feb 12, 2025 at 12:30:20PM +0100, Alexander Graf wrote:
>> On 12.02.25 11:24, Gerd Hoffmann wrote:
>>> Why do you use confidential computing in the first place if you trust
>>> the host with your EFI variables? I'd rather see something simliar
>>> running under guest control, in svsm context.
>> That depends heavily on your threat model. You can use a host provided
>> variable store to gain variable persistence for things like boot variables
>> and then have an ephemeral SVSM based TPM that you use to measure the loaded
>> payloads. A malicious host can already replace your root volume, so
>> extending the threat to variables is not the end of the world.
> If you go depend on measured boot instead of secure boot then yes, this
> might be a workable model. Should be doable with a small svsm driver
> which forwards requests to the host via svsm-controlled bounce buffer
> (where the svsm has control over page properties).
In a BYOF world it's even useful without SVSM at all, because the launch
digest performs measurement for you, but you still want to find your
boot variables.
>>> The firmware knows all this very well though. The OS passes a mapping
>>> table to the firmware, efi runtime drivers can subscribe to mapping
>>> updates and can use RT->ConvertPointer to translate addresses from
>>> physical to virtual.
>>>
>>> The edk2 code (https://github.com/tianocore/edk2/pull/10695) does
>>> exactly that.
>>>
>>> I see your driver does that too, so in theory it should work just fine.
>>> I'm wondering what exactly the problem with macOS is?
>> You get to know the new virtual address, but ConvertPointer never tells you
>> what the new *physical* address is. That means you have no idea where to DMA
>> from once you're in virtual land.
> Yes. Knowing both physical and virtual address works only for memory
> you allocated yourself before ExitBootServices. So you can't pass on
> pointers from the OS, you have to copy the data to a buffer where you
> know the physical address instead. Yes, some overhead. Should still
> be much faster than going to pio transfer mode ...
MacOS takes over the full physical address map past ExitBootServices:
Your code no longer has VA access to random code and it literally
memcpy()'s all preserved (virtual available) code and data to different
physical addresses. You simply have nothing that is all of 1) RAM
(mapped as cacheable on ARM), 2) known VA 3) known PA.
So we really really need a fallback mechanism that works without DMA :).
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 13:45 ` Alexander Graf
@ 2025-02-12 15:18 ` Gerd Hoffmann
2025-02-12 21:26 ` Alexander Graf
0 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-12 15:18 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Hi,
> > Yes. Knowing both physical and virtual address works only for memory
> > you allocated yourself before ExitBootServices. So you can't pass on
> > pointers from the OS, you have to copy the data to a buffer where you
> > know the physical address instead. Yes, some overhead. Should still
> > be much faster than going to pio transfer mode ...
>
> MacOS takes over the full physical address map past ExitBootServices: Your
> code no longer has VA access to random code
That is totally fine. EFI drivers must register everything they need as
runtime memory. Anything else can be unmapped by the OS when calling
EFI services.
> and it literally memcpy()'s all preserved (virtual available) code and
> data to different physical addresses.
Uhm. I have my doubts this copying behavior is blessed by the UEFI spec.
> You simply have nothing that is all of 1) RAM (mapped as cacheable on
> ARM), 2) known VA 3) known PA.
Bummer.
> So we really really need a fallback mechanism that works without DMA
> :).
On arm it should be relatively simple to move the buffer to device
memory. Just place one more region on the platform bus, advertise
address + size via device tree, done.
Not sure how to do that best on x86 though. Find 64k unused address
space over ioapic? Do we have enough free space there? And how
future-proof would that be?
take care,
Gerd
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 15:18 ` Gerd Hoffmann
@ 2025-02-12 21:26 ` Alexander Graf
2025-02-13 9:28 ` Ard Biesheuvel
2025-02-13 9:52 ` Gerd Hoffmann
0 siblings, 2 replies; 45+ messages in thread
From: Alexander Graf @ 2025-02-12 21:26 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 12.02.25 16:18, Gerd Hoffmann wrote:
> Hi,
>
>>> Yes. Knowing both physical and virtual address works only for memory
>>> you allocated yourself before ExitBootServices. So you can't pass on
>>> pointers from the OS, you have to copy the data to a buffer where you
>>> know the physical address instead. Yes, some overhead. Should still
>>> be much faster than going to pio transfer mode ...
>> MacOS takes over the full physical address map past ExitBootServices: Your
>> code no longer has VA access to random code
> That is totally fine. EFI drivers must register everything they need as
> runtime memory. Anything else can be unmapped by the OS when calling
> EFI services.
>
>> and it literally memcpy()'s all preserved (virtual available) code and
>> data to different physical addresses.
> Uhm. I have my doubts this copying behavior is blessed by the UEFI spec.
I don't remember anything in the spec prohibiting it.
>> You simply have nothing that is all of 1) RAM (mapped as cacheable on
>> ARM), 2) known VA 3) known PA.
> Bummer.
>
>> So we really really need a fallback mechanism that works without DMA
>> :).
> On arm it should be relatively simple to move the buffer to device
> memory. Just place one more region on the platform bus, advertise
> address + size via device tree, done.
That will bring back all issues with cached vs non-cached memory
accesses, no? So edk2 will always access that memory as device memory
which means it bypasses the cache, while QEMU will access it through the
cache. So that buffer would need to actually be MMIO memory I suppose?
> Not sure how to do that best on x86 though. Find 64k unused address
> space over ioapic? Do we have enough free space there? And how
> future-proof would that be?
I'm not worried yet about where we place that memory, but more about
ensuring that we actually have a working path to access it. We can
always find space in the PCI hole, as long as we properly advertise it
to all stakeholders via ACPI and memory map.
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 21:26 ` Alexander Graf
@ 2025-02-13 9:28 ` Ard Biesheuvel
2025-02-13 10:06 ` Alexander Graf
2025-02-13 9:52 ` Gerd Hoffmann
1 sibling, 1 reply; 45+ messages in thread
From: Ard Biesheuvel @ 2025-02-13 9:28 UTC (permalink / raw)
To: Alexander Graf
Cc: Gerd Hoffmann, qemu-devel, Eric Blake, Peter Maydell,
Paolo Bonzini, Daniel P. Berrangé, Thomas Huth,
Marc-André Lureau, qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé
On Wed, 12 Feb 2025 at 22:26, Alexander Graf <graf@amazon.com> wrote:
>
>
> On 12.02.25 16:18, Gerd Hoffmann wrote:
> > Hi,
> >
> >>> Yes. Knowing both physical and virtual address works only for memory
> >>> you allocated yourself before ExitBootServices. So you can't pass on
> >>> pointers from the OS, you have to copy the data to a buffer where you
> >>> know the physical address instead. Yes, some overhead. Should still
> >>> be much faster than going to pio transfer mode ...
> >> MacOS takes over the full physical address map past ExitBootServices: Your
> >> code no longer has VA access to random code
> > That is totally fine. EFI drivers must register everything they need as
> > runtime memory. Anything else can be unmapped by the OS when calling
> > EFI services.
> >
> >> and it literally memcpy()'s all preserved (virtual available) code and
> >> data to different physical addresses.
> > Uhm. I have my doubts this copying behavior is blessed by the UEFI spec.
>
>
> I don't remember anything in the spec prohibiting it.
>
The UEFI spec clearly states that runtime services must either be
called using a 1:1 mapping, or via a virtual remapping but in that
case, SetVirtualAddresMap() must be called to inform the firmware of
the new virtual mapping.
Even if this is not clearly stated, this violates the intent of the
UEFI spec: the code reasons about mappings of physical memory,
implying that the mapping is the only thing that changes. Moving
memory contents around can only be done safely after
SetVirtualAddressMap(), making it mandatory on these systems, whereas
the spec clearly states that it is entirely optional.
But whatever OSX does on x86 is irrelevant anyway: it is vertically
integrated with the firmware, which is vaguely EFI based but does not
aim for spec compliance. The OSX EULA does not permit running it on
anything other than Apple hardware. And x86 Apple hardware will be
reaching obsolescence pretty soon, at least where future development
is concerned.
My colleague filed a USWG proposal for a EFI_MEMORY_SHARED attribute
that must be honored by the OS when creating runtime mappings, and map
the region in a way that allows access by another observer (typically
the VMM but semantically it could mean other things too)
>
> >> You simply have nothing that is all of 1) RAM (mapped as cacheable on
> >> ARM), 2) known VA 3) known PA.
> > Bummer.
> >
> >> So we really really need a fallback mechanism that works without DMA
> >> :).
> > On arm it should be relatively simple to move the buffer to device
> > memory. Just place one more region on the platform bus, advertise
> > address + size via device tree, done.
>
>
> That will bring back all issues with cached vs non-cached memory
> accesses, no? So edk2 will always access that memory as device memory
> which means it bypasses the cache, while QEMU will access it through the
> cache. So that buffer would need to actually be MMIO memory I suppose?
>
Indeed. Presenting memory as MMIO just to trick the guest into mapping
it shared is not the right approach here, which is why we need
EFI_MEMORY_SHARED on ARM. On x86, using the EfiMemoryMappedIo type
happens to work, but it is a hack (e.g., you cannot allocate memory of
this type)
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-13 9:28 ` Ard Biesheuvel
@ 2025-02-13 10:06 ` Alexander Graf
0 siblings, 0 replies; 45+ messages in thread
From: Alexander Graf @ 2025-02-13 10:06 UTC (permalink / raw)
To: Ard Biesheuvel
Cc: Gerd Hoffmann, qemu-devel, Eric Blake, Peter Maydell,
Paolo Bonzini, Daniel P. Berrangé, Thomas Huth,
Marc-André Lureau, qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé
[-- Attachment #1: Type: text/plain, Size: 5575 bytes --]
On 13.02.25 10:28, Ard Biesheuvel wrote:
> On Wed, 12 Feb 2025 at 22:26, Alexander Graf<graf@amazon.com> wrote:
>>
>> On 12.02.25 16:18, Gerd Hoffmann wrote:
>>> Hi,
>>>
>>>>> Yes. Knowing both physical and virtual address works only for memory
>>>>> you allocated yourself before ExitBootServices. So you can't pass on
>>>>> pointers from the OS, you have to copy the data to a buffer where you
>>>>> know the physical address instead. Yes, some overhead. Should still
>>>>> be much faster than going to pio transfer mode ...
>>>> MacOS takes over the full physical address map past ExitBootServices: Your
>>>> code no longer has VA access to random code
>>> That is totally fine. EFI drivers must register everything they need as
>>> runtime memory. Anything else can be unmapped by the OS when calling
>>> EFI services.
>>>
>>>> and it literally memcpy()'s all preserved (virtual available) code and
>>>> data to different physical addresses.
>>> Uhm. I have my doubts this copying behavior is blessed by the UEFI spec.
>>
>> I don't remember anything in the spec prohibiting it.
>>
> The UEFI spec clearly states that runtime services must either be
> called using a 1:1 mapping, or via a virtual remapping but in that
> case, SetVirtualAddresMap() must be called to inform the firmware of
> the new virtual mapping.
Correct, which is what boot.efi does. That mapping only tells firmware
about the change from old VA -> new VA though.
> Even if this is not clearly stated, this violates the intent of the
> UEFI spec: the code reasons about mappings of physical memory,
> implying that the mapping is the only thing that changes. Moving
> memory contents around can only be done safely after
> SetVirtualAddressMap(), making it mandatory on these systems, whereas
> the spec clearly states that it is entirely optional.
The spec says that calling SetVirtualAddressMap is optional from the
firmware's point of view. A boot loader may call it - and even depend on
its functionality. The spec even goes further and almost endorses the
case boot.efi does:
> The call to SetVirtualAddressMap() must be done with the physical
> mappings. On successful return from this function, the system must
> then make any future calls with the newly assigned virtual mappings.
> All address space mappings must be done in accordance to the
> cacheability flags as specified in the original address map. [...]
You can absolutely read that paragraph as "From here on, only virtual
mappings matter".
> But whatever OSX does on x86 is irrelevant anyway: it is vertically
> integrated with the firmware, which is vaguely EFI based but does not
> aim for spec compliance. The OSX EULA does not permit running it on
> anything other than Apple hardware. And x86 Apple hardware will be
> reaching obsolescence pretty soon, at least where future development
> is concerned.
I halfway agree. It's fairly trivial to make x86 Mac OS X work with
stock edk2 [1]. All you need are a few boot services, a non-standard way
(or BootXXX variables) to find the real boot binary and APFS driver
(binary is included in every APFS FS) load support. The rest is a fairly
standard compliant UEFI environment.
But I'm not sure how that's relevant to the argument that we need a way
to perform runtime service calls without relying on DMA? In addition to
the potential VA/PA mismatch issue, relying on DMA from RTS can quickly
also get broken for any confidential compute environments where you need
to take explicit action to make memory visible to the hypervisor.
All I'm asking for is an (optional) viable path that works without DMA :).
> My colleague filed a USWG proposal for a EFI_MEMORY_SHARED attribute
> that must be honored by the OS when creating runtime mappings, and map
> the region in a way that allows access by another observer (typically
> the VMM but semantically it could mean other things too)
For something as core as a UEFI variable service that should become the
default in a generic platform like QEMU, I don't think we should rely on
new additions to the UEFI spec :(. Users want to be able to run old
Operating Systems.
>
>>>> You simply have nothing that is all of 1) RAM (mapped as cacheable on
>>>> ARM), 2) known VA 3) known PA.
>>> Bummer.
>>>
>>>> So we really really need a fallback mechanism that works without DMA
>>>> :).
>>> On arm it should be relatively simple to move the buffer to device
>>> memory. Just place one more region on the platform bus, advertise
>>> address + size via device tree, done.
>>
>> That will bring back all issues with cached vs non-cached memory
>> accesses, no? So edk2 will always access that memory as device memory
>> which means it bypasses the cache, while QEMU will access it through the
>> cache. So that buffer would need to actually be MMIO memory I suppose?
>>
> Indeed. Presenting memory as MMIO just to trick the guest into mapping
> it shared is not the right approach here, which is why we need
> EFI_MEMORY_SHARED on ARM. On x86, using the EfiMemoryMappedIo type
> happens to work, but it is a hack (e.g., you cannot allocate memory of
> this type)
The non-hacky alternative would be to expose a real 64KiB large MMIO
window into host memory that goes through full instruction emulation for
every access. Then we could expose it as real MMIO target memory to RTS
which should always work. DMA can then be an (optional) optimization on
top to copy to/from that buffer.
Alex
[1]
https://github.com/tianocore/edk2/compare/vUDK2018...agraf:edk2:vUDK2018-AppleSupportPkg
[-- Attachment #2: Type: text/html, Size: 9251 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-12 21:26 ` Alexander Graf
2025-02-13 9:28 ` Ard Biesheuvel
@ 2025-02-13 9:52 ` Gerd Hoffmann
2025-02-13 10:14 ` Alexander Graf
1 sibling, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-13 9:52 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Hi,
> That will bring back all issues with cached vs non-cached memory accesses,
> no? So edk2 will always access that memory as device memory which means it
> bypasses the cache, while QEMU will access it through the cache. So that
> buffer would need to actually be MMIO memory I suppose?
I don't think so. The firmware driver knows this actually is normal ram
and can setup mappings and memory attributes accordingly. The situation
is a bit different from vga memory bars which are handled by pci bus
management which doesn't know anything about virtualization specifics.
Well, unless macos thinks it knows everything better and goes setup
uncached mappings ...
> > Not sure how to do that best on x86 though. Find 64k unused address
> > space over ioapic? Do we have enough free space there? And how
> > future-proof would that be?
>
> I'm not worried yet about where we place that memory, but more about
> ensuring that we actually have a working path to access it. We can always
> find space in the PCI hole, as long as we properly advertise it to all
> stakeholders via ACPI and memory map.
Well, the host can't easily place stuff in the pci hole because the
guest will manage that (map pci bars etc). But making the pci hole
a bit smaller to make room is an option I think.
take care,
Gerd
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-13 9:52 ` Gerd Hoffmann
@ 2025-02-13 10:14 ` Alexander Graf
2025-02-13 14:54 ` Gerd Hoffmann
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-13 10:14 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 13.02.25 10:52, Gerd Hoffmann wrote:
> Hi,
>
>> That will bring back all issues with cached vs non-cached memory accesses,
>> no? So edk2 will always access that memory as device memory which means it
>> bypasses the cache, while QEMU will access it through the cache. So that
>> buffer would need to actually be MMIO memory I suppose?
> I don't think so. The firmware driver knows this actually is normal ram
> and can setup mappings and memory attributes accordingly. The situation
> is a bit different from vga memory bars which are handled by pci bus
> management which doesn't know anything about virtualization specifics.
>
> Well, unless macos thinks it knows everything better and goes setup
> uncached mappings ...
It's not only macOS. After SetVirtualAddressMap, the OS owns the virtual
address space of Runtime Services. So in theory it also owns
cacheability attributes of all mappings.
>
>>> Not sure how to do that best on x86 though. Find 64k unused address
>>> space over ioapic? Do we have enough free space there? And how
>>> future-proof would that be?
>> I'm not worried yet about where we place that memory, but more about
>> ensuring that we actually have a working path to access it. We can always
>> find space in the PCI hole, as long as we properly advertise it to all
>> stakeholders via ACPI and memory map.
> Well, the host can't easily place stuff in the pci hole because the
> guest will manage that (map pci bars etc). But making the pci hole
> a bit smaller to make room is an option I think.
Yes, IIRC we advertise where the hole is. I'm sure we can find a spot.
Somewhere next to the HPET :).
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-13 10:14 ` Alexander Graf
@ 2025-02-13 14:54 ` Gerd Hoffmann
2025-02-13 22:25 ` Alexander Graf
0 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-13 14:54 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On Thu, Feb 13, 2025 at 11:14:03AM +0100, Alexander Graf wrote:
>
> > I don't think so. The firmware driver knows this actually is normal ram
> > and can setup mappings and memory attributes accordingly. The situation
> > is a bit different from vga memory bars which are handled by pci bus
> > management which doesn't know anything about virtualization specifics.
> >
> > Well, unless macos thinks it knows everything better and goes setup
> > uncached mappings ...
>
> It's not only macOS. After SetVirtualAddressMap, the OS owns the virtual
> address space of Runtime Services. So in theory it also owns cacheability
> attributes of all mappings.
Hmm. Played around with the device memory approach a bit today. Looks
workable for both arm/sysbus and x86/isa. Problem is, if that does
leave any unsolved corner cases on the table it doesn't buy us much, and
the arm caching issues start to make me a bit nervous ...
So, maybe allowing pio data transfers is the better approach after all.
How do your patches pick the transfer mode? Is that dictated by the
host? Or is the guest free to choose? In case of the latter: How does
the guest decide what to do?
> Yes, IIRC we advertise where the hole is. I'm sure we can find a spot.
> Somewhere next to the HPET :).
0xfef1000 seems to be free, which is kida fun b/c of the 'ef1' in the
address.
take care,
Gerd
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-13 14:54 ` Gerd Hoffmann
@ 2025-02-13 22:25 ` Alexander Graf
2025-02-14 7:55 ` Gerd Hoffmann
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-13 22:25 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 13.02.25 15:54, Gerd Hoffmann wrote:
> On Thu, Feb 13, 2025 at 11:14:03AM +0100, Alexander Graf wrote:
>>> I don't think so. The firmware driver knows this actually is normal ram
>>> and can setup mappings and memory attributes accordingly. The situation
>>> is a bit different from vga memory bars which are handled by pci bus
>>> management which doesn't know anything about virtualization specifics.
>>>
>>> Well, unless macos thinks it knows everything better and goes setup
>>> uncached mappings ...
>> It's not only macOS. After SetVirtualAddressMap, the OS owns the virtual
>> address space of Runtime Services. So in theory it also owns cacheability
>> attributes of all mappings.
> Hmm. Played around with the device memory approach a bit today. Looks
> workable for both arm/sysbus and x86/isa. Problem is, if that does
> leave any unsolved corner cases on the table it doesn't buy us much, and
> the arm caching issues start to make me a bit nervous ...
>
> So, maybe allowing pio data transfers is the better approach after all.
>
> How do your patches pick the transfer mode? Is that dictated by the
> host? Or is the guest free to choose? In case of the latter: How does
> the guest decide what to do?
In our version, the guest gets to pick. It defaults to the DMA interface
unless it detects that it's running either the macOS logic (a case you
can ignore for now) or is running with SEV-SNP.
I think for the upstream interface, it would be best to have the host
indicate which one it recommends the guest to use. That way you can
force the fallback path without requiring tedious edk2 changes.
>
>> Yes, IIRC we advertise where the hole is. I'm sure we can find a spot.
>> Somewhere next to the HPET :).
> 0xfef1000 seems to be free, which is kida fun b/c of the 'ef1' in the
> address.
True, I love it! :)
It's not enough address space to fit the full 64k buffer though, right?
Would all of 0xfef00000 be free by chance? Then you could just direct
map the transfer buffer there.
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-13 22:25 ` Alexander Graf
@ 2025-02-14 7:55 ` Gerd Hoffmann
2025-02-14 9:51 ` Alexander Graf
0 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-14 7:55 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Hi,
> > How do your patches pick the transfer mode? Is that dictated by the
> > host? Or is the guest free to choose? In case of the latter: How does
> > the guest decide what to do?
>
> In our version, the guest gets to pick. It defaults to the DMA interface
> unless it detects that it's running either the macOS logic (a case you can
> ignore for now) or is running with SEV-SNP.
>
> I think for the upstream interface, it would be best to have the host
> indicate which one it recommends the guest to use. That way you can force
> the fallback path without requiring tedious edk2 changes.
I'm more thinking about a hard switch, i.e. the host would support only
the one or the other. That way we'll go need less register space,
because we'll need either the buffer location register (dma mode) or the
pio transfer register (pio mode) but not both at the same time so they
can share the location.
> > 0xfef1000 seems to be free, which is kida fun b/c of the 'ef1' in the
> > address.
>
> True, I love it! :)
>
> It's not enough address space to fit the full 64k buffer though, right?
> Would all of 0xfef00000 be free by chance? Then you could just direct map
> the transfer buffer there.
All of 0xfef00000 is 1M, i.e. 16 x 64k.
take care,
Gerd
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-14 7:55 ` Gerd Hoffmann
@ 2025-02-14 9:51 ` Alexander Graf
2025-02-14 11:16 ` Gerd Hoffmann
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-14 9:51 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 14.02.25 08:55, Gerd Hoffmann wrote:
> Hi,
>
>>> How do your patches pick the transfer mode? Is that dictated by the
>>> host? Or is the guest free to choose? In case of the latter: How does
>>> the guest decide what to do?
>> In our version, the guest gets to pick. It defaults to the DMA interface
>> unless it detects that it's running either the macOS logic (a case you can
>> ignore for now) or is running with SEV-SNP.
>>
>> I think for the upstream interface, it would be best to have the host
>> indicate which one it recommends the guest to use. That way you can force
>> the fallback path without requiring tedious edk2 changes.
> I'm more thinking about a hard switch, i.e. the host would support only
> the one or the other. That way we'll go need less register space,
> because we'll need either the buffer location register (dma mode) or the
> pio transfer register (pio mode) but not both at the same time so they
> can share the location.
The nice thing about supporting both in the hypervisor and advertising
preference is that a minimal guest firmware could choose to only support
the safe one. Given the simplicity of the DMA protocol, it's not a hill
I will die on though :).
I also like to have dedicated register spaces per component. So even if
you choose to make it a hard split, I think we're better off with 4k at
0xfef10000 for control and 64k at 0xfef20000 for the buffer for example.
Even in the buffer case, you need some control registers. And you want
to leave the door open to making the buffer space be a direct RAM map,
which means you want to make it a page granule of the largest typical
page size (64k).
>
>>> 0xfef1000 seems to be free, which is kida fun b/c of the 'ef1' in the
>>> address.
>> True, I love it! :)
>>
>> It's not enough address space to fit the full 64k buffer though, right?
>> Would all of 0xfef00000 be free by chance? Then you could just direct map
>> the transfer buffer there.
> All of 0xfef00000 is 1M, i.e. 16 x 64k.
Oh, there was a missing 0! So the target space is really 64k right now
(0xfef10000).
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-14 9:51 ` Alexander Graf
@ 2025-02-14 11:16 ` Gerd Hoffmann
2025-02-14 12:22 ` Alexander Graf
0 siblings, 1 reply; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-14 11:16 UTC (permalink / raw)
To: Alexander Graf
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On Fri, Feb 14, 2025 at 10:51:17AM +0100, Alexander Graf wrote:
> I also like to have dedicated register spaces per component. So even if you
> choose to make it a hard split, I think we're better off with 4k at
> 0xfef10000 for control and 64k at 0xfef20000 for the buffer for example.
Well, if we go for PIO transfer mode instead of device memory we don't
need map the buffer any more.
The control registers for the x86 variant are in io address space right
now (0x520, next to fw_cfg). We could place them in a mmio page @
0xfef10000 instead. Any preference, and if so, why?
First sketch below, on top of this series. No edk2 counterpart yet, so
untested beyond compiling the code.
take care,
Gerd
--------------------- cut here ------------------------
diff --git a/include/hw/uefi/var-service-api.h b/include/hw/uefi/var-service-api.h
index 6765925d9ed0..99911e904652 100644
--- a/include/hw/uefi/var-service-api.h
+++ b/include/hw/uefi/var-service-api.h
@@ -21,16 +21,21 @@
#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_BUFFER_ADDR_LO 0x08 /* 32 bit */
-#define UEFI_VARS_REG_BUFFER_ADDR_HI 0x0c /* 32 bit */
-#define UEFI_VARS_REGS_SIZE 0x10
+#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_RESERVED 0x1c /* 32 bit */
+#define UEFI_VARS_REGS_SIZE 0x20
/* magic value */
#define UEFI_VARS_MAGIC_VALUE 0xef1
/* command values */
#define UEFI_VARS_CMD_RESET 0x01
-#define UEFI_VARS_CMD_MM 0x02
+#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
diff --git a/include/hw/uefi/var-service.h b/include/hw/uefi/var-service.h
index e078d2b0e68f..7dbede659a8f 100644
--- a/include/hw/uefi/var-service.h
+++ b/include/hw/uefi/var-service.h
@@ -56,6 +56,10 @@ struct uefi_vars_state {
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;
diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c
index 78a668e68fa2..a96b66934769 100644
--- a/hw/uefi/var-service-core.c
+++ b/hw/uefi/var-service-core.c
@@ -4,6 +4,7 @@
* uefi vars device
*/
#include "qemu/osdep.h"
+#include "qemu/crc32c.h"
#include "system/dma.h"
#include "migration/vmstate.h"
@@ -41,6 +42,7 @@ const VMStateDescription vmstate_uefi_vars = {
VMSTATE_UINT32(buf_size, uefi_vars_state),
VMSTATE_UINT32(buf_addr_lo, uefi_vars_state),
VMSTATE_UINT32(buf_addr_hi, uefi_vars_state),
+ /* TODO: pio xfer offset + buffer */
VMSTATE_BOOL(end_of_dxe, uefi_vars_state),
VMSTATE_BOOL(ready_to_boot, uefi_vars_state),
VMSTATE_BOOL(exit_boot_service, uefi_vars_state),
@@ -54,7 +56,7 @@ const VMStateDescription vmstate_uefi_vars = {
},
};
-static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv)
+static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv, bool dma_mode)
{
hwaddr dma;
mm_header *mhdr;
@@ -69,9 +71,13 @@ static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv)
}
/* read header */
- dma_memory_read(&address_space_memory, dma,
- uv->buffer, sizeof(*mhdr),
- MEMTXATTRS_UNSPECIFIED);
+ 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;
@@ -81,9 +87,15 @@ static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv)
}
/* read buffer (excl header) */
- dma_memory_read(&address_space_memory, dma + sizeof(*mhdr),
- uv->buffer + sizeof(*mhdr), mhdr->length,
- MEMTXATTRS_UNSPECIFIED);
+ 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 */
@@ -113,9 +125,15 @@ static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv)
}
/* write buffer */
- dma_memory_write(&address_space_memory, dma,
- uv->buffer, sizeof(*mhdr) + mhdr->length,
- MEMTXATTRS_UNSPECIFIED);
+ 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;
}
@@ -150,8 +168,13 @@ static uint32_t uefi_vars_cmd(uefi_vars_state *uv, uint32_t cmd)
case UEFI_VARS_CMD_RESET:
uefi_vars_soft_reset(uv);
return UEFI_VARS_STS_SUCCESS;
- case UEFI_VARS_CMD_MM:
- return uefi_vars_cmd_mm(uv);
+ 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;
}
@@ -161,6 +184,7 @@ 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);
@@ -174,12 +198,37 @@ static uint64_t uefi_vars_read(void *opaque, hwaddr addr, unsigned size)
case UEFI_VARS_REG_BUFFER_SIZE:
retval = uv->buf_size;
break;
- case UEFI_VARS_REG_BUFFER_ADDR_LO:
+ case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO:
retval = uv->buf_addr_lo;
break;
- case UEFI_VARS_REG_BUFFER_ADDR_HI:
+ 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;
}
return retval;
}
@@ -187,6 +236,7 @@ static uint64_t uefi_vars_read(void *opaque, hwaddr addr, unsigned size)
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);
@@ -200,14 +250,40 @@ static void uefi_vars_write(void *opaque, hwaddr addr, uint64_t val, unsigned si
}
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_BUFFER_ADDR_LO:
+ case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO:
uv->buf_addr_lo = val;
break;
- case UEFI_VARS_REG_BUFFER_ADDR_HI:
+ 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:
+ default:
+ break;
}
}
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v3 09/23] hw/uefi: add var-service-core.c
2025-02-14 11:16 ` Gerd Hoffmann
@ 2025-02-14 12:22 ` Alexander Graf
0 siblings, 0 replies; 45+ messages in thread
From: Alexander Graf @ 2025-02-14 12:22 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
On 14.02.25 12:16, Gerd Hoffmann wrote:
> On Fri, Feb 14, 2025 at 10:51:17AM +0100, Alexander Graf wrote:
>
>> I also like to have dedicated register spaces per component. So even if you
>> choose to make it a hard split, I think we're better off with 4k at
>> 0xfef10000 for control and 64k at 0xfef20000 for the buffer for example.
> Well, if we go for PIO transfer mode instead of device memory we don't
> need map the buffer any more.
>
> The control registers for the x86 variant are in io address space right
> now (0x520, next to fw_cfg). We could place them in a mmio page @
> 0xfef10000 instead. Any preference, and if so, why?
I did the same mistake in my version and use PIO for x86 but MMIO for
ARM. In hindsight, I think the same mechanism for both would have
simplified things a lot: You get better testing coverage of the exact
same code path. If you split between PIO and MMIO, you always have
issues that only pop up in one of the implementations. It complexifies
your test matrix for little gain.
Since you need an MMIO avenue anyway, you may as well always use that.
This is not a high performance interface where the exit latency
difference between PIO and MMIO really matters.
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* [PATCH v3 10/23] hw/uefi: add var-service-pkcs7.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (8 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 09/23] hw/uefi: add var-service-core.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 11/23] hw/uefi: add var-service-pkcs7-stub.c Gerd Hoffmann
` (13 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
This implements pkcs7 signature verification using gnutls.
Needed to check authenticated variable updates.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-pkcs7.c | 436 ++++++++++++++++++++++++++++++++++++
1 file changed, 436 insertions(+)
create mode 100644 hw/uefi/var-service-pkcs7.c
diff --git a/hw/uefi/var-service-pkcs7.c b/hw/uefi/var-service-pkcs7.c
new file mode 100644
index 000000000000..32accf4e44e0
--- /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;
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 11/23] hw/uefi: add var-service-pkcs7-stub.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (9 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 10/23] hw/uefi: add var-service-pkcs7.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 12/23] hw/uefi: add var-service-siglist.c Gerd Hoffmann
` (12 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
pkcs7 stub which is used in case gnutls is not available.
It throws EFI_WRITE_PROTECTED errors unconditionally, so all
authenticated variables are readonly for the guest.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-pkcs7-stub.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
create mode 100644 hw/uefi/var-service-pkcs7-stub.c
diff --git a/hw/uefi/var-service-pkcs7-stub.c b/hw/uefi/var-service-pkcs7-stub.c
new file mode 100644
index 000000000000..118cba446d4b
--- /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;
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 12/23] hw/uefi: add var-service-siglist.c
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (10 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 11/23] hw/uefi: add var-service-pkcs7-stub.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 13/23] hw/uefi: add var-service-json.c + qapi for NV vars Gerd Hoffmann
` (11 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Functions to serialize and de-serialize EFI signature databases. This
is needed to merge signature databases (happens in practice when
appending dbx updates) and also to extract the certificates for
pkcs7 signature verification.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-siglist.c | 212 ++++++++++++++++++++++++++++++++++
1 file changed, 212 insertions(+)
create mode 100644 hw/uefi/var-service-siglist.c
diff --git a/hw/uefi/var-service-siglist.c b/hw/uefi/var-service-siglist.c
new file mode 100644
index 000000000000..8948f1b78471
--- /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);
+}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 13/23] hw/uefi: add var-service-json.c + qapi for NV vars.
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (11 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 12/23] hw/uefi: add var-service-siglist.c Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 14/23] hw/uefi: add trace-events Gerd Hoffmann
` (10 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Define qapi schema for the uefi variable store state.
Use it and the generated visitor helper functions to store persistent
(EFI_VARIABLE_NON_VOLATILE) variables in JSON format on disk.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-json.c | 242 +++++++++++++++++++++++++++++++++++++
qapi/meson.build | 1 +
qapi/qapi-schema.json | 1 +
qapi/uefi.json | 45 +++++++
4 files changed, 289 insertions(+)
create mode 100644 hw/uefi/var-service-json.c
create mode 100644 qapi/uefi.json
diff --git a/hw/uefi/var-service-json.c b/hw/uefi/var-service-json.c
new file mode 100644
index 000000000000..ae2fc7726ac6
--- /dev/null
+++ b/hw/uefi/var-service-json.c
@@ -0,0 +1,242 @@
+/*
+ * 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 "qapi/dealloc-visitor.h"
+#include "qapi/qobject-input-visitor.h"
+#include "qapi/qobject-output-visitor.h"
+#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qjson.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/qapi/meson.build b/qapi/meson.build
index e7bc54e5d047..eadde4db307f 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 b1581988e4eb..2877aff73d0c 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 000000000000..c268ed11b70c
--- /dev/null
+++ b/qapi/uefi.json
@@ -0,0 +1,45 @@
+# -*- Mode: Python -*-
+# vim: filetype=python
+#
+
+##
+# @UefiVariable:
+#
+# UEFI Variable
+#
+# @guid: variable namespace guid
+#
+# @name: variable name (utf-8)
+#
+# @attr: variable attributes
+#
+# @data: variable content (base64)
+#
+# @time: variable modification time (EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS).
+#
+# @digest: variable certificate digest (EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS).
+#
+# Since: 10.0
+##
+{ 'struct' : 'UefiVariable',
+ 'data' : { 'guid' : 'str',
+ 'name' : 'str',
+ 'attr' : 'int',
+ 'data' : 'str',
+ '*time' : 'str',
+ '*digest' : 'str'}}
+
+##
+# @UefiVarStore:
+#
+# UEFI Variable Store
+#
+# @version: 2
+#
+# @variables: list of uefi variables
+#
+# Since: 10.0
+##
+{ 'struct' : 'UefiVarStore',
+ 'data' : { 'version' : 'int',
+ 'variables' : [ 'UefiVariable' ] }}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 14/23] hw/uefi: add trace-events
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (12 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 13/23] hw/uefi: add var-service-json.c + qapi for NV vars Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 15/23] hw/uefi: add UEFI_VARS to Kconfig Gerd Hoffmann
` (9 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Add trace events for debugging and trouble shooting.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/trace-events | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 hw/uefi/trace-events
diff --git a/hw/uefi/trace-events b/hw/uefi/trace-events
new file mode 100644
index 000000000000..3694712a946d
--- /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"
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 15/23] hw/uefi: add UEFI_VARS to Kconfig
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (13 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 14/23] hw/uefi: add trace-events Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 16/23] hw/uefi: add to meson Gerd Hoffmann
` (8 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Add UEFI_VARS config option, enable by default for x86_64 and aarch64.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/Kconfig | 1 +
hw/uefi/Kconfig | 3 +++
2 files changed, 4 insertions(+)
create mode 100644 hw/uefi/Kconfig
diff --git a/hw/Kconfig b/hw/Kconfig
index 1b4e9bb07f7d..c4dfe2e7af7c 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/uefi/Kconfig b/hw/uefi/Kconfig
new file mode 100644
index 000000000000..ca6c2bc46a96
--- /dev/null
+++ b/hw/uefi/Kconfig
@@ -0,0 +1,3 @@
+config UEFI_VARS
+ bool
+ default y if X86_64 || AARCH64
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 16/23] hw/uefi: add to meson
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (14 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 15/23] hw/uefi: add UEFI_VARS to Kconfig Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 17/23] hw/uefi: add uefi-vars-sysbus device Gerd Hoffmann
` (7 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Wire up uefi-vars in the build system.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/meson.build | 1 +
hw/uefi/meson.build | 18 ++++++++++++++++++
meson.build | 1 +
3 files changed, 20 insertions(+)
create mode 100644 hw/uefi/meson.build
diff --git a/hw/meson.build b/hw/meson.build
index b827c82c5d7b..138f5d59e178 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/meson.build b/hw/uefi/meson.build
new file mode 100644
index 000000000000..d280881f457a
--- /dev/null
+++ b/hw/uefi/meson.build
@@ -0,0 +1,18 @@
+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'))
+ 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/meson.build b/meson.build
index 131b2225ab67..9202a35cdff1 100644
--- a/meson.build
+++ b/meson.build
@@ -3584,6 +3584,7 @@ if have_system
'hw/ssi',
'hw/timer',
'hw/tpm',
+ 'hw/uefi',
'hw/ufs',
'hw/usb',
'hw/vfio',
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 17/23] hw/uefi: add uefi-vars-sysbus device
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (15 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 16/23] hw/uefi: add to meson Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 18/23] hw/uefi-vars-sysbus: qemu platform bus support Gerd Hoffmann
` (6 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
This adds sysbus bindings for the variable service.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-sysbus.c | 89 ++++++++++++++++++++++++++++++++++++
hw/uefi/meson.build | 3 +-
2 files changed, 91 insertions(+), 1 deletion(-)
create mode 100644 hw/uefi/var-service-sysbus.c
diff --git a/hw/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c
new file mode 100644
index 000000000000..1815c0b8d27f
--- /dev/null
+++ b/hw/uefi/var-service-sysbus.c
@@ -0,0 +1,89 @@
+/*
+ * 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/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),
+};
+
+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;
+ 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);
+}
+
+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_sysbus_register_types(void)
+{
+ type_register_static(&uefi_vars_sysbus_info);
+}
+
+type_init(uefi_vars_sysbus_register_types)
diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build
index d280881f457a..cf2d26e9bf9a 100644
--- a/hw/uefi/meson.build
+++ b/hw/uefi/meson.build
@@ -6,7 +6,8 @@ if (config_all_devices.has_key('CONFIG_UEFI_VARS'))
'var-service-auth.c',
'var-service-guid.c',
'var-service-utils.c',
- 'var-service-policy.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'))
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 18/23] hw/uefi-vars-sysbus: qemu platform bus support
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (16 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 17/23] hw/uefi: add uefi-vars-sysbus device Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 19/23] hw/uefi-vars-sysbus: allow for arm virt Gerd Hoffmann
` (5 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Add and register function to create an device tree entry when
the device is added to the qemu platform bus.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/core/sysbus-fdt.c | 24 ++++++++++++++++++++++++
hw/uefi/var-service-sysbus.c | 1 +
2 files changed, 25 insertions(+)
diff --git a/hw/core/sysbus-fdt.c b/hw/core/sysbus-fdt.c
index 774c0aed41b5..e85066b90563 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/uefi/var-service-sysbus.c b/hw/uefi/var-service-sysbus.c
index 1815c0b8d27f..2d6a602a7993 100644
--- a/hw/uefi/var-service-sysbus.c
+++ b/hw/uefi/var-service-sysbus.c
@@ -67,6 +67,7 @@ static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data)
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);
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 19/23] hw/uefi-vars-sysbus: allow for arm virt
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (17 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 18/23] hw/uefi-vars-sysbus: qemu platform bus support Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 20/23] hw/uefi: add uefi-vars-isa device Gerd Hoffmann
` (4 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Allow the device being added to aarch64 virt VMs.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/arm/virt.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/arm/virt.c b/hw/arm/virt.c
index 99e0a68b6c55..6146f47f746e 100644
--- a/hw/arm/virt.c
+++ b/hw/arm/virt.c
@@ -81,6 +81,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"
@@ -3120,6 +3121,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
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 20/23] hw/uefi: add uefi-vars-isa device
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (18 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 19/23] hw/uefi-vars-sysbus: allow for arm virt Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 21/23] hw/uefi-vars-isa: add acpi device Gerd Hoffmann
` (3 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
This adds isa bindings for the variable service.
Usage: qemu-system-x86_64 -device uefi-vars-isa,jsonfile=/path/to/uefivars.json
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-isa.c | 90 +++++++++++++++++++++++++++++++++++++++
hw/uefi/Kconfig | 6 +++
hw/uefi/meson.build | 5 +++
3 files changed, 101 insertions(+)
create mode 100644 hw/uefi/var-service-isa.c
diff --git a/hw/uefi/var-service-isa.c b/hw/uefi/var-service-isa.c
new file mode 100644
index 000000000000..8247b9ae054a
--- /dev/null
+++ b/hw/uefi/var-service-isa.c
@@ -0,0 +1,90 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * uefi vars device - ISA variant for x64.
+ */
+#include "qemu/osdep.h"
+#include "migration/vmstate.h"
+
+#include "hw/isa/isa.h"
+#include "hw/qdev-properties.h"
+
+#include "hw/uefi/var-service.h"
+#include "hw/uefi/var-service-api.h"
+
+OBJECT_DECLARE_SIMPLE_TYPE(uefi_vars_isa_state, UEFI_VARS_ISA)
+
+struct uefi_vars_isa_state {
+ ISADevice parent_obj;
+ struct uefi_vars_state state;
+};
+
+static const VMStateDescription vmstate_uefi_vars_isa = {
+ .name = TYPE_UEFI_VARS_ISA,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(state, uefi_vars_isa_state, 0,
+ vmstate_uefi_vars, uefi_vars_state),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property uefi_vars_isa_properties[] = {
+ DEFINE_PROP_SIZE("size", uefi_vars_isa_state, state.max_storage,
+ 256 * 1024),
+ DEFINE_PROP_STRING("jsonfile", uefi_vars_isa_state, state.jsonfile),
+ DEFINE_PROP_BOOL("force-secure-boot", uefi_vars_isa_state,
+ state.force_secure_boot, false),
+ DEFINE_PROP_BOOL("disable-custom-mode", uefi_vars_isa_state,
+ state.disable_custom_mode, false),
+};
+
+static void uefi_vars_isa_init(Object *obj)
+{
+ uefi_vars_isa_state *uv = UEFI_VARS_ISA(obj);
+
+ uefi_vars_init(obj, &uv->state);
+}
+
+static void uefi_vars_isa_reset(DeviceState *dev)
+{
+ uefi_vars_isa_state *uv = UEFI_VARS_ISA(dev);
+
+ uefi_vars_hard_reset(&uv->state);
+}
+
+static void uefi_vars_isa_realize(DeviceState *dev, Error **errp)
+{
+ uefi_vars_isa_state *uv = UEFI_VARS_ISA(dev);
+ ISADevice *isa = ISA_DEVICE(dev);
+
+ isa_register_ioport(isa, &uv->state.mr, UEFI_VARS_IO_BASE);
+ uefi_vars_realize(&uv->state, errp);
+}
+
+static void uefi_vars_isa_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = uefi_vars_isa_realize;
+ dc->vmsd = &vmstate_uefi_vars_isa;
+ device_class_set_legacy_reset(dc, uefi_vars_isa_reset);
+ device_class_set_props(dc, uefi_vars_isa_properties);
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo uefi_vars_isa_info = {
+ .name = TYPE_UEFI_VARS_ISA,
+ .parent = TYPE_ISA_DEVICE,
+ .instance_size = sizeof(uefi_vars_isa_state),
+ .instance_init = uefi_vars_isa_init,
+ .class_init = uefi_vars_isa_class_init,
+};
+module_obj(TYPE_UEFI_VARS_ISA);
+module_dep("hw-uefi-vars");
+
+static void uefi_vars_isa_register_types(void)
+{
+ type_register_static(&uefi_vars_isa_info);
+}
+
+type_init(uefi_vars_isa_register_types)
diff --git a/hw/uefi/Kconfig b/hw/uefi/Kconfig
index ca6c2bc46a96..feb9f6de5e30 100644
--- a/hw/uefi/Kconfig
+++ b/hw/uefi/Kconfig
@@ -1,3 +1,9 @@
config UEFI_VARS
bool
default y if X86_64 || AARCH64
+
+config UEFI_VARS_ISA
+ bool
+ default y
+ depends on UEFI_VARS
+ depends on ISA_BUS
diff --git a/hw/uefi/meson.build b/hw/uefi/meson.build
index cf2d26e9bf9a..0f191c436bcf 100644
--- a/hw/uefi/meson.build
+++ b/hw/uefi/meson.build
@@ -14,6 +14,11 @@ if (config_all_devices.has_key('CONFIG_UEFI_VARS'))
uefi_vars_ss.add(files('var-service-siglist.c'))
endif
+uefi_vars_isa_ss = ss.source_set()
+uefi_vars_isa_ss.add(when: 'CONFIG_UEFI_VARS_ISA',
+ if_true: files('var-service-isa.c'))
+
modules += { 'hw-uefi' : {
'vars' : uefi_vars_ss,
+ 'vars-isa' : uefi_vars_isa_ss,
}}
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 21/23] hw/uefi-vars-isa: add acpi device
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (19 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 20/23] hw/uefi: add uefi-vars-isa device Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 22/23] docs: add uefi variable service documentation Gerd Hoffmann
` (2 subsequent siblings)
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Tell the guest OS the io address range is used.
Shows up in /proc/ioports in linux.
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
hw/uefi/var-service-isa.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/hw/uefi/var-service-isa.c b/hw/uefi/var-service-isa.c
index 8247b9ae054a..24d1cae1bb19 100644
--- a/hw/uefi/var-service-isa.c
+++ b/hw/uefi/var-service-isa.c
@@ -6,6 +6,7 @@
#include "qemu/osdep.h"
#include "migration/vmstate.h"
+#include "hw/acpi/acpi_aml_interface.h"
#include "hw/isa/isa.h"
#include "hw/qdev-properties.h"
@@ -61,12 +62,32 @@ static void uefi_vars_isa_realize(DeviceState *dev, Error **errp)
uefi_vars_realize(&uv->state, errp);
}
+static void uefi_vars_isa_build_aml(AcpiDevAmlIf *adev, Aml *scope)
+{
+ Aml *dev;
+ Aml *crs;
+
+ crs = aml_resource_template();
+ aml_append(crs, aml_io(AML_DECODE16,
+ UEFI_VARS_IO_BASE, UEFI_VARS_IO_BASE,
+ 0x00, UEFI_VARS_REGS_SIZE));
+
+ dev = aml_device("QEFI");
+ aml_append(dev, aml_name_decl("_HID", aml_string("UEFIVARS")));
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xb)));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+
+ aml_append(scope, dev);
+}
+
static void uefi_vars_isa_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
+ AcpiDevAmlIfClass *adevc = ACPI_DEV_AML_IF_CLASS(klass);
dc->realize = uefi_vars_isa_realize;
dc->vmsd = &vmstate_uefi_vars_isa;
+ adevc->build_dev_aml = uefi_vars_isa_build_aml;
device_class_set_legacy_reset(dc, uefi_vars_isa_reset);
device_class_set_props(dc, uefi_vars_isa_properties);
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
@@ -78,6 +99,10 @@ static const TypeInfo uefi_vars_isa_info = {
.instance_size = sizeof(uefi_vars_isa_state),
.instance_init = uefi_vars_isa_init,
.class_init = uefi_vars_isa_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_ACPI_DEV_AML_IF },
+ { },
+ },
};
module_obj(TYPE_UEFI_VARS_ISA);
module_dep("hw-uefi-vars");
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 22/23] docs: add uefi variable service documentation
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (20 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 21/23] hw/uefi-vars-isa: add acpi device Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-11 9:23 ` [PATCH v3 23/23] hw/uefi: add MAINTAINERS entry Gerd Hoffmann
2025-02-13 9:41 ` [PATCH v3 00/23] hw/uefi: add uefi variable service Ard Biesheuvel
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
docs/devel/index-internals.rst | 1 +
docs/devel/uefi-vars.rst | 66 ++++++++++++++++++++++++++++++++++
hw/uefi/LIMITATIONS.md | 7 ++++
3 files changed, 74 insertions(+)
create mode 100644 docs/devel/uefi-vars.rst
create mode 100644 hw/uefi/LIMITATIONS.md
diff --git a/docs/devel/index-internals.rst b/docs/devel/index-internals.rst
index bca597c65895..7a0678cbdd3a 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 000000000000..3e7bd98b5208
--- /dev/null
+++ b/docs/devel/uefi-vars.rst
@@ -0,0 +1,66 @@
+==============
+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-isa`` 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 (isa)
+---------------------
+
+.. code::
+
+ qemu-system-x86_64 -device uefi-vars-isa,jsonfile=/path/to/vars.json
+
+usage on aarch64 (sysbus)
+-------------------------
+
+.. code::
+
+ qemu-system-aarch64 -M virt,x-uefi-vars=on
diff --git a/hw/uefi/LIMITATIONS.md b/hw/uefi/LIMITATIONS.md
new file mode 100644
index 000000000000..29308bd587aa
--- /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
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* [PATCH v3 23/23] hw/uefi: add MAINTAINERS entry
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (21 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 22/23] docs: add uefi variable service documentation Gerd Hoffmann
@ 2025-02-11 9:23 ` Gerd Hoffmann
2025-02-13 9:41 ` [PATCH v3 00/23] hw/uefi: add uefi variable service Ard Biesheuvel
23 siblings, 0 replies; 45+ messages in thread
From: Gerd Hoffmann @ 2025-02-11 9:23 UTC (permalink / raw)
To: qemu-devel
Cc: Gerd Hoffmann, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ard Biesheuvel
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
MAINTAINERS | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0091bd1a90f6..cd45771f5cd2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2799,6 +2799,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
--
2.48.1
^ permalink raw reply related [flat|nested] 45+ messages in thread
* Re: [PATCH v3 00/23] hw/uefi: add uefi variable service
2025-02-11 9:22 [PATCH v3 00/23] hw/uefi: add uefi variable service Gerd Hoffmann
` (22 preceding siblings ...)
2025-02-11 9:23 ` [PATCH v3 23/23] hw/uefi: add MAINTAINERS entry Gerd Hoffmann
@ 2025-02-13 9:41 ` Ard Biesheuvel
2025-02-13 10:11 ` Alexander Graf
23 siblings, 1 reply; 45+ messages in thread
From: Ard Biesheuvel @ 2025-02-13 9:41 UTC (permalink / raw)
To: Gerd Hoffmann
Cc: qemu-devel, graf, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé
On Tue, 11 Feb 2025 at 10:23, Gerd Hoffmann <kraxel@redhat.com> wrote:
>
> This patch adds a virtual device to qemu which the uefi firmware can use
> to store variables. This moves the UEFI variable management from
> privileged guest code (managing vars in pflash) to the host. Main
> advantage is that the need to have privilege separation in the guest
> goes away.
>
> On x86 privileged guest code runs in SMM. It's supported by kvm, but
> not liked much by various stakeholders in cloud space due to the
> complexity SMM emulation brings.
>
> On arm privileged guest code runs in el3 (aka secure world). This is
> not supported by kvm, which is unlikely to change anytime soon given
> that even el2 support (nested virt) is being worked on for years and is
> not yet in mainline.
>
The secure counterpart of this would never execute at EL3 on ARM, but
at secure EL1 (or potentially at secure EL2 on more recent CPUs). But
the general point that this is difficult to virtualize stands; I've
contemplated doing something similar to SMM emulation using non-secure
EL1 in a separate VM to provide an execution context that could those
the secure EL1 payload (using standalone MM) but I never found the
time to work on this.
> The design idea is to reuse the request serialization protocol edk2 uses
> for communication between SMM and non-SMM code, so large chunks of the
> edk2 variable driver stack can be used unmodified. Only the driver
> which traps into SMM mode must be replaced by a driver which talks to
> qemu instead.
>
I like this approach, but I will note that these protocols are not
standardized: it is basically an EDK2 implementation detail, but this
is fine, given that this targets firmware that is based on EDK2 (or
its derivatives).
Using a single shared communication buffer makes it feasible to
paravirtualize this even under confidential compute scenarios (where
the buffer needs special shared mapping semantics), and I think this
might be useful, even if in principle, the VMM is untrusted in such
scenarios. Paravirtualizing the individual variable services directly
creates a problem here, given that the firmware cannot share mappings
of arbitrary arguments passed via pointers.
For the record, I've already acked the OVMF counterpart of this, and
I've started working on adding support for this to my minimal EFI for
mach-virt [0], which is another scenario (i.e., minimal EFI compatible
firmware for micro VMs) where having this complexity in the VMM is
preferred.
[0] https://github.com/ardbiesheuvel/efilite
> A edk2 test branch can be found here (build with "-D QEMU_PV_VARS=TRUE").
> https://github.com/kraxel/edk2/commits/devel/secure-boot-external-vars
>
> The uefi-vars device re-implements the privileged edk2 protocols
> (i.e. the code running in SMM mode).
>
> v3 changes:
> - switch sysbus device variant to use the qemu platform bus.
> - misc minor changes.
> v2 changes:
> - fully implement authenticated variables.
> - various cleanups and fixes.
>
> enjoy & take care,
> Gerd
>
> Gerd Hoffmann (23):
> hw/uefi: add include/hw/uefi/var-service-api.h
> hw/uefi: add include/hw/uefi/var-service-edk2.h
> hw/uefi: add include/hw/uefi/var-service.h
> hw/uefi: add var-service-guid.c
> hw/uefi: add var-service-utils.c
> hw/uefi: add var-service-vars.c
> hw/uefi: add var-service-auth.c
> hw/uefi: add var-service-policy.c
> hw/uefi: add var-service-core.c
> hw/uefi: add var-service-pkcs7.c
> hw/uefi: add var-service-pkcs7-stub.c
> hw/uefi: add var-service-siglist.c
> hw/uefi: add var-service-json.c + qapi for NV vars.
> hw/uefi: add trace-events
> hw/uefi: add UEFI_VARS to Kconfig
> hw/uefi: add to meson
> hw/uefi: add uefi-vars-sysbus device
> hw/uefi-vars-sysbus: qemu platform bus support
> hw/uefi-vars-sysbus: allow for arm virt
> hw/uefi: add uefi-vars-isa device
> hw/uefi-vars-isa: add acpi device
> docs: add uefi variable service documentation
> hw/uefi: add MAINTAINERS entry
>
> include/hw/uefi/var-service-api.h | 43 ++
> include/hw/uefi/var-service-edk2.h | 227 +++++++++
> include/hw/uefi/var-service.h | 186 ++++++++
> hw/arm/virt.c | 2 +
> hw/core/sysbus-fdt.c | 24 +
> hw/uefi/var-service-auth.c | 361 ++++++++++++++
> hw/uefi/var-service-core.c | 237 ++++++++++
> hw/uefi/var-service-guid.c | 99 ++++
> hw/uefi/var-service-isa.c | 115 +++++
> hw/uefi/var-service-json.c | 242 ++++++++++
> hw/uefi/var-service-pkcs7-stub.c | 16 +
> hw/uefi/var-service-pkcs7.c | 436 +++++++++++++++++
> hw/uefi/var-service-policy.c | 370 +++++++++++++++
> hw/uefi/var-service-siglist.c | 212 +++++++++
> hw/uefi/var-service-sysbus.c | 90 ++++
> hw/uefi/var-service-utils.c | 241 ++++++++++
> hw/uefi/var-service-vars.c | 725 +++++++++++++++++++++++++++++
> MAINTAINERS | 6 +
> docs/devel/index-internals.rst | 1 +
> docs/devel/uefi-vars.rst | 66 +++
> hw/Kconfig | 1 +
> hw/meson.build | 1 +
> hw/uefi/Kconfig | 9 +
> hw/uefi/LIMITATIONS.md | 7 +
> hw/uefi/meson.build | 24 +
> hw/uefi/trace-events | 17 +
> meson.build | 1 +
> qapi/meson.build | 1 +
> qapi/qapi-schema.json | 1 +
> qapi/uefi.json | 45 ++
> 30 files changed, 3806 insertions(+)
> create mode 100644 include/hw/uefi/var-service-api.h
> create mode 100644 include/hw/uefi/var-service-edk2.h
> create mode 100644 include/hw/uefi/var-service.h
> create mode 100644 hw/uefi/var-service-auth.c
> create mode 100644 hw/uefi/var-service-core.c
> create mode 100644 hw/uefi/var-service-guid.c
> create mode 100644 hw/uefi/var-service-isa.c
> create mode 100644 hw/uefi/var-service-json.c
> create mode 100644 hw/uefi/var-service-pkcs7-stub.c
> create mode 100644 hw/uefi/var-service-pkcs7.c
> create mode 100644 hw/uefi/var-service-policy.c
> create mode 100644 hw/uefi/var-service-siglist.c
> create mode 100644 hw/uefi/var-service-sysbus.c
> create mode 100644 hw/uefi/var-service-utils.c
> create mode 100644 hw/uefi/var-service-vars.c
> create mode 100644 docs/devel/uefi-vars.rst
> create mode 100644 hw/uefi/Kconfig
> create mode 100644 hw/uefi/LIMITATIONS.md
> create mode 100644 hw/uefi/meson.build
> create mode 100644 hw/uefi/trace-events
> create mode 100644 qapi/uefi.json
>
> --
> 2.48.1
>
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 00/23] hw/uefi: add uefi variable service
2025-02-13 9:41 ` [PATCH v3 00/23] hw/uefi: add uefi variable service Ard Biesheuvel
@ 2025-02-13 10:11 ` Alexander Graf
2025-02-13 10:13 ` Ard Biesheuvel
0 siblings, 1 reply; 45+ messages in thread
From: Alexander Graf @ 2025-02-13 10:11 UTC (permalink / raw)
To: Ard Biesheuvel, Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ilias Apalodimas
On 13.02.25 10:41, Ard Biesheuvel wrote:
> On Tue, 11 Feb 2025 at 10:23, Gerd Hoffmann <kraxel@redhat.com> wrote:
>> This patch adds a virtual device to qemu which the uefi firmware can use
>> to store variables. This moves the UEFI variable management from
>> privileged guest code (managing vars in pflash) to the host. Main
>> advantage is that the need to have privilege separation in the guest
>> goes away.
>>
>> On x86 privileged guest code runs in SMM. It's supported by kvm, but
>> not liked much by various stakeholders in cloud space due to the
>> complexity SMM emulation brings.
>>
>> On arm privileged guest code runs in el3 (aka secure world). This is
>> not supported by kvm, which is unlikely to change anytime soon given
>> that even el2 support (nested virt) is being worked on for years and is
>> not yet in mainline.
>>
> The secure counterpart of this would never execute at EL3 on ARM, but
> at secure EL1 (or potentially at secure EL2 on more recent CPUs). But
> the general point that this is difficult to virtualize stands; I've
> contemplated doing something similar to SMM emulation using non-secure
> EL1 in a separate VM to provide an execution context that could those
> the secure EL1 payload (using standalone MM) but I never found the
> time to work on this.
Sounds very similar to what Ilias built a few years ago?
https://lore.kernel.org/all/20200511085205.GD73895@apalos.home/T/
Which reminds me: How similar is the protocol in this patch set to the
one implemented in U-Boot? No need to reinvent the wheel over and over
again.
>> The design idea is to reuse the request serialization protocol edk2 uses
>> for communication between SMM and non-SMM code, so large chunks of the
>> edk2 variable driver stack can be used unmodified. Only the driver
>> which traps into SMM mode must be replaced by a driver which talks to
>> qemu instead.
>>
> I like this approach, but I will note that these protocols are not
> standardized: it is basically an EDK2 implementation detail, but this
> is fine, given that this targets firmware that is based on EDK2 (or
> its derivatives).
>
> Using a single shared communication buffer makes it feasible to
> paravirtualize this even under confidential compute scenarios (where
> the buffer needs special shared mapping semantics), and I think this
> might be useful, even if in principle, the VMM is untrusted in such
> scenarios. Paravirtualizing the individual variable services directly
> creates a problem here, given that the firmware cannot share mappings
> of arbitrary arguments passed via pointers.
>
> For the record, I've already acked the OVMF counterpart of this, and
> I've started working on adding support for this to my minimal EFI for
> mach-virt [0], which is another scenario (i.e., minimal EFI compatible
> firmware for micro VMs) where having this complexity in the VMM is
> preferred.
Amazing! :)
Alex
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 00/23] hw/uefi: add uefi variable service
2025-02-13 10:11 ` Alexander Graf
@ 2025-02-13 10:13 ` Ard Biesheuvel
2025-02-20 12:43 ` Ilias Apalodimas
0 siblings, 1 reply; 45+ messages in thread
From: Ard Biesheuvel @ 2025-02-13 10:13 UTC (permalink / raw)
To: Alexander Graf
Cc: Gerd Hoffmann, qemu-devel, Eric Blake, Peter Maydell,
Paolo Bonzini, Daniel P. Berrangé, Thomas Huth,
Marc-André Lureau, qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé, Ilias Apalodimas
On Thu, 13 Feb 2025 at 11:11, Alexander Graf <graf@amazon.com> wrote:
>
>
> On 13.02.25 10:41, Ard Biesheuvel wrote:
> > On Tue, 11 Feb 2025 at 10:23, Gerd Hoffmann <kraxel@redhat.com> wrote:
> >> This patch adds a virtual device to qemu which the uefi firmware can use
> >> to store variables. This moves the UEFI variable management from
> >> privileged guest code (managing vars in pflash) to the host. Main
> >> advantage is that the need to have privilege separation in the guest
> >> goes away.
> >>
> >> On x86 privileged guest code runs in SMM. It's supported by kvm, but
> >> not liked much by various stakeholders in cloud space due to the
> >> complexity SMM emulation brings.
> >>
> >> On arm privileged guest code runs in el3 (aka secure world). This is
> >> not supported by kvm, which is unlikely to change anytime soon given
> >> that even el2 support (nested virt) is being worked on for years and is
> >> not yet in mainline.
> >>
> > The secure counterpart of this would never execute at EL3 on ARM, but
> > at secure EL1 (or potentially at secure EL2 on more recent CPUs). But
> > the general point that this is difficult to virtualize stands; I've
> > contemplated doing something similar to SMM emulation using non-secure
> > EL1 in a separate VM to provide an execution context that could those
> > the secure EL1 payload (using standalone MM) but I never found the
> > time to work on this.
>
>
> Sounds very similar to what Ilias built a few years ago?
>
> https://lore.kernel.org/all/20200511085205.GD73895@apalos.home/T/
>
> Which reminds me: How similar is the protocol in this patch set to the
> one implemented in U-Boot? No need to reinvent the wheel over and over
> again.
>
Identical afaik
^ permalink raw reply [flat|nested] 45+ messages in thread
* Re: [PATCH v3 00/23] hw/uefi: add uefi variable service
2025-02-13 10:13 ` Ard Biesheuvel
@ 2025-02-20 12:43 ` Ilias Apalodimas
0 siblings, 0 replies; 45+ messages in thread
From: Ilias Apalodimas @ 2025-02-20 12:43 UTC (permalink / raw)
To: Ard Biesheuvel, Alexander Graf, Gerd Hoffmann
Cc: qemu-devel, Eric Blake, Peter Maydell, Paolo Bonzini,
Daniel P. Berrangé, Thomas Huth, Marc-André Lureau,
qemu-arm, Michael Roth, Markus Armbruster,
Philippe Mathieu-Daudé
Hi Alex, Ard, Gerd,
Thanks for roping me in,
On Thu, 13 Feb 2025 at 12:13, Ard Biesheuvel <ardb@kernel.org> wrote:
>
> On Thu, 13 Feb 2025 at 11:11, Alexander Graf <graf@amazon.com> wrote:
> >
> >
> > On 13.02.25 10:41, Ard Biesheuvel wrote:
> > > On Tue, 11 Feb 2025 at 10:23, Gerd Hoffmann <kraxel@redhat.com> wrote:
> > >> This patch adds a virtual device to qemu which the uefi firmware can use
> > >> to store variables. This moves the UEFI variable management from
> > >> privileged guest code (managing vars in pflash) to the host. Main
> > >> advantage is that the need to have privilege separation in the guest
> > >> goes away.
> > >>
> > >> On x86 privileged guest code runs in SMM. It's supported by kvm, but
> > >> not liked much by various stakeholders in cloud space due to the
> > >> complexity SMM emulation brings.
> > >>
> > >> On arm privileged guest code runs in el3 (aka secure world). This is
> > >> not supported by kvm, which is unlikely to change anytime soon given
> > >> that even el2 support (nested virt) is being worked on for years and is
> > >> not yet in mainline.
> > >>
> > > The secure counterpart of this would never execute at EL3 on ARM, but
> > > at secure EL1 (or potentially at secure EL2 on more recent CPUs). But
> > > the general point that this is difficult to virtualize stands; I've
> > > contemplated doing something similar to SMM emulation using non-secure
> > > EL1 in a separate VM to provide an execution context that could those
> > > the secure EL1 payload (using standalone MM) but I never found the
> > > time to work on this.
> >
> >
> > Sounds very similar to what Ilias built a few years ago?
> >
> > https://lore.kernel.org/all/20200511085205.GD73895@apalos.home/T/
> >
> > Which reminds me: How similar is the protocol in this patch set to the
> > one implemented in U-Boot? No need to reinvent the wheel over and over
> > again.
> >
>
> Identical afaik
I don't know what I can do to help here but I'll explain what we have
in case we can figure something out .
The idea is very close indeed and in fact it works on QEMU with some
hacks for arm(7/8). [0]. Since QEMU doesn't have an RPMB emulation I
am providing one in software in U-Boot. That's obviously useless in
real use usecases, since the memory backend disappears when we leave
the firmware, but still useful for testing.
I also have a blog explaining the arm specific bits here [1].
The TL;DR is that we set up everything StMM needs inside OP-TEE and
execute it in S-EL1. For storage, we have a 'special' StMM driver that
sends requests to OP-TEE and uses its RPMB support to write sensitive
data on the device.
[0] https://git.linaro.org/people/ilias.apalodimas/efi_optee_variables.git/
[1] https://old.linaro.org/blog/protected-uefi-variables-with-u-boot/
Let me know if you need anything else
Cheers
/Ilias
^ permalink raw reply [flat|nested] 45+ messages in thread