mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ subdir('smbios')
|
||||
subdir('ssi')
|
||||
subdir('timer')
|
||||
subdir('tpm')
|
||||
subdir('uefi')
|
||||
subdir('ufs')
|
||||
subdir('usb')
|
||||
subdir('vfio')
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
config UEFI_VARS
|
||||
bool
|
||||
default y if X86_64 || AARCH64
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
}}
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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)
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user