Merge tag 'firmware-20250304-pull-request' of https://gitlab.com/kraxel/qemu into staging

- add uefi variable store support

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmfG9m4ACgkQTLbY7tPo
# cTgk8BAAn60ezSx2iet/JarkMugacOJ6C2UbVQho/Q3WCyrQ7K+F0NByczcfKLA6
# OZX84p93qxiK8KJ9tva41eOIYViyfsKf+wGUInNCbXbyGy7RZV3SjE5Yuk9BE9Ta
# 8f/5dDGyCELQWliy4atLUWl2dL0rQ76twLZewYo9n2A+LijIzjuP+kVJeccK8U7A
# qStio3rGZ0vul2OYhE3+veSXd2m2oU32Tce31MUoj9yCbTE1RZSKMXbwbUU/nul9
# RN3X0q4rvXKwbKMUdC+YI+oIzY/1nzrmy5zwwbJsAszsSKjAc2LZeoDqKdbOIynL
# B01dorpg5pVxQUqHz1t+YTfGyuZaYDM6WsaGoU5/9QLW7ZbI857EULq7ptE3DVAS
# YjHiBYqiiYYrCatV4UT1XjkRjX7W8lTdK2M+8Vh1E5b1pGpfPwuKE4YRGwMMK0Ac
# 5LD9HMxnXIDOT9A6+tGc6GYLfT7YToFA3pHn6WdLlGSowB7sYVZy0/xGe3ABjvzt
# WOl1WDWtHCpYIiROpEl+KkbRilwvbLF/IW7x0Ovfsjyh5ucBFu6ojxgRBcOee4Na
# oeBz5GfpeIoelhWl1aSYIUrFCvN2Q/9EafHRsfTzPoKlD3t/7oLNYtMYloiQpsks
# IPpD5OMMmWGaD2G76Nw24nS4+zUf4Gagg6+IAlYt6zjqnmxFWxY=
# =HnUt
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 04 Mar 2025 20:47:42 HKT
# gpg:                using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [full]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full]
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* tag 'firmware-20250304-pull-request' of https://gitlab.com/kraxel/qemu: (24 commits)
  docs: add uefi variable service documentation
  hw/uefi: add MAINTAINERS entry
  hw/uefi-vars-sysbus: allow for pc and q35
  hw/uefi-vars-sysbus: allow for arm virt
  hw/uefi-vars-sysbus: add x64 variant
  hw/uefi-vars-sysbus: qemu platform bus support
  hw/uefi: add uefi-vars-sysbus device
  hw/uefi: add to meson
  hw/uefi: add UEFI_VARS to Kconfig
  hw/uefi: add trace-events
  hw/uefi: add var-service-json.c + qapi for NV vars.
  hw/uefi: add var-service-siglist.c
  hw/uefi: add var-service-pkcs7-stub.c
  hw/uefi: add var-service-pkcs7.c
  hw/uefi: add var-service-core.c
  hw/uefi: add var-service-policy.c
  hw/uefi: add var-service-auth.c
  hw/uefi: add var-service-vars.c
  hw/uefi: add var-service-utils.c
  hw/uefi: add var-service-guid.c
  ...

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi
2025-03-05 21:53:36 +08:00
33 changed files with 3902 additions and 0 deletions
+6
View File
@@ -2820,6 +2820,12 @@ F: hw/misc/ivshmem-flat.c
F: include/hw/misc/ivshmem-flat.h
F: docs/system/devices/ivshmem-flat.rst
UEFI variable service
M: Gerd Hoffmann <kraxel@redhat.com>
S: Maintained
F: hw/uefi/
F: include/hw/uefi/
Subsystems
----------
Overall Audio backends
+1
View File
@@ -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
+68
View File
@@ -0,0 +1,68 @@
==============
UEFI variables
==============
Guest UEFI variable management
==============================
The traditional approach for UEFI Variable storage in qemu guests is
to work as close as possible to physical hardware. That means
providing pflash as storage and leaving the management of variables
and flash to the guest.
Secure boot support comes with the requirement that the UEFI variable
storage must be protected against direct access by the OS. All update
requests must pass the sanity checks. (Parts of) the firmware must
run with a higher privilege level than the OS so this can be enforced
by the firmware. On x86 this has been implemented using System
Management Mode (SMM) in qemu and kvm, which again is the same
approach taken by physical hardware. Only privileged code running in
SMM mode is allowed to access flash storage.
Communication with the firmware code running in SMM mode works by
serializing the requests to a shared buffer, then trapping into SMM
mode via SMI. The SMM code processes the request, stores the reply in
the same buffer and returns.
Host UEFI variable service
==========================
Instead of running the privileged code inside the guest we can run it
on the host. The serialization protocol can be reused. The
communication with the host uses a virtual device, which essentially
configures the shared buffer location and size, and traps to the host
to process the requests.
The ``uefi-vars`` device implements the UEFI virtual device. It comes
in ``uefi-vars-x86`` and ``uefi-vars-sysbus`` flavours. The device
reimplements the handlers needed, specifically
``EfiSmmVariableProtocol`` and ``VarCheckPolicyLibMmiHandler``. It
also consumes events (``EfiEndOfDxeEventGroup``,
``EfiEventReadyToBoot`` and ``EfiEventExitBootServices``).
The advantage of the approach is that we do not need a special
privilege level for the firmware to protect itself, i.e. it does not
depend on SMM emulation on x64, which allows the removal of a bunch of
complex code for SMM emulation from the linux kernel
(CONFIG_KVM_SMM=n). It also allows support for secure boot on arm
without implementing secure world (el3) emulation in kvm.
Of course there are also downsides. The added device increases the
attack surface of the host, and we are adding some code duplication
because we have to reimplement some edk2 functionality in qemu.
usage on x86_64
---------------
.. code::
qemu-system-x86_64 \
-device uefi-vars-x86,jsonfile=/path/to/vars.json
usage on aarch64
----------------
.. code::
qemu-system-aarch64 -M virt \
-device uefi-vars-sysbus,jsonfile=/path/to/vars.json
+1
View File
@@ -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
+2
View File
@@ -82,6 +82,7 @@
#include "hw/mem/pc-dimm.h"
#include "hw/mem/nvdimm.h"
#include "hw/acpi/generic_event_device.h"
#include "hw/uefi/var-service-api.h"
#include "hw/virtio/virtio-md-pci.h"
#include "hw/virtio/virtio-iommu.h"
#include "hw/char/pl011.h"
@@ -3162,6 +3163,7 @@ static void virt_machine_class_init(ObjectClass *oc, void *data)
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_AMD_XGBE);
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_VFIO_PLATFORM);
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_UEFI_VARS_SYSBUS);
#ifdef CONFIG_TPM
machine_class_allow_dynamic_sysbus_dev(mc, TYPE_TPM_TIS_SYSBUS);
#endif
+24
View File
@@ -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 */
};
+2
View File
@@ -65,6 +65,7 @@
#include "system/numa.h"
#include "hw/hyperv/vmbus-bridge.h"
#include "hw/mem/nvdimm.h"
#include "hw/uefi/var-service-api.h"
#include "hw/i386/acpi-build.h"
#include "target/i386/cpu.h"
@@ -468,6 +469,7 @@ static void pc_i440fx_machine_options(MachineClass *m)
m->no_parallel = !module_object_class_by_name(TYPE_ISA_PARALLEL);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64);
object_class_property_add_enum(oc, "x-south-bridge", "PCSouthBridgeOption",
&PCSouthBridgeOption_lookup,
+2
View File
@@ -58,6 +58,7 @@
#include "system/numa.h"
#include "hw/hyperv/vmbus-bridge.h"
#include "hw/mem/nvdimm.h"
#include "hw/uefi/var-service-api.h"
#include "hw/i386/acpi-build.h"
#include "target/i386/cpu.h"
@@ -355,6 +356,7 @@ static void pc_q35_machine_options(MachineClass *m)
machine_class_allow_dynamic_sysbus_dev(m, TYPE_INTEL_IOMMU_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_RAMFB_DEVICE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_VMBUS_BRIDGE);
machine_class_allow_dynamic_sysbus_dev(m, TYPE_UEFI_VARS_X64);
compat_props_add(m->compat_props,
pc_q35_compat_defaults, pc_q35_compat_defaults_len);
}
+1
View File
@@ -35,6 +35,7 @@ subdir('smbios')
subdir('ssi')
subdir('timer')
subdir('tpm')
subdir('uefi')
subdir('ufs')
subdir('usb')
subdir('vfio')
+3
View File
@@ -0,0 +1,3 @@
config UEFI_VARS
bool
default y if X86_64 || AARCH64
+7
View File
@@ -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
+31
View File
@@ -0,0 +1,31 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* pass hardware information to uefi
*
* see OvmfPkg/Library/HardwareInfoLib/ in edk2
*/
#include "qemu/osdep.h"
#include "hw/nvram/fw_cfg.h"
#include "hw/uefi/hardware-info.h"
static void *blob;
static uint64_t blobsize;
void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t infosize)
{
HARDWARE_INFO_HEADER hdr = {
.type.value = cpu_to_le64(type),
.size = cpu_to_le64(infosize),
};
blob = g_realloc(blob, blobsize + sizeof(hdr) + infosize);
memcpy(blob + blobsize, &hdr, sizeof(hdr));
blobsize += sizeof(hdr);
memcpy(blob + blobsize, info, infosize);
blobsize += infosize;
fw_cfg_modify_file(fw_cfg_find(), "etc/hardware-info", blob, blobsize);
}
+21
View File
@@ -0,0 +1,21 @@
system_ss.add(files('hardware-info.c'))
uefi_vars_ss = ss.source_set()
if (config_all_devices.has_key('CONFIG_UEFI_VARS'))
uefi_vars_ss.add(files('var-service-core.c',
'var-service-json.c',
'var-service-vars.c',
'var-service-auth.c',
'var-service-guid.c',
'var-service-utils.c',
'var-service-policy.c',
'var-service-sysbus.c'))
uefi_vars_ss.add(when: gnutls,
if_true: files('var-service-pkcs7.c'),
if_false: files('var-service-pkcs7-stub.c'))
uefi_vars_ss.add(files('var-service-siglist.c'))
endif
modules += { 'hw-uefi' : {
'vars' : uefi_vars_ss,
}}
+17
View File
@@ -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"
+361
View File
@@ -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);
}
+321
View File
@@ -0,0 +1,321 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* uefi vars device
*/
#include "qemu/osdep.h"
#include "qemu/crc32c.h"
#include "system/dma.h"
#include "migration/vmstate.h"
#include "hw/uefi/var-service.h"
#include "hw/uefi/var-service-api.h"
#include "hw/uefi/var-service-edk2.h"
#include "trace/trace-hw_uefi.h"
static int uefi_vars_pre_load(void *opaque)
{
uefi_vars_state *uv = opaque;
uefi_vars_clear_all(uv);
uefi_vars_policies_clear(uv);
g_free(uv->buffer);
return 0;
}
static int uefi_vars_post_load(void *opaque, int version_id)
{
uefi_vars_state *uv = opaque;
uefi_vars_update_storage(uv);
uv->buffer = g_malloc(uv->buf_size);
return 0;
}
const VMStateDescription vmstate_uefi_vars = {
.name = "uefi-vars",
.pre_load = uefi_vars_pre_load,
.post_load = uefi_vars_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT16(sts, uefi_vars_state),
VMSTATE_UINT32(buf_size, uefi_vars_state),
VMSTATE_UINT32(buf_addr_lo, uefi_vars_state),
VMSTATE_UINT32(buf_addr_hi, uefi_vars_state),
VMSTATE_UINT32(pio_xfer_offset, uefi_vars_state),
VMSTATE_VBUFFER_ALLOC_UINT32(pio_xfer_buffer, uefi_vars_state,
0, NULL, buf_size),
VMSTATE_BOOL(end_of_dxe, uefi_vars_state),
VMSTATE_BOOL(ready_to_boot, uefi_vars_state),
VMSTATE_BOOL(exit_boot_service, uefi_vars_state),
VMSTATE_BOOL(policy_locked, uefi_vars_state),
VMSTATE_UINT64(used_storage, uefi_vars_state),
VMSTATE_QTAILQ_V(variables, uefi_vars_state, 0,
vmstate_uefi_variable, uefi_variable, next),
VMSTATE_QTAILQ_V(var_policies, uefi_vars_state, 0,
vmstate_uefi_var_policy, uefi_var_policy, next),
VMSTATE_END_OF_LIST()
},
};
static uint32_t uefi_vars_cmd_mm(uefi_vars_state *uv, bool dma_mode)
{
hwaddr dma;
mm_header *mhdr;
uint64_t size;
uint32_t retval;
dma = uv->buf_addr_lo | ((hwaddr)uv->buf_addr_hi << 32);
mhdr = (mm_header *) uv->buffer;
if (!uv->buffer || uv->buf_size < sizeof(*mhdr)) {
return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
}
/* read header */
if (dma_mode) {
dma_memory_read(&address_space_memory, dma,
uv->buffer, sizeof(*mhdr),
MEMTXATTRS_UNSPECIFIED);
} else {
memcpy(uv->buffer, uv->pio_xfer_buffer, sizeof(*mhdr));
}
if (uadd64_overflow(sizeof(*mhdr), mhdr->length, &size)) {
return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
}
if (uv->buf_size < size) {
return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
}
/* read buffer (excl header) */
if (dma_mode) {
dma_memory_read(&address_space_memory, dma + sizeof(*mhdr),
uv->buffer + sizeof(*mhdr), mhdr->length,
MEMTXATTRS_UNSPECIFIED);
} else {
memcpy(uv->buffer + sizeof(*mhdr),
uv->pio_xfer_buffer + sizeof(*mhdr),
mhdr->length);
}
memset(uv->buffer + size, 0, uv->buf_size - size);
/* dispatch */
if (qemu_uuid_is_equal(&mhdr->guid, &EfiSmmVariableProtocolGuid)) {
retval = uefi_vars_mm_vars_proto(uv);
} else if (qemu_uuid_is_equal(&mhdr->guid, &VarCheckPolicyLibMmiHandlerGuid)) {
retval = uefi_vars_mm_check_policy_proto(uv);
} else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEndOfDxeEventGroupGuid)) {
trace_uefi_event("end-of-dxe");
uv->end_of_dxe = true;
retval = UEFI_VARS_STS_SUCCESS;
} else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventReadyToBootGuid)) {
trace_uefi_event("ready-to-boot");
uv->ready_to_boot = true;
retval = UEFI_VARS_STS_SUCCESS;
} else if (qemu_uuid_is_equal(&mhdr->guid, &EfiEventExitBootServicesGuid)) {
trace_uefi_event("exit-boot-service");
uv->exit_boot_service = true;
retval = UEFI_VARS_STS_SUCCESS;
} else {
retval = UEFI_VARS_STS_ERR_NOT_SUPPORTED;
}
/* write buffer */
if (dma_mode) {
dma_memory_write(&address_space_memory, dma,
uv->buffer, sizeof(*mhdr) + mhdr->length,
MEMTXATTRS_UNSPECIFIED);
} else {
memcpy(uv->pio_xfer_buffer + sizeof(*mhdr),
uv->buffer + sizeof(*mhdr),
sizeof(*mhdr) + mhdr->length);
}
return retval;
}
static void uefi_vars_soft_reset(uefi_vars_state *uv)
{
g_free(uv->buffer);
uv->buffer = NULL;
uv->buf_size = 0;
uv->buf_addr_lo = 0;
uv->buf_addr_hi = 0;
}
void uefi_vars_hard_reset(uefi_vars_state *uv)
{
trace_uefi_hard_reset();
uefi_vars_soft_reset(uv);
uv->end_of_dxe = false;
uv->ready_to_boot = false;
uv->exit_boot_service = false;
uv->policy_locked = false;
uefi_vars_clear_volatile(uv);
uefi_vars_policies_clear(uv);
uefi_vars_auth_init(uv);
}
static uint32_t uefi_vars_cmd(uefi_vars_state *uv, uint32_t cmd)
{
switch (cmd) {
case UEFI_VARS_CMD_RESET:
uefi_vars_soft_reset(uv);
return UEFI_VARS_STS_SUCCESS;
case UEFI_VARS_CMD_DMA_MM:
return uefi_vars_cmd_mm(uv, true);
case UEFI_VARS_CMD_PIO_MM:
return uefi_vars_cmd_mm(uv, false);
case UEFI_VARS_CMD_PIO_ZERO_OFFSET:
uv->pio_xfer_offset = 0;
return UEFI_VARS_STS_SUCCESS;
default:
return UEFI_VARS_STS_ERR_NOT_SUPPORTED;
}
}
static uint64_t uefi_vars_read(void *opaque, hwaddr addr, unsigned size)
{
uefi_vars_state *uv = opaque;
uint64_t retval = -1;
void *xfer_ptr;
trace_uefi_reg_read(addr, size);
switch (addr) {
case UEFI_VARS_REG_MAGIC:
retval = UEFI_VARS_MAGIC_VALUE;
break;
case UEFI_VARS_REG_CMD_STS:
retval = uv->sts;
break;
case UEFI_VARS_REG_BUFFER_SIZE:
retval = uv->buf_size;
break;
case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO:
retval = uv->buf_addr_lo;
break;
case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI:
retval = uv->buf_addr_hi;
break;
case UEFI_VARS_REG_PIO_BUFFER_TRANSFER:
if (uv->pio_xfer_offset + size > uv->buf_size) {
retval = 0;
break;
}
xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset;
switch (size) {
case 1:
retval = *(uint8_t *)xfer_ptr;
break;
case 2:
retval = *(uint16_t *)xfer_ptr;
break;
case 4:
retval = *(uint32_t *)xfer_ptr;
break;
case 8:
retval = *(uint64_t *)xfer_ptr;
break;
}
uv->pio_xfer_offset += size;
break;
case UEFI_VARS_REG_PIO_BUFFER_CRC32C:
retval = crc32c(0xffffffff, uv->pio_xfer_buffer, uv->pio_xfer_offset);
break;
case UEFI_VARS_REG_FLAGS:
retval = 0;
if (uv->use_pio) {
retval |= UEFI_VARS_FLAG_USE_PIO;
}
}
return retval;
}
static void uefi_vars_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
uefi_vars_state *uv = opaque;
void *xfer_ptr;
trace_uefi_reg_write(addr, val, size);
switch (addr) {
case UEFI_VARS_REG_CMD_STS:
uv->sts = uefi_vars_cmd(uv, val);
break;
case UEFI_VARS_REG_BUFFER_SIZE:
if (val > MAX_BUFFER_SIZE) {
val = MAX_BUFFER_SIZE;
}
uv->buf_size = val;
g_free(uv->buffer);
g_free(uv->pio_xfer_buffer);
uv->buffer = g_malloc(uv->buf_size);
uv->pio_xfer_buffer = g_malloc(uv->buf_size);
break;
case UEFI_VARS_REG_DMA_BUFFER_ADDR_LO:
uv->buf_addr_lo = val;
break;
case UEFI_VARS_REG_DMA_BUFFER_ADDR_HI:
uv->buf_addr_hi = val;
break;
case UEFI_VARS_REG_PIO_BUFFER_TRANSFER:
if (uv->pio_xfer_offset + size > uv->buf_size) {
break;
}
xfer_ptr = uv->pio_xfer_buffer + uv->pio_xfer_offset;
switch (size) {
case 1:
*(uint8_t *)xfer_ptr = val;
break;
case 2:
*(uint16_t *)xfer_ptr = val;
break;
case 4:
*(uint32_t *)xfer_ptr = val;
break;
case 8:
*(uint64_t *)xfer_ptr = val;
break;
}
uv->pio_xfer_offset += size;
break;
case UEFI_VARS_REG_PIO_BUFFER_CRC32C:
case UEFI_VARS_REG_FLAGS:
default:
break;
}
}
static const MemoryRegionOps uefi_vars_ops = {
.read = uefi_vars_read,
.write = uefi_vars_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.impl = {
.min_access_size = 2,
.max_access_size = 4,
},
};
void uefi_vars_init(Object *obj, uefi_vars_state *uv)
{
QTAILQ_INIT(&uv->variables);
QTAILQ_INIT(&uv->var_policies);
uv->jsonfd = -1;
memory_region_init_io(&uv->mr, obj, &uefi_vars_ops, uv,
"uefi-vars", UEFI_VARS_REGS_SIZE);
}
void uefi_vars_realize(uefi_vars_state *uv, Error **errp)
{
uefi_vars_json_init(uv, errp);
uefi_vars_json_load(uv, errp);
}
+99
View File
@@ -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)
};
+243
View File
@@ -0,0 +1,243 @@
/*
* SPDX-License-Identifier: GPL-2.0-or-later
*
* uefi vars device - serialize non-volatile varstore from/to json,
* using qapi
*
* tools which can read/write these json files:
* - https://gitlab.com/kraxel/virt-firmware
* - https://github.com/awslabs/python-uefivars
*/
#include "qemu/osdep.h"
#include "qemu/cutils.h"
#include "qemu/error-report.h"
#include "system/dma.h"
#include "hw/uefi/var-service.h"
#include "qobject/qobject.h"
#include "qobject/qjson.h"
#include "qapi/dealloc-visitor.h"
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
#include "qapi/qapi-types-uefi.h"
#include "qapi/qapi-visit-uefi.h"
static char *generate_hexstr(void *data, size_t len)
{
static const char hex[] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
};
uint8_t *src = data;
char *dest;
size_t i;
dest = g_malloc(len * 2 + 1);
for (i = 0; i < len * 2;) {
dest[i++] = hex[*src >> 4];
dest[i++] = hex[*src & 15];
src++;
}
dest[i++] = 0;
return dest;
}
static UefiVarStore *uefi_vars_to_qapi(uefi_vars_state *uv)
{
UefiVarStore *vs;
UefiVariableList **tail;
UefiVariable *v;
QemuUUID be;
uefi_variable *var;
vs = g_new0(UefiVarStore, 1);
vs->version = 2;
tail = &vs->variables;
QTAILQ_FOREACH(var, &uv->variables, next) {
if (!(var->attributes & EFI_VARIABLE_NON_VOLATILE)) {
continue;
}
v = g_new0(UefiVariable, 1);
be = qemu_uuid_bswap(var->guid);
v->guid = qemu_uuid_unparse_strdup(&be);
v->name = uefi_ucs2_to_ascii(var->name, var->name_size);
v->attr = var->attributes;
v->data = generate_hexstr(var->data, var->data_size);
if (var->attributes &
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
v->time = generate_hexstr(&var->time, sizeof(var->time));
if (var->digest && var->digest_size) {
v->digest = generate_hexstr(var->digest, var->digest_size);
}
}
QAPI_LIST_APPEND(tail, v);
}
return vs;
}
static unsigned parse_hexchar(char c)
{
switch (c) {
case '0' ... '9': return c - '0';
case 'a' ... 'f': return c - 'a' + 0xa;
case 'A' ... 'F': return c - 'A' + 0xA;
default: return 0;
}
}
static void parse_hexstr(void *dest, char *src, int len)
{
uint8_t *data = dest;
size_t i;
for (i = 0; i < len; i += 2) {
*(data++) =
parse_hexchar(src[i]) << 4 |
parse_hexchar(src[i + 1]);
}
}
static void uefi_vars_from_qapi(uefi_vars_state *uv, UefiVarStore *vs)
{
UefiVariableList *item;
UefiVariable *v;
QemuUUID be;
uefi_variable *var;
uint8_t *data;
size_t i, len;
for (item = vs->variables; item != NULL; item = item->next) {
v = item->value;
var = g_new0(uefi_variable, 1);
var->attributes = v->attr;
qemu_uuid_parse(v->guid, &be);
var->guid = qemu_uuid_bswap(be);
len = strlen(v->name);
var->name_size = len * 2 + 2;
var->name = g_malloc(var->name_size);
for (i = 0; i <= len; i++) {
var->name[i] = v->name[i];
}
len = strlen(v->data);
var->data_size = len / 2;
var->data = data = g_malloc(var->data_size);
parse_hexstr(var->data, v->data, len);
if (v->time && strlen(v->time) == 32) {
parse_hexstr(&var->time, v->time, 32);
}
if (v->digest) {
len = strlen(v->digest);
var->digest_size = len / 2;
var->digest = g_malloc(var->digest_size);
parse_hexstr(var->digest, v->digest, len);
}
QTAILQ_INSERT_TAIL(&uv->variables, var, next);
}
}
static GString *uefi_vars_to_json(uefi_vars_state *uv)
{
UefiVarStore *vs = uefi_vars_to_qapi(uv);
QObject *qobj = NULL;
Visitor *v;
GString *gstr;
v = qobject_output_visitor_new(&qobj);
if (visit_type_UefiVarStore(v, NULL, &vs, NULL)) {
visit_complete(v, &qobj);
}
visit_free(v);
qapi_free_UefiVarStore(vs);
gstr = qobject_to_json_pretty(qobj, true);
qobject_unref(qobj);
return gstr;
}
void uefi_vars_json_init(uefi_vars_state *uv, Error **errp)
{
if (uv->jsonfile) {
uv->jsonfd = qemu_create(uv->jsonfile, O_RDWR, 0666, errp);
}
}
void uefi_vars_json_save(uefi_vars_state *uv)
{
GString *gstr;
int rc;
if (uv->jsonfd == -1) {
return;
}
gstr = uefi_vars_to_json(uv);
lseek(uv->jsonfd, 0, SEEK_SET);
rc = ftruncate(uv->jsonfd, 0);
if (rc != 0) {
warn_report("%s: ftruncate error", __func__);
}
rc = write(uv->jsonfd, gstr->str, gstr->len);
if (rc != gstr->len) {
warn_report("%s: write error", __func__);
}
fsync(uv->jsonfd);
g_string_free(gstr, true);
}
void uefi_vars_json_load(uefi_vars_state *uv, Error **errp)
{
UefiVarStore *vs;
QObject *qobj;
Visitor *v;
char *str;
size_t len;
int rc;
if (uv->jsonfd == -1) {
return;
}
len = lseek(uv->jsonfd, 0, SEEK_END);
if (len == 0) {
return;
}
str = g_malloc(len + 1);
lseek(uv->jsonfd, 0, SEEK_SET);
rc = read(uv->jsonfd, str, len);
if (rc != len) {
warn_report("%s: read error", __func__);
}
str[len] = 0;
qobj = qobject_from_json(str, errp);
v = qobject_input_visitor_new(qobj);
visit_type_UefiVarStore(v, NULL, &vs, errp);
visit_free(v);
if (!(*errp)) {
uefi_vars_from_qapi(uv, vs);
uefi_vars_update_storage(uv);
}
qapi_free_UefiVarStore(vs);
qobject_unref(qobj);
g_free(str);
}
+16
View File
@@ -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;
}
+436
View File
@@ -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;
}

Some files were not shown because too many files have changed in this diff Show More