mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-02 07:13:54 -06:00
- 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----- 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:
commit
50aa3d0984
33 changed files with 3902 additions and 0 deletions
|
@ -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
|
||||
|
|
68
docs/devel/uefi-vars.rst
Normal file
68
docs/devel/uefi-vars.rst
Normal 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
|
|
@ -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')
|
||||
|
|
3
hw/uefi/Kconfig
Normal file
3
hw/uefi/Kconfig
Normal file
|
@ -0,0 +1,3 @@
|
|||
config UEFI_VARS
|
||||
bool
|
||||
default y if X86_64 || AARCH64
|
7
hw/uefi/LIMITATIONS.md
Normal file
7
hw/uefi/LIMITATIONS.md
Normal 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
hw/uefi/hardware-info.c
Normal file
31
hw/uefi/hardware-info.c
Normal 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
hw/uefi/meson.build
Normal file
21
hw/uefi/meson.build
Normal 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
hw/uefi/trace-events
Normal file
17
hw/uefi/trace-events
Normal 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
hw/uefi/var-service-auth.c
Normal file
361
hw/uefi/var-service-auth.c
Normal 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
hw/uefi/var-service-core.c
Normal file
321
hw/uefi/var-service-core.c
Normal 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
hw/uefi/var-service-guid.c
Normal file
99
hw/uefi/var-service-guid.c
Normal 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
hw/uefi/var-service-json.c
Normal file
243
hw/uefi/var-service-json.c
Normal 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
hw/uefi/var-service-pkcs7-stub.c
Normal file
16
hw/uefi/var-service-pkcs7-stub.c
Normal 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
hw/uefi/var-service-pkcs7.c
Normal file
436
hw/uefi/var-service-pkcs7.c
Normal 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;
|
||||
}
|
370
hw/uefi/var-service-policy.c
Normal file
370
hw/uefi/var-service-policy.c
Normal file
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi vars device - VarCheckPolicyLibMmiHandler implementation
|
||||
*
|
||||
* variable policy specs:
|
||||
* https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "system/dma.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
#include "hw/uefi/var-service.h"
|
||||
#include "hw/uefi/var-service-api.h"
|
||||
#include "hw/uefi/var-service-edk2.h"
|
||||
|
||||
#include "trace/trace-hw_uefi.h"
|
||||
|
||||
static void calc_policy(uefi_var_policy *pol);
|
||||
|
||||
static int uefi_var_policy_post_load(void *opaque, int version_id)
|
||||
{
|
||||
uefi_var_policy *pol = opaque;
|
||||
|
||||
calc_policy(pol);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const VMStateDescription vmstate_uefi_var_policy = {
|
||||
.name = "uefi-var-policy",
|
||||
.post_load = uefi_var_policy_post_load,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(entry_size, uefi_var_policy),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(entry, uefi_var_policy,
|
||||
0, NULL, entry_size),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
static void print_policy_entry(variable_policy_entry *pe)
|
||||
{
|
||||
uint16_t *name = (void *)pe + pe->offset_to_name;
|
||||
|
||||
fprintf(stderr, "%s:\n", __func__);
|
||||
|
||||
fprintf(stderr, " name ´");
|
||||
while (*name) {
|
||||
fprintf(stderr, "%c", *name);
|
||||
name++;
|
||||
}
|
||||
fprintf(stderr, "', version=%d.%d, size=%d\n",
|
||||
pe->version >> 16, pe->version & 0xffff, pe->size);
|
||||
|
||||
if (pe->min_size) {
|
||||
fprintf(stderr, " size min=%d\n", pe->min_size);
|
||||
}
|
||||
if (pe->max_size != UINT32_MAX) {
|
||||
fprintf(stderr, " size max=%u\n", pe->max_size);
|
||||
}
|
||||
if (pe->attributes_must_have) {
|
||||
fprintf(stderr, " attr must=0x%x\n", pe->attributes_must_have);
|
||||
}
|
||||
if (pe->attributes_cant_have) {
|
||||
fprintf(stderr, " attr cant=0x%x\n", pe->attributes_cant_have);
|
||||
}
|
||||
if (pe->lock_policy_type) {
|
||||
fprintf(stderr, " lock policy type %d\n", pe->lock_policy_type);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean wildcard_str_equal(uefi_var_policy *pol,
|
||||
uefi_variable *var)
|
||||
{
|
||||
return uefi_str_equal_ex(pol->name, pol->name_size,
|
||||
var->name, var->name_size,
|
||||
true);
|
||||
}
|
||||
|
||||
static uefi_var_policy *find_policy(uefi_vars_state *uv, QemuUUID guid,
|
||||
uint16_t *name, uint64_t name_size)
|
||||
{
|
||||
uefi_var_policy *pol;
|
||||
|
||||
QTAILQ_FOREACH(pol, &uv->var_policies, next) {
|
||||
if (!qemu_uuid_is_equal(&pol->entry->namespace, &guid)) {
|
||||
continue;
|
||||
}
|
||||
if (!uefi_str_equal(pol->name, pol->name_size,
|
||||
name, name_size)) {
|
||||
continue;
|
||||
}
|
||||
return pol;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uefi_var_policy *wildcard_find_policy(uefi_vars_state *uv,
|
||||
uefi_variable *var)
|
||||
{
|
||||
uefi_var_policy *pol;
|
||||
|
||||
QTAILQ_FOREACH(pol, &uv->var_policies, next) {
|
||||
if (!qemu_uuid_is_equal(&pol->entry->namespace, &var->guid)) {
|
||||
continue;
|
||||
}
|
||||
if (!wildcard_str_equal(pol, var)) {
|
||||
continue;
|
||||
}
|
||||
return pol;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void calc_policy(uefi_var_policy *pol)
|
||||
{
|
||||
variable_policy_entry *pe = pol->entry;
|
||||
unsigned int i;
|
||||
|
||||
pol->name = (void *)pol->entry + pe->offset_to_name;
|
||||
pol->name_size = pe->size - pe->offset_to_name;
|
||||
|
||||
for (i = 0; i < pol->name_size / 2; i++) {
|
||||
if (pol->name[i] == '#') {
|
||||
pol->hashmarks++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv,
|
||||
variable_policy_entry *pe)
|
||||
{
|
||||
uefi_var_policy *pol, *p;
|
||||
|
||||
pol = g_new0(uefi_var_policy, 1);
|
||||
pol->entry = g_malloc(pe->size);
|
||||
memcpy(pol->entry, pe, pe->size);
|
||||
pol->entry_size = pe->size;
|
||||
|
||||
calc_policy(pol);
|
||||
|
||||
/* keep list sorted by priority, add to tail of priority group */
|
||||
QTAILQ_FOREACH(p, &uv->var_policies, next) {
|
||||
if ((p->hashmarks > pol->hashmarks) ||
|
||||
(!p->name_size && pol->name_size)) {
|
||||
QTAILQ_INSERT_BEFORE(p, pol, next);
|
||||
return pol;
|
||||
}
|
||||
}
|
||||
|
||||
QTAILQ_INSERT_TAIL(&uv->var_policies, pol, next);
|
||||
return pol;
|
||||
}
|
||||
|
||||
efi_status uefi_vars_policy_check(uefi_vars_state *uv,
|
||||
uefi_variable *var,
|
||||
gboolean is_newvar)
|
||||
{
|
||||
uefi_var_policy *pol;
|
||||
variable_policy_entry *pe;
|
||||
variable_lock_on_var_state *lvarstate;
|
||||
uint16_t *lvarname;
|
||||
size_t lvarnamesize;
|
||||
uefi_variable *lvar;
|
||||
|
||||
if (!uv->end_of_dxe) {
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
pol = wildcard_find_policy(uv, var);
|
||||
if (!pol) {
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
pe = pol->entry;
|
||||
|
||||
uefi_trace_variable(__func__, var->guid, var->name, var->name_size);
|
||||
print_policy_entry(pe);
|
||||
|
||||
if ((var->attributes & pe->attributes_must_have) != pe->attributes_must_have) {
|
||||
trace_uefi_vars_policy_deny("must-have-attr");
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
if ((var->attributes & pe->attributes_cant_have) != 0) {
|
||||
trace_uefi_vars_policy_deny("cant-have-attr");
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (var->data_size < pe->min_size) {
|
||||
trace_uefi_vars_policy_deny("min-size");
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
if (var->data_size > pe->max_size) {
|
||||
trace_uefi_vars_policy_deny("max-size");
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
switch (pe->lock_policy_type) {
|
||||
case VARIABLE_POLICY_TYPE_NO_LOCK:
|
||||
break;
|
||||
|
||||
case VARIABLE_POLICY_TYPE_LOCK_NOW:
|
||||
trace_uefi_vars_policy_deny("lock-now");
|
||||
return EFI_WRITE_PROTECTED;
|
||||
|
||||
case VARIABLE_POLICY_TYPE_LOCK_ON_CREATE:
|
||||
if (!is_newvar) {
|
||||
trace_uefi_vars_policy_deny("lock-on-create");
|
||||
return EFI_WRITE_PROTECTED;
|
||||
}
|
||||
break;
|
||||
|
||||
case VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE:
|
||||
lvarstate = (void *)pol->entry + sizeof(*pe);
|
||||
lvarname = (void *)pol->entry + sizeof(*pe) + sizeof(*lvarstate);
|
||||
lvarnamesize = pe->offset_to_name - sizeof(*pe) - sizeof(*lvarstate);
|
||||
|
||||
uefi_trace_variable(__func__, lvarstate->namespace,
|
||||
lvarname, lvarnamesize);
|
||||
lvar = uefi_vars_find_variable(uv, lvarstate->namespace,
|
||||
lvarname, lvarnamesize);
|
||||
if (lvar && lvar->data_size == 1) {
|
||||
uint8_t *value = lvar->data;
|
||||
if (lvarstate->value == *value) {
|
||||
return EFI_WRITE_PROTECTED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
void uefi_vars_policies_clear(uefi_vars_state *uv)
|
||||
{
|
||||
uefi_var_policy *pol;
|
||||
|
||||
while (!QTAILQ_EMPTY(&uv->var_policies)) {
|
||||
pol = QTAILQ_FIRST(&uv->var_policies);
|
||||
QTAILQ_REMOVE(&uv->var_policies, pol, next);
|
||||
g_free(pol->entry);
|
||||
g_free(pol);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t uefi_vars_mm_policy_error(mm_header *mhdr,
|
||||
mm_check_policy *mchk,
|
||||
uint64_t status)
|
||||
{
|
||||
mchk->result = status;
|
||||
return sizeof(*mchk);
|
||||
}
|
||||
|
||||
static uint32_t uefi_vars_mm_check_policy_is_enabled(uefi_vars_state *uv,
|
||||
mm_header *mhdr,
|
||||
mm_check_policy *mchk,
|
||||
void *func)
|
||||
{
|
||||
mm_check_policy_is_enabled *mpar = func;
|
||||
size_t length;
|
||||
|
||||
length = sizeof(*mchk) + sizeof(*mpar);
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
mpar->state = TRUE;
|
||||
mchk->result = EFI_SUCCESS;
|
||||
return sizeof(*mchk);
|
||||
}
|
||||
|
||||
static uint32_t uefi_vars_mm_check_policy_register(uefi_vars_state *uv,
|
||||
mm_header *mhdr,
|
||||
mm_check_policy *mchk,
|
||||
void *func)
|
||||
{
|
||||
variable_policy_entry *pe = func;
|
||||
uefi_var_policy *pol;
|
||||
uint64_t length;
|
||||
|
||||
if (uadd64_overflow(sizeof(*mchk), pe->size, &length)) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (pe->size < sizeof(*pe)) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (pe->offset_to_name < sizeof(*pe)) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (pe->lock_policy_type == VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE &&
|
||||
pe->offset_to_name < sizeof(*pe) + sizeof(variable_lock_on_var_state)) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
/* check space for minimum string length */
|
||||
if (pe->size < (size_t)pe->offset_to_name) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (!uefi_str_is_valid((void *)pe + pe->offset_to_name,
|
||||
pe->size - pe->offset_to_name,
|
||||
false)) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
pol = find_policy(uv, pe->namespace,
|
||||
(void *)pe + pe->offset_to_name,
|
||||
pe->size - pe->offset_to_name);
|
||||
if (pol) {
|
||||
return uefi_vars_mm_policy_error(mhdr, mchk, EFI_ALREADY_STARTED);
|
||||
}
|
||||
|
||||
uefi_vars_add_policy(uv, pe);
|
||||
|
||||
mchk->result = EFI_SUCCESS;
|
||||
return sizeof(*mchk);
|
||||
}
|
||||
|
||||
uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv)
|
||||
{
|
||||
static const char *fnames[] = {
|
||||
"zero",
|
||||
"disable",
|
||||
"is-enabled",
|
||||
"register",
|
||||
"dump",
|
||||
"lock",
|
||||
};
|
||||
const char *fname;
|
||||
mm_header *mhdr = (mm_header *) uv->buffer;
|
||||
mm_check_policy *mchk = (mm_check_policy *) (uv->buffer + sizeof(*mhdr));
|
||||
void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mchk));
|
||||
|
||||
if (mhdr->length < sizeof(*mchk)) {
|
||||
return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
fname = mchk->command < ARRAY_SIZE(fnames)
|
||||
? fnames[mchk->command]
|
||||
: "unknown";
|
||||
trace_uefi_vars_policy_cmd(fname);
|
||||
|
||||
switch (mchk->command) {
|
||||
case VAR_CHECK_POLICY_COMMAND_DISABLE:
|
||||
mchk->result = EFI_UNSUPPORTED;
|
||||
break;
|
||||
case VAR_CHECK_POLICY_COMMAND_IS_ENABLED:
|
||||
uefi_vars_mm_check_policy_is_enabled(uv, mhdr, mchk, func);
|
||||
break;
|
||||
case VAR_CHECK_POLICY_COMMAND_REGISTER:
|
||||
if (uv->policy_locked) {
|
||||
mchk->result = EFI_WRITE_PROTECTED;
|
||||
} else {
|
||||
uefi_vars_mm_check_policy_register(uv, mhdr, mchk, func);
|
||||
}
|
||||
break;
|
||||
case VAR_CHECK_POLICY_COMMAND_LOCK:
|
||||
uv->policy_locked = true;
|
||||
mchk->result = EFI_SUCCESS;
|
||||
break;
|
||||
default:
|
||||
mchk->result = EFI_UNSUPPORTED;
|
||||
break;
|
||||
}
|
||||
|
||||
uefi_trace_status(__func__, mchk->result);
|
||||
return UEFI_VARS_STS_SUCCESS;
|
||||
}
|
212
hw/uefi/var-service-siglist.c
Normal file
212
hw/uefi/var-service-siglist.c
Normal file
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi vars device - parse and generate efi signature databases
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "system/dma.h"
|
||||
|
||||
#include "hw/uefi/var-service.h"
|
||||
|
||||
/*
|
||||
* Add x509 certificate to list (with duplicate check).
|
||||
*/
|
||||
static void uefi_vars_siglist_add_x509(uefi_vars_siglist *siglist,
|
||||
QemuUUID *owner,
|
||||
void *data, uint64_t size)
|
||||
{
|
||||
uefi_vars_cert *c;
|
||||
|
||||
QTAILQ_FOREACH(c, &siglist->x509, next) {
|
||||
if (c->size != size) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(c->data, data, size) != 0) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
c = g_malloc(sizeof(*c) + size);
|
||||
c->owner = *owner;
|
||||
c->size = size;
|
||||
memcpy(c->data, data, size);
|
||||
QTAILQ_INSERT_TAIL(&siglist->x509, c, next);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add sha256 hash to list (with duplicate check).
|
||||
*/
|
||||
static void uefi_vars_siglist_add_sha256(uefi_vars_siglist *siglist,
|
||||
QemuUUID *owner,
|
||||
void *data)
|
||||
{
|
||||
uefi_vars_hash *h;
|
||||
|
||||
QTAILQ_FOREACH(h, &siglist->sha256, next) {
|
||||
if (memcmp(h->data, data, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
h = g_malloc(sizeof(*h) + 32);
|
||||
h->owner = *owner;
|
||||
memcpy(h->data, data, 32);
|
||||
QTAILQ_INSERT_TAIL(&siglist->sha256, h, next);
|
||||
}
|
||||
|
||||
void uefi_vars_siglist_init(uefi_vars_siglist *siglist)
|
||||
{
|
||||
memset(siglist, 0, sizeof(*siglist));
|
||||
QTAILQ_INIT(&siglist->x509);
|
||||
QTAILQ_INIT(&siglist->sha256);
|
||||
}
|
||||
|
||||
void uefi_vars_siglist_free(uefi_vars_siglist *siglist)
|
||||
{
|
||||
uefi_vars_cert *c, *cs;
|
||||
uefi_vars_hash *h, *hs;
|
||||
|
||||
QTAILQ_FOREACH_SAFE(c, &siglist->x509, next, cs) {
|
||||
QTAILQ_REMOVE(&siglist->x509, c, next);
|
||||
g_free(c);
|
||||
}
|
||||
QTAILQ_FOREACH_SAFE(h, &siglist->sha256, next, hs) {
|
||||
QTAILQ_REMOVE(&siglist->sha256, h, next);
|
||||
g_free(h);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse UEFI signature list.
|
||||
*/
|
||||
void uefi_vars_siglist_parse(uefi_vars_siglist *siglist,
|
||||
void *data, uint64_t size)
|
||||
{
|
||||
efi_siglist *efilist;
|
||||
uint64_t start;
|
||||
|
||||
while (size) {
|
||||
if (size < sizeof(*efilist)) {
|
||||
break;
|
||||
}
|
||||
efilist = data;
|
||||
if (size < efilist->siglist_size) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (uadd64_overflow(sizeof(*efilist), efilist->header_size, &start)) {
|
||||
break;
|
||||
}
|
||||
if (efilist->sig_size <= sizeof(QemuUUID)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertX509Guid)) {
|
||||
if (start + efilist->sig_size != efilist->siglist_size) {
|
||||
break;
|
||||
}
|
||||
uefi_vars_siglist_add_x509(siglist,
|
||||
(QemuUUID *)(data + start),
|
||||
data + start + sizeof(QemuUUID),
|
||||
efilist->sig_size - sizeof(QemuUUID));
|
||||
|
||||
} else if (qemu_uuid_is_equal(&efilist->guid_type, &EfiCertSha256Guid)) {
|
||||
if (efilist->sig_size != sizeof(QemuUUID) + 32) {
|
||||
break;
|
||||
}
|
||||
if (start + efilist->sig_size > efilist->siglist_size) {
|
||||
break;
|
||||
}
|
||||
while (start <= efilist->siglist_size - efilist->sig_size) {
|
||||
uefi_vars_siglist_add_sha256(siglist,
|
||||
(QemuUUID *)(data + start),
|
||||
data + start + sizeof(QemuUUID));
|
||||
start += efilist->sig_size;
|
||||
}
|
||||
|
||||
} else {
|
||||
QemuUUID be = qemu_uuid_bswap(efilist->guid_type);
|
||||
char *str_uuid = qemu_uuid_unparse_strdup(&be);
|
||||
warn_report("%s: unknown type (%s)", __func__, str_uuid);
|
||||
g_free(str_uuid);
|
||||
}
|
||||
|
||||
data += efilist->siglist_size;
|
||||
size -= efilist->siglist_size;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist)
|
||||
{
|
||||
uefi_vars_cert *c;
|
||||
uefi_vars_hash *h;
|
||||
uint64_t size = 0;
|
||||
|
||||
QTAILQ_FOREACH(c, &siglist->x509, next) {
|
||||
size += sizeof(efi_siglist) + sizeof(QemuUUID) + c->size;
|
||||
}
|
||||
|
||||
if (!QTAILQ_EMPTY(&siglist->sha256)) {
|
||||
size += sizeof(efi_siglist);
|
||||
QTAILQ_FOREACH(h, &siglist->sha256, next) {
|
||||
size += sizeof(QemuUUID) + 32;
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate UEFI signature list.
|
||||
*/
|
||||
void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist,
|
||||
void *data, uint64_t size)
|
||||
{
|
||||
uefi_vars_cert *c;
|
||||
uefi_vars_hash *h;
|
||||
efi_siglist *efilist;
|
||||
uint64_t pos = 0, start;
|
||||
uint32_t i;
|
||||
|
||||
QTAILQ_FOREACH(c, &siglist->x509, next) {
|
||||
efilist = data + pos;
|
||||
efilist->guid_type = EfiCertX509Guid;
|
||||
efilist->sig_size = sizeof(QemuUUID) + c->size;
|
||||
efilist->header_size = 0;
|
||||
|
||||
start = pos + sizeof(efi_siglist);
|
||||
memcpy(data + start,
|
||||
&c->owner, sizeof(QemuUUID));
|
||||
memcpy(data + start + sizeof(QemuUUID),
|
||||
c->data, c->size);
|
||||
|
||||
efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size;
|
||||
pos += efilist->siglist_size;
|
||||
}
|
||||
|
||||
if (!QTAILQ_EMPTY(&siglist->sha256)) {
|
||||
efilist = data + pos;
|
||||
efilist->guid_type = EfiCertSha256Guid;
|
||||
efilist->sig_size = sizeof(QemuUUID) + 32;
|
||||
efilist->header_size = 0;
|
||||
|
||||
i = 0;
|
||||
start = pos + sizeof(efi_siglist);
|
||||
QTAILQ_FOREACH(h, &siglist->sha256, next) {
|
||||
memcpy(data + start + efilist->sig_size * i,
|
||||
&h->owner, sizeof(QemuUUID));
|
||||
memcpy(data + start + efilist->sig_size * i + sizeof(QemuUUID),
|
||||
h->data, 32);
|
||||
i++;
|
||||
}
|
||||
|
||||
efilist->siglist_size = sizeof(efi_siglist) + efilist->sig_size * i;
|
||||
pos += efilist->siglist_size;
|
||||
}
|
||||
|
||||
assert(pos == size);
|
||||
}
|
124
hw/uefi/var-service-sysbus.c
Normal file
124
hw/uefi/var-service-sysbus.c
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi vars device - sysbus variant.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
#include "hw/uefi/hardware-info.h"
|
||||
#include "hw/uefi/var-service.h"
|
||||
#include "hw/uefi/var-service-api.h"
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(uefi_vars_sysbus_state, UEFI_VARS_SYSBUS)
|
||||
|
||||
struct uefi_vars_sysbus_state {
|
||||
SysBusDevice parent_obj;
|
||||
struct uefi_vars_state state;
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_uefi_vars_sysbus = {
|
||||
.name = TYPE_UEFI_VARS_SYSBUS,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_STRUCT(state, uefi_vars_sysbus_state, 0,
|
||||
vmstate_uefi_vars, uefi_vars_state),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const Property uefi_vars_sysbus_properties[] = {
|
||||
DEFINE_PROP_SIZE("size", uefi_vars_sysbus_state, state.max_storage,
|
||||
256 * 1024),
|
||||
DEFINE_PROP_STRING("jsonfile", uefi_vars_sysbus_state, state.jsonfile),
|
||||
DEFINE_PROP_BOOL("force-secure-boot", uefi_vars_sysbus_state,
|
||||
state.force_secure_boot, false),
|
||||
DEFINE_PROP_BOOL("disable-custom-mode", uefi_vars_sysbus_state,
|
||||
state.disable_custom_mode, false),
|
||||
DEFINE_PROP_BOOL("use-pio", uefi_vars_sysbus_state,
|
||||
state.use_pio, false),
|
||||
};
|
||||
|
||||
static void uefi_vars_sysbus_init(Object *obj)
|
||||
{
|
||||
uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(obj);
|
||||
|
||||
uefi_vars_init(obj, &uv->state);
|
||||
}
|
||||
|
||||
static void uefi_vars_sysbus_reset(DeviceState *dev)
|
||||
{
|
||||
uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev);
|
||||
|
||||
uefi_vars_hard_reset(&uv->state);
|
||||
}
|
||||
|
||||
static void uefi_vars_sysbus_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
uefi_vars_sysbus_state *uv = UEFI_VARS_SYSBUS(dev);
|
||||
SysBusDevice *sysbus = SYS_BUS_DEVICE(dev);
|
||||
|
||||
sysbus_init_mmio(sysbus, &uv->state.mr);
|
||||
uefi_vars_realize(&uv->state, errp);
|
||||
}
|
||||
|
||||
static void uefi_vars_sysbus_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = uefi_vars_sysbus_realize;
|
||||
dc->vmsd = &vmstate_uefi_vars_sysbus;
|
||||
dc->user_creatable = true;
|
||||
device_class_set_legacy_reset(dc, uefi_vars_sysbus_reset);
|
||||
device_class_set_props(dc, uefi_vars_sysbus_properties);
|
||||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||||
}
|
||||
|
||||
/* generic: hardware discovery via FDT */
|
||||
static const TypeInfo uefi_vars_sysbus_info = {
|
||||
.name = TYPE_UEFI_VARS_SYSBUS,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(uefi_vars_sysbus_state),
|
||||
.instance_init = uefi_vars_sysbus_init,
|
||||
.class_init = uefi_vars_sysbus_class_init,
|
||||
};
|
||||
module_obj(TYPE_UEFI_VARS_SYSBUS);
|
||||
|
||||
static void uefi_vars_x64_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
HARDWARE_INFO_SIMPLE_DEVICE hwinfo = {
|
||||
.mmio_address = cpu_to_le64(0xfef10000),
|
||||
};
|
||||
SysBusDevice *sysbus = SYS_BUS_DEVICE(dev);
|
||||
|
||||
uefi_vars_sysbus_realize(dev, errp);
|
||||
|
||||
hardware_info_register(HardwareInfoQemuUefiVars,
|
||||
&hwinfo, sizeof(hwinfo));
|
||||
sysbus_mmio_map(sysbus, 0, hwinfo.mmio_address);
|
||||
}
|
||||
|
||||
static void uefi_vars_x64_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = uefi_vars_x64_realize;
|
||||
}
|
||||
|
||||
/* x64: hardware discovery via etc/hardware-info fw_cfg */
|
||||
static const TypeInfo uefi_vars_x64_info = {
|
||||
.name = TYPE_UEFI_VARS_X64,
|
||||
.parent = TYPE_UEFI_VARS_SYSBUS,
|
||||
.class_init = uefi_vars_x64_class_init,
|
||||
};
|
||||
module_obj(TYPE_UEFI_VARS_X64);
|
||||
|
||||
static void uefi_vars_sysbus_register_types(void)
|
||||
{
|
||||
type_register_static(&uefi_vars_sysbus_info);
|
||||
type_register_static(&uefi_vars_x64_info);
|
||||
}
|
||||
|
||||
type_init(uefi_vars_sysbus_register_types)
|
241
hw/uefi/var-service-utils.c
Normal file
241
hw/uefi/var-service-utils.c
Normal file
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi vars device - helper functions for ucs2 strings and tracing
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "system/dma.h"
|
||||
|
||||
#include "hw/uefi/var-service.h"
|
||||
|
||||
#include "trace/trace-hw_uefi.h"
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/*
|
||||
* string helper functions.
|
||||
*
|
||||
* Most of the time uefi ucs2 strings are NULL-terminated, except
|
||||
* sometimes when they are not (for example in variable policies).
|
||||
*/
|
||||
|
||||
gboolean uefi_str_is_valid(const uint16_t *str, size_t len,
|
||||
gboolean must_be_null_terminated)
|
||||
{
|
||||
size_t pos = 0;
|
||||
|
||||
for (;;) {
|
||||
if (pos == len) {
|
||||
if (must_be_null_terminated) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
switch (str[pos]) {
|
||||
case 0:
|
||||
/* end of string */
|
||||
return true;
|
||||
case 0xd800 ... 0xdfff:
|
||||
/* reject surrogates */
|
||||
return false;
|
||||
default:
|
||||
/* char is good, check next */
|
||||
break;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
size_t uefi_strlen(const uint16_t *str, size_t len)
|
||||
{
|
||||
size_t pos = 0;
|
||||
|
||||
for (;;) {
|
||||
if (pos == len) {
|
||||
return pos;
|
||||
}
|
||||
if (str[pos] == 0) {
|
||||
return pos;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen,
|
||||
const uint16_t *b, size_t blen,
|
||||
gboolean wildcards_in_a)
|
||||
{
|
||||
size_t pos = 0;
|
||||
|
||||
alen = alen / 2;
|
||||
blen = blen / 2;
|
||||
for (;;) {
|
||||
if (pos == alen && pos == blen) {
|
||||
return true;
|
||||
}
|
||||
if (pos == alen && b[pos] == 0) {
|
||||
return true;
|
||||
}
|
||||
if (pos == blen && a[pos] == 0) {
|
||||
return true;
|
||||
}
|
||||
if (pos == alen || pos == blen) {
|
||||
return false;
|
||||
}
|
||||
if (a[pos] == 0 && b[pos] == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wildcards_in_a && a[pos] == '#') {
|
||||
if (!isxdigit(b[pos])) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (a[pos] != b[pos]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean uefi_str_equal(const uint16_t *a, size_t alen,
|
||||
const uint16_t *b, size_t blen)
|
||||
{
|
||||
return uefi_str_equal_ex(a, alen, b, blen, false);
|
||||
}
|
||||
|
||||
char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size)
|
||||
{
|
||||
char *str = g_malloc0(ucs2_size / 2 + 1);
|
||||
int i;
|
||||
|
||||
for (i = 0; i * 2 < ucs2_size; i++) {
|
||||
if (ucs2[i] == 0) {
|
||||
break;
|
||||
}
|
||||
if (ucs2[i] < 128) {
|
||||
str[i] = ucs2[i];
|
||||
} else {
|
||||
str[i] = '?';
|
||||
}
|
||||
}
|
||||
str[i] = 0;
|
||||
return str;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* time helper functions */
|
||||
|
||||
int uefi_time_compare(efi_time *a, efi_time *b)
|
||||
{
|
||||
if (a->year < b->year) {
|
||||
return -1;
|
||||
}
|
||||
if (a->year > b->year) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->month < b->month) {
|
||||
return -1;
|
||||
}
|
||||
if (a->month > b->month) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->day < b->day) {
|
||||
return -1;
|
||||
}
|
||||
if (a->day > b->day) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->hour < b->hour) {
|
||||
return -1;
|
||||
}
|
||||
if (a->hour > b->hour) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->minute < b->minute) {
|
||||
return -1;
|
||||
}
|
||||
if (a->minute > b->minute) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->second < b->second) {
|
||||
return -1;
|
||||
}
|
||||
if (a->second > b->second) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a->nanosecond < b->nanosecond) {
|
||||
return -1;
|
||||
}
|
||||
if (a->nanosecond > b->nanosecond) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* tracing helper functions */
|
||||
|
||||
void uefi_trace_variable(const char *action, QemuUUID guid,
|
||||
const uint16_t *name, uint64_t name_size)
|
||||
{
|
||||
QemuUUID be = qemu_uuid_bswap(guid);
|
||||
char *str_uuid = qemu_uuid_unparse_strdup(&be);
|
||||
char *str_name = uefi_ucs2_to_ascii(name, name_size);
|
||||
|
||||
trace_uefi_variable(action, str_name, name_size, str_uuid);
|
||||
|
||||
g_free(str_name);
|
||||
g_free(str_uuid);
|
||||
}
|
||||
|
||||
void uefi_trace_status(const char *action, efi_status status)
|
||||
{
|
||||
switch (status) {
|
||||
case EFI_SUCCESS:
|
||||
trace_uefi_status(action, "success");
|
||||
break;
|
||||
case EFI_INVALID_PARAMETER:
|
||||
trace_uefi_status(action, "invalid parameter");
|
||||
break;
|
||||
case EFI_UNSUPPORTED:
|
||||
trace_uefi_status(action, "unsupported");
|
||||
break;
|
||||
case EFI_BAD_BUFFER_SIZE:
|
||||
trace_uefi_status(action, "bad buffer size");
|
||||
break;
|
||||
case EFI_BUFFER_TOO_SMALL:
|
||||
trace_uefi_status(action, "buffer too small");
|
||||
break;
|
||||
case EFI_WRITE_PROTECTED:
|
||||
trace_uefi_status(action, "write protected");
|
||||
break;
|
||||
case EFI_OUT_OF_RESOURCES:
|
||||
trace_uefi_status(action, "out of resources");
|
||||
break;
|
||||
case EFI_NOT_FOUND:
|
||||
trace_uefi_status(action, "not found");
|
||||
break;
|
||||
case EFI_ACCESS_DENIED:
|
||||
trace_uefi_status(action, "access denied");
|
||||
break;
|
||||
case EFI_ALREADY_STARTED:
|
||||
trace_uefi_status(action, "already started");
|
||||
break;
|
||||
case EFI_SECURITY_VIOLATION:
|
||||
trace_uefi_status(action, "security violation");
|
||||
break;
|
||||
default:
|
||||
trace_uefi_status(action, "unknown error");
|
||||
break;
|
||||
}
|
||||
}
|
725
hw/uefi/var-service-vars.c
Normal file
725
hw/uefi/var-service-vars.c
Normal file
|
@ -0,0 +1,725 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi vars device - EfiSmmVariableProtocol implementation
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "system/dma.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
#include "hw/uefi/var-service.h"
|
||||
#include "hw/uefi/var-service-api.h"
|
||||
#include "hw/uefi/var-service-edk2.h"
|
||||
|
||||
#include "trace/trace-hw_uefi.h"
|
||||
|
||||
#define EFI_VARIABLE_ATTRIBUTE_SUPPORTED \
|
||||
(EFI_VARIABLE_NON_VOLATILE | \
|
||||
EFI_VARIABLE_BOOTSERVICE_ACCESS | \
|
||||
EFI_VARIABLE_RUNTIME_ACCESS | \
|
||||
EFI_VARIABLE_HARDWARE_ERROR_RECORD | \
|
||||
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | \
|
||||
EFI_VARIABLE_APPEND_WRITE)
|
||||
|
||||
|
||||
const VMStateDescription vmstate_uefi_time = {
|
||||
.name = "uefi-time",
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT16(year, efi_time),
|
||||
VMSTATE_UINT8(month, efi_time),
|
||||
VMSTATE_UINT8(day, efi_time),
|
||||
VMSTATE_UINT8(hour, efi_time),
|
||||
VMSTATE_UINT8(minute, efi_time),
|
||||
VMSTATE_UINT8(second, efi_time),
|
||||
VMSTATE_UINT32(nanosecond, efi_time),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
const VMStateDescription vmstate_uefi_variable = {
|
||||
.name = "uefi-variable",
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0),
|
||||
VMSTATE_UINT32(name_size, uefi_variable),
|
||||
VMSTATE_UINT32(data_size, uefi_variable),
|
||||
VMSTATE_UINT32(attributes, uefi_variable),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(name, uefi_variable, 0, NULL, name_size),
|
||||
VMSTATE_VBUFFER_ALLOC_UINT32(data, uefi_variable, 0, NULL, data_size),
|
||||
VMSTATE_STRUCT(time, uefi_variable, 0, vmstate_uefi_time, efi_time),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid,
|
||||
const uint16_t *name, uint64_t name_size)
|
||||
{
|
||||
uefi_variable *var;
|
||||
|
||||
QTAILQ_FOREACH(var, &uv->variables, next) {
|
||||
if (!uefi_str_equal(var->name, var->name_size,
|
||||
name, name_size)) {
|
||||
continue;
|
||||
}
|
||||
if (!qemu_uuid_is_equal(&var->guid, &guid)) {
|
||||
continue;
|
||||
}
|
||||
if (!var->data_size) {
|
||||
/* in process of being created/updated */
|
||||
continue;
|
||||
}
|
||||
return var;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static uefi_variable *add_variable(uefi_vars_state *uv, QemuUUID guid,
|
||||
const uint16_t *name, uint64_t name_size,
|
||||
uint32_t attributes)
|
||||
{
|
||||
uefi_variable *var;
|
||||
|
||||
var = g_new0(uefi_variable, 1);
|
||||
var->guid = guid;
|
||||
var->name = g_malloc(name_size);
|
||||
memcpy(var->name, name, name_size);
|
||||
var->name_size = name_size;
|
||||
var->attributes = attributes;
|
||||
|
||||
var->attributes &= ~EFI_VARIABLE_APPEND_WRITE;
|
||||
|
||||
QTAILQ_INSERT_TAIL(&uv->variables, var, next);
|
||||
return var;
|
||||
}
|
||||
|
||||
static void del_variable(uefi_vars_state *uv, uefi_variable *var)
|
||||
{
|
||||
if (!var) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTAILQ_REMOVE(&uv->variables, var, next);
|
||||
g_free(var->data);
|
||||
g_free(var->name);
|
||||
g_free(var->digest);
|
||||
g_free(var);
|
||||
}
|
||||
|
||||
static size_t variable_size(uefi_variable *var)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
size = sizeof(*var);
|
||||
size += var->name_size;
|
||||
size += var->data_size;
|
||||
size += var->digest_size;
|
||||
return size;
|
||||
}
|
||||
|
||||
void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid,
|
||||
const uint16_t *name, uint64_t name_size,
|
||||
uint32_t attributes,
|
||||
void *data, uint64_t data_size)
|
||||
{
|
||||
uefi_variable *old_var, *new_var;
|
||||
|
||||
uefi_trace_variable(__func__, guid, name, name_size);
|
||||
|
||||
old_var = uefi_vars_find_variable(uv, guid, name, name_size);
|
||||
if (old_var) {
|
||||
uv->used_storage -= variable_size(old_var);
|
||||
del_variable(uv, old_var);
|
||||
}
|
||||
|
||||
new_var = add_variable(uv, guid, name, name_size, attributes);
|
||||
new_var->data = g_malloc(data_size);
|
||||
new_var->data_size = data_size;
|
||||
memcpy(new_var->data, data, data_size);
|
||||
uv->used_storage += variable_size(new_var);
|
||||
}
|
||||
|
||||
void uefi_vars_clear_volatile(uefi_vars_state *uv)
|
||||
{
|
||||
uefi_variable *var, *n;
|
||||
|
||||
QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) {
|
||||
if (var->attributes & EFI_VARIABLE_NON_VOLATILE) {
|
||||
continue;
|
||||
}
|
||||
uv->used_storage -= variable_size(var);
|
||||
del_variable(uv, var);
|
||||
}
|
||||
}
|
||||
|
||||
void uefi_vars_clear_all(uefi_vars_state *uv)
|
||||
{
|
||||
uefi_variable *var, *n;
|
||||
|
||||
QTAILQ_FOREACH_SAFE(var, &uv->variables, next, n) {
|
||||
del_variable(uv, var);
|
||||
}
|
||||
uv->used_storage = 0;
|
||||
}
|
||||
|
||||
void uefi_vars_update_storage(uefi_vars_state *uv)
|
||||
{
|
||||
uefi_variable *var;
|
||||
|
||||
uv->used_storage = 0;
|
||||
QTAILQ_FOREACH(var, &uv->variables, next) {
|
||||
uv->used_storage += variable_size(var);
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean check_access(uefi_vars_state *uv, uefi_variable *var)
|
||||
{
|
||||
if (!uv->exit_boot_service) {
|
||||
if (!(var->attributes & EFI_VARIABLE_BOOTSERVICE_ACCESS)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!(var->attributes & EFI_VARIABLE_RUNTIME_ACCESS)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static efi_status check_update(uefi_vars_state *uv, uefi_variable *old_var,
|
||||
uefi_variable *new_var)
|
||||
{
|
||||
efi_status status;
|
||||
|
||||
if (old_var) {
|
||||
if (!check_access(uv, old_var)) {
|
||||
return EFI_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_var) {
|
||||
if (new_var->attributes & ~EFI_VARIABLE_ATTRIBUTE_SUPPORTED) {
|
||||
return EFI_UNSUPPORTED;
|
||||
}
|
||||
if (!check_access(uv, new_var)) {
|
||||
return EFI_ACCESS_DENIED;
|
||||
}
|
||||
}
|
||||
|
||||
if (old_var && new_var) {
|
||||
if (old_var->attributes != new_var->attributes) {
|
||||
return EFI_INVALID_PARAMETER;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_var) {
|
||||
/* create + update */
|
||||
status = uefi_vars_policy_check(uv, new_var, old_var == NULL);
|
||||
} else {
|
||||
/* delete */
|
||||
g_assert(old_var);
|
||||
status = uefi_vars_policy_check(uv, old_var, false);
|
||||
}
|
||||
if (status != EFI_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = uefi_vars_check_secure_boot(uv, new_var ?: old_var);
|
||||
if (status != EFI_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
return EFI_SUCCESS;
|
||||
}
|
||||
|
||||
static void append_write(uefi_variable *old_var,
|
||||
uefi_variable *new_var)
|
||||
{
|
||||
uefi_vars_siglist siglist;
|
||||
uint64_t size;
|
||||
void *data;
|
||||
|
||||
uefi_vars_siglist_init(&siglist);
|
||||
uefi_vars_siglist_parse(&siglist, old_var->data, old_var->data_size);
|
||||
uefi_vars_siglist_parse(&siglist, new_var->data, new_var->data_size);
|
||||
|
||||
size = uefi_vars_siglist_blob_size(&siglist);
|
||||
data = g_malloc(size);
|
||||
uefi_vars_siglist_blob_generate(&siglist, data, size);
|
||||
|
||||
g_free(new_var->data);
|
||||
new_var->data = data;
|
||||
new_var->data_size = size;
|
||||
|
||||
uefi_vars_siglist_free(&siglist);
|
||||
}
|
||||
|
||||
static size_t uefi_vars_mm_error(mm_header *mhdr, mm_variable *mvar,
|
||||
uint64_t status)
|
||||
{
|
||||
mvar->status = status;
|
||||
return sizeof(*mvar);
|
||||
}
|
||||
|
||||
static size_t uefi_vars_mm_get_variable(uefi_vars_state *uv, mm_header *mhdr,
|
||||
mm_variable *mvar, void *func)
|
||||
{
|
||||
mm_variable_access *va = func;
|
||||
uint16_t *name;
|
||||
void *data;
|
||||
uefi_variable *var;
|
||||
uint64_t length;
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*va);
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (va->name_size > uv->max_storage ||
|
||||
va->data_size > uv->max_storage) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES);
|
||||
}
|
||||
|
||||
name = func + sizeof(*va);
|
||||
if (uadd64_overflow(length, va->name_size, &length)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (!uefi_str_is_valid(name, va->name_size, true)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uefi_trace_variable(__func__, va->guid, name, va->name_size);
|
||||
|
||||
var = uefi_vars_find_variable(uv, va->guid, name, va->name_size);
|
||||
if (!var) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND);
|
||||
}
|
||||
|
||||
/* check permissions etc. */
|
||||
if (!check_access(uv, var)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_ACCESS_DENIED);
|
||||
}
|
||||
|
||||
data = func + sizeof(*va) + va->name_size;
|
||||
if (uadd64_overflow(length, va->data_size, &length)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (uv->buf_size < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
va->attributes = var->attributes;
|
||||
if (va->data_size < var->data_size) {
|
||||
va->data_size = var->data_size;
|
||||
length -= va->data_size;
|
||||
mvar->status = EFI_BUFFER_TOO_SMALL;
|
||||
} else {
|
||||
va->data_size = var->data_size;
|
||||
memcpy(data, var->data, var->data_size);
|
||||
mvar->status = EFI_SUCCESS;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
uefi_vars_mm_get_next_variable(uefi_vars_state *uv, mm_header *mhdr,
|
||||
mm_variable *mvar, void *func)
|
||||
{
|
||||
mm_next_variable *nv = func;
|
||||
uefi_variable *var;
|
||||
uint16_t *name;
|
||||
uint64_t length;
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*nv);
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (nv->name_size > uv->max_storage) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES);
|
||||
}
|
||||
|
||||
name = func + sizeof(*nv);
|
||||
if (uadd64_overflow(length, nv->name_size, &length)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (!uefi_str_is_valid(name, nv->name_size, true)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
if (uefi_strlen(name, nv->name_size) == 0) {
|
||||
/* empty string -> first */
|
||||
var = QTAILQ_FIRST(&uv->variables);
|
||||
if (!var) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
var = uefi_vars_find_variable(uv, nv->guid, name, nv->name_size);
|
||||
if (!var) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
|
||||
}
|
||||
do {
|
||||
var = QTAILQ_NEXT(var, next);
|
||||
} while (var && !check_access(uv, var));
|
||||
if (!var) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*nv) + var->name_size;
|
||||
if (uv->buf_size < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
nv->guid = var->guid;
|
||||
nv->name_size = var->name_size;
|
||||
memcpy(name, var->name, var->name_size);
|
||||
mvar->status = EFI_SUCCESS;
|
||||
return length;
|
||||
}
|
||||
|
||||
static bool uefi_vars_mm_digest_compare(uefi_variable *old_var,
|
||||
uefi_variable *new_var)
|
||||
{
|
||||
if (!old_var->digest ||
|
||||
!new_var->digest ||
|
||||
!old_var->digest_size ||
|
||||
!new_var->digest_size) {
|
||||
/* should not happen */
|
||||
trace_uefi_vars_security_violation("inconsistent authvar digest state");
|
||||
return false;
|
||||
}
|
||||
if (old_var->digest_size != new_var->digest_size) {
|
||||
trace_uefi_vars_security_violation("authvar digest size mismatch");
|
||||
return false;
|
||||
}
|
||||
if (memcmp(old_var->digest, new_var->digest,
|
||||
old_var->digest_size) != 0) {
|
||||
trace_uefi_vars_security_violation("authvar digest data mismatch");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t uefi_vars_mm_set_variable(uefi_vars_state *uv, mm_header *mhdr,
|
||||
mm_variable *mvar, void *func)
|
||||
{
|
||||
mm_variable_access *va = func;
|
||||
uint32_t attributes = 0;
|
||||
uint16_t *name;
|
||||
void *data;
|
||||
uefi_variable *old_var, *new_var;
|
||||
uint64_t length;
|
||||
size_t new_storage;
|
||||
efi_status status;
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*va);
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
if (va->name_size > uv->max_storage ||
|
||||
va->data_size > uv->max_storage) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_OUT_OF_RESOURCES);
|
||||
}
|
||||
|
||||
name = func + sizeof(*va);
|
||||
if (uadd64_overflow(length, va->name_size, &length)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
data = func + sizeof(*va) + va->name_size;
|
||||
if (uadd64_overflow(length, va->data_size, &length)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
g_assert(va->name_size < G_MAXUINT32);
|
||||
g_assert(va->data_size < G_MAXUINT32);
|
||||
|
||||
if (!uefi_str_is_valid(name, va->name_size, true)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uefi_trace_variable(__func__, va->guid, name, va->name_size);
|
||||
|
||||
old_var = uefi_vars_find_variable(uv, va->guid, name, va->name_size);
|
||||
if (va->data_size) {
|
||||
new_var = add_variable(uv, va->guid, name, va->name_size,
|
||||
va->attributes);
|
||||
if (va->attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) {
|
||||
/* not implemented (deprecated in uefi spec) */
|
||||
warn_report("%s: AUTHENTICATED_WRITE_ACCESS", __func__);
|
||||
mvar->status = EFI_UNSUPPORTED;
|
||||
goto rollback;
|
||||
} else if (va->attributes &
|
||||
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) {
|
||||
status = uefi_vars_check_auth_2(uv, new_var, va, data);
|
||||
if (status != EFI_SUCCESS) {
|
||||
mvar->status = status;
|
||||
goto rollback;
|
||||
}
|
||||
if (old_var && new_var) {
|
||||
if (uefi_time_compare(&old_var->time, &new_var->time) > 0) {
|
||||
trace_uefi_vars_security_violation("time check failed");
|
||||
mvar->status = EFI_SECURITY_VIOLATION;
|
||||
goto rollback;
|
||||
}
|
||||
if (old_var->digest_size || new_var->digest_size) {
|
||||
if (!uefi_vars_mm_digest_compare(old_var, new_var)) {
|
||||
mvar->status = EFI_SECURITY_VIOLATION;
|
||||
goto rollback;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
new_var->data = g_malloc(va->data_size);
|
||||
memcpy(new_var->data, data, va->data_size);
|
||||
new_var->data_size = va->data_size;
|
||||
}
|
||||
if (!new_var->data) {
|
||||
/* we land here when deleting authenticated variables */
|
||||
del_variable(uv, new_var);
|
||||
new_var = NULL;
|
||||
}
|
||||
} else {
|
||||
new_var = NULL;
|
||||
}
|
||||
|
||||
if (!old_var && !new_var) {
|
||||
/* delete non-existing variable -> nothing to do */
|
||||
mvar->status = EFI_SUCCESS;
|
||||
return sizeof(*mvar);
|
||||
}
|
||||
|
||||
/* check permissions etc. */
|
||||
status = check_update(uv, old_var, new_var);
|
||||
if (status != EFI_SUCCESS) {
|
||||
mvar->status = status;
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
if (va->attributes & EFI_VARIABLE_APPEND_WRITE && old_var && new_var) {
|
||||
/* merge signature databases */
|
||||
if (!uefi_vars_is_sb_any(new_var)) {
|
||||
mvar->status = EFI_UNSUPPORTED;
|
||||
goto rollback;
|
||||
}
|
||||
append_write(old_var, new_var);
|
||||
}
|
||||
|
||||
/* check storage space */
|
||||
new_storage = uv->used_storage;
|
||||
if (old_var) {
|
||||
new_storage -= variable_size(old_var);
|
||||
}
|
||||
if (new_var) {
|
||||
new_storage += variable_size(new_var);
|
||||
}
|
||||
if (new_storage > uv->max_storage) {
|
||||
mvar->status = EFI_OUT_OF_RESOURCES;
|
||||
goto rollback;
|
||||
}
|
||||
|
||||
attributes = new_var
|
||||
? new_var->attributes
|
||||
: old_var->attributes;
|
||||
|
||||
/* all good, commit */
|
||||
del_variable(uv, old_var);
|
||||
uv->used_storage = new_storage;
|
||||
|
||||
if (attributes & EFI_VARIABLE_NON_VOLATILE) {
|
||||
uefi_vars_json_save(uv);
|
||||
}
|
||||
|
||||
if (new_var && uefi_vars_is_sb_pk(new_var)) {
|
||||
uefi_vars_auth_init(uv);
|
||||
}
|
||||
|
||||
mvar->status = EFI_SUCCESS;
|
||||
return sizeof(*mvar);
|
||||
|
||||
rollback:
|
||||
del_variable(uv, new_var);
|
||||
return sizeof(*mvar);
|
||||
}
|
||||
|
||||
static size_t uefi_vars_mm_variable_info(uefi_vars_state *uv, mm_header *mhdr,
|
||||
mm_variable *mvar, void *func)
|
||||
{
|
||||
mm_variable_info *vi = func;
|
||||
uint64_t length;
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*vi);
|
||||
if (uv->buf_size < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
vi->max_storage_size = uv->max_storage;
|
||||
vi->free_storage_size = uv->max_storage - uv->used_storage;
|
||||
vi->max_variable_size = uv->max_storage >> 2;
|
||||
vi->attributes = 0;
|
||||
|
||||
mvar->status = EFI_SUCCESS;
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
uefi_vars_mm_get_payload_size(uefi_vars_state *uv, mm_header *mhdr,
|
||||
mm_variable *mvar, void *func)
|
||||
{
|
||||
mm_get_payload_size *ps = func;
|
||||
uint64_t length;
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*ps);
|
||||
if (uv->buf_size < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
ps->payload_size = uv->buf_size;
|
||||
mvar->status = EFI_SUCCESS;
|
||||
return length;
|
||||
}
|
||||
|
||||
static size_t
|
||||
uefi_vars_mm_lock_variable(uefi_vars_state *uv, mm_header *mhdr,
|
||||
mm_variable *mvar, void *func)
|
||||
{
|
||||
mm_lock_variable *lv = func;
|
||||
variable_policy_entry *pe;
|
||||
uint16_t *name, *dest;
|
||||
uint64_t length;
|
||||
|
||||
length = sizeof(*mvar) + sizeof(*lv);
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
name = func + sizeof(*lv);
|
||||
if (uadd64_overflow(length, lv->name_size, &length)) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
if (mhdr->length < length) {
|
||||
return uefi_vars_mm_error(mhdr, mvar, EFI_BAD_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
uefi_trace_variable(__func__, lv->guid, name, lv->name_size);
|
||||
|
||||
pe = g_malloc0(sizeof(*pe) + lv->name_size);
|
||||
pe->version = VARIABLE_POLICY_ENTRY_REVISION;
|
||||
pe->size = sizeof(*pe) + lv->name_size;
|
||||
pe->offset_to_name = sizeof(*pe);
|
||||
pe->namespace = lv->guid;
|
||||
pe->min_size = 0;
|
||||
pe->max_size = UINT32_MAX;
|
||||
pe->attributes_must_have = 0;
|
||||
pe->attributes_cant_have = 0;
|
||||
pe->lock_policy_type = VARIABLE_POLICY_TYPE_LOCK_NOW;
|
||||
|
||||
dest = (void *)pe + pe->offset_to_name;
|
||||
memcpy(dest, name, lv->name_size);
|
||||
|
||||
uefi_vars_add_policy(uv, pe);
|
||||
g_free(pe);
|
||||
|
||||
mvar->status = EFI_SUCCESS;
|
||||
return length;
|
||||
}
|
||||
|
||||
uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv)
|
||||
{
|
||||
static const char *fnames[] = {
|
||||
"zero",
|
||||
"get-variable",
|
||||
"get-next-variable-name",
|
||||
"set-variable",
|
||||
"query-variable-info",
|
||||
"ready-to-boot",
|
||||
"exit-boot-service",
|
||||
"get-statistics",
|
||||
"lock-variable",
|
||||
"var-check-prop-set",
|
||||
"var-check-prop-get",
|
||||
"get-payload-size",
|
||||
"init-runtime-cache-contect",
|
||||
"sync-runtime-cache",
|
||||
"get-runtime-cache-info",
|
||||
};
|
||||
const char *fname;
|
||||
uint64_t length;
|
||||
|
||||
mm_header *mhdr = (mm_header *) uv->buffer;
|
||||
mm_variable *mvar = (mm_variable *) (uv->buffer + sizeof(*mhdr));
|
||||
void *func = (uv->buffer + sizeof(*mhdr) + sizeof(*mvar));
|
||||
|
||||
if (mhdr->length < sizeof(*mvar)) {
|
||||
return UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE;
|
||||
}
|
||||
|
||||
fname = mvar->function < ARRAY_SIZE(fnames)
|
||||
? fnames[mvar->function]
|
||||
: "unknown";
|
||||
trace_uefi_vars_proto_cmd(fname);
|
||||
|
||||
switch (mvar->function) {
|
||||
case SMM_VARIABLE_FUNCTION_GET_VARIABLE:
|
||||
length = uefi_vars_mm_get_variable(uv, mhdr, mvar, func);
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME:
|
||||
length = uefi_vars_mm_get_next_variable(uv, mhdr, mvar, func);
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_SET_VARIABLE:
|
||||
length = uefi_vars_mm_set_variable(uv, mhdr, mvar, func);
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO:
|
||||
length = uefi_vars_mm_variable_info(uv, mhdr, mvar, func);
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_LOCK_VARIABLE:
|
||||
length = uefi_vars_mm_lock_variable(uv, mhdr, mvar, func);
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE:
|
||||
length = uefi_vars_mm_get_payload_size(uv, mhdr, mvar, func);
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_READY_TO_BOOT:
|
||||
trace_uefi_event("ready-to-boot");
|
||||
uv->ready_to_boot = true;
|
||||
length = 0;
|
||||
break;
|
||||
|
||||
case SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE:
|
||||
trace_uefi_event("exit-boot-service");
|
||||
uv->exit_boot_service = true;
|
||||
length = 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
length = uefi_vars_mm_error(mhdr, mvar, EFI_UNSUPPORTED);
|
||||
break;
|
||||
}
|
||||
|
||||
if (mhdr->length < length) {
|
||||
mvar->status = EFI_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
uefi_trace_status(__func__, mvar->status);
|
||||
return UEFI_VARS_STS_SUCCESS;
|
||||
}
|
35
include/hw/uefi/hardware-info.h
Normal file
35
include/hw/uefi/hardware-info.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* pass hardware information to uefi
|
||||
*
|
||||
* see OvmfPkg/Library/HardwareInfoLib/ in edk2
|
||||
*/
|
||||
#ifndef QEMU_UEFI_HARDWARE_INFO_H
|
||||
#define QEMU_UEFI_HARDWARE_INFO_H
|
||||
|
||||
/* data structures */
|
||||
|
||||
typedef enum {
|
||||
HardwareInfoTypeUndefined = 0,
|
||||
HardwareInfoTypeHostBridge = 1,
|
||||
HardwareInfoQemuUefiVars = 2,
|
||||
} HARDWARE_INFO_TYPE;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
uint64_t uint64;
|
||||
HARDWARE_INFO_TYPE value;
|
||||
} type;
|
||||
uint64_t size;
|
||||
} HARDWARE_INFO_HEADER;
|
||||
|
||||
typedef struct {
|
||||
uint64_t mmio_address;
|
||||
} HARDWARE_INFO_SIMPLE_DEVICE;
|
||||
|
||||
/* qemu functions */
|
||||
|
||||
void hardware_info_register(HARDWARE_INFO_TYPE type, void *info, uint64_t size);
|
||||
|
||||
#endif /* QEMU_UEFI_HARDWARE_INFO_H */
|
48
include/hw/uefi/var-service-api.h
Normal file
48
include/hw/uefi/var-service-api.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi-vars device - API of the virtual device for guest/host communication.
|
||||
*/
|
||||
#ifndef QEMU_UEFI_VAR_SERVICE_API_H
|
||||
#define QEMU_UEFI_VAR_SERVICE_API_H
|
||||
|
||||
/* qom: device names */
|
||||
#define TYPE_UEFI_VARS_X64 "uefi-vars-x64"
|
||||
#define TYPE_UEFI_VARS_SYSBUS "uefi-vars-sysbus"
|
||||
|
||||
/* sysbus: fdt node path */
|
||||
#define UEFI_VARS_FDT_NODE "qemu-uefi-vars"
|
||||
#define UEFI_VARS_FDT_COMPAT "qemu,uefi-vars"
|
||||
|
||||
/* registers */
|
||||
#define UEFI_VARS_REG_MAGIC 0x00 /* 16 bit */
|
||||
#define UEFI_VARS_REG_CMD_STS 0x02 /* 16 bit */
|
||||
#define UEFI_VARS_REG_BUFFER_SIZE 0x04 /* 32 bit */
|
||||
#define UEFI_VARS_REG_DMA_BUFFER_ADDR_LO 0x08 /* 32 bit */
|
||||
#define UEFI_VARS_REG_DMA_BUFFER_ADDR_HI 0x0c /* 32 bit */
|
||||
#define UEFI_VARS_REG_PIO_BUFFER_TRANSFER 0x10 /* 8-64 bit */
|
||||
#define UEFI_VARS_REG_PIO_BUFFER_CRC32C 0x18 /* 32 bit (read-only) */
|
||||
#define UEFI_VARS_REG_FLAGS 0x1c /* 32 bit */
|
||||
#define UEFI_VARS_REGS_SIZE 0x20
|
||||
|
||||
/* flags register */
|
||||
#define UEFI_VARS_FLAG_USE_PIO (1 << 0)
|
||||
|
||||
/* magic value */
|
||||
#define UEFI_VARS_MAGIC_VALUE 0xef1
|
||||
|
||||
/* command values */
|
||||
#define UEFI_VARS_CMD_RESET 0x01
|
||||
#define UEFI_VARS_CMD_DMA_MM 0x02
|
||||
#define UEFI_VARS_CMD_PIO_MM 0x03
|
||||
#define UEFI_VARS_CMD_PIO_ZERO_OFFSET 0x04
|
||||
|
||||
/* status values */
|
||||
#define UEFI_VARS_STS_SUCCESS 0x00
|
||||
#define UEFI_VARS_STS_BUSY 0x01
|
||||
#define UEFI_VARS_STS_ERR_UNKNOWN 0x10
|
||||
#define UEFI_VARS_STS_ERR_NOT_SUPPORTED 0x11
|
||||
#define UEFI_VARS_STS_ERR_BAD_BUFFER_SIZE 0x12
|
||||
|
||||
|
||||
#endif /* QEMU_UEFI_VAR_SERVICE_API_H */
|
227
include/hw/uefi/var-service-edk2.h
Normal file
227
include/hw/uefi/var-service-edk2.h
Normal file
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi-vars device - structs and defines from edk2
|
||||
*
|
||||
* Note: The edk2 UINTN type has been mapped to uint64_t,
|
||||
* so the structs are compatible with 64bit edk2 builds.
|
||||
*/
|
||||
#ifndef QEMU_UEFI_VAR_SERVICE_EDK2_H
|
||||
#define QEMU_UEFI_VAR_SERVICE_EDK2_H
|
||||
|
||||
#include "qemu/uuid.h"
|
||||
|
||||
#define MAX_BIT 0x8000000000000000ULL
|
||||
#define ENCODE_ERROR(StatusCode) (MAX_BIT | (StatusCode))
|
||||
#define EFI_SUCCESS 0
|
||||
#define EFI_INVALID_PARAMETER ENCODE_ERROR(2)
|
||||
#define EFI_UNSUPPORTED ENCODE_ERROR(3)
|
||||
#define EFI_BAD_BUFFER_SIZE ENCODE_ERROR(4)
|
||||
#define EFI_BUFFER_TOO_SMALL ENCODE_ERROR(5)
|
||||
#define EFI_WRITE_PROTECTED ENCODE_ERROR(8)
|
||||
#define EFI_OUT_OF_RESOURCES ENCODE_ERROR(9)
|
||||
#define EFI_NOT_FOUND ENCODE_ERROR(14)
|
||||
#define EFI_ACCESS_DENIED ENCODE_ERROR(15)
|
||||
#define EFI_ALREADY_STARTED ENCODE_ERROR(20)
|
||||
#define EFI_SECURITY_VIOLATION ENCODE_ERROR(26)
|
||||
|
||||
#define EFI_VARIABLE_NON_VOLATILE 0x01
|
||||
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x02
|
||||
#define EFI_VARIABLE_RUNTIME_ACCESS 0x04
|
||||
#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x08
|
||||
#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x10 /* deprecated */
|
||||
#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x20
|
||||
#define EFI_VARIABLE_APPEND_WRITE 0x40
|
||||
|
||||
/* SecureBootEnable */
|
||||
#define SECURE_BOOT_ENABLE 1
|
||||
#define SECURE_BOOT_DISABLE 0
|
||||
|
||||
/* SecureBoot */
|
||||
#define SECURE_BOOT_MODE_ENABLE 1
|
||||
#define SECURE_BOOT_MODE_DISABLE 0
|
||||
|
||||
/* CustomMode */
|
||||
#define CUSTOM_SECURE_BOOT_MODE 1
|
||||
#define STANDARD_SECURE_BOOT_MODE 0
|
||||
|
||||
/* SetupMode */
|
||||
#define SETUP_MODE 1
|
||||
#define USER_MODE 0
|
||||
|
||||
typedef uint64_t efi_status;
|
||||
typedef struct mm_header mm_header;
|
||||
|
||||
/* EFI_MM_COMMUNICATE_HEADER */
|
||||
struct mm_header {
|
||||
QemuUUID guid;
|
||||
uint64_t length;
|
||||
};
|
||||
|
||||
/* --- EfiSmmVariableProtocol ---------------------------------------- */
|
||||
|
||||
#define SMM_VARIABLE_FUNCTION_GET_VARIABLE 1
|
||||
#define SMM_VARIABLE_FUNCTION_GET_NEXT_VARIABLE_NAME 2
|
||||
#define SMM_VARIABLE_FUNCTION_SET_VARIABLE 3
|
||||
#define SMM_VARIABLE_FUNCTION_QUERY_VARIABLE_INFO 4
|
||||
#define SMM_VARIABLE_FUNCTION_READY_TO_BOOT 5
|
||||
#define SMM_VARIABLE_FUNCTION_EXIT_BOOT_SERVICE 6
|
||||
#define SMM_VARIABLE_FUNCTION_LOCK_VARIABLE 8
|
||||
#define SMM_VARIABLE_FUNCTION_GET_PAYLOAD_SIZE 11
|
||||
|
||||
typedef struct mm_variable mm_variable;
|
||||
typedef struct mm_variable_access mm_variable_access;
|
||||
typedef struct mm_next_variable mm_next_variable;
|
||||
typedef struct mm_next_variable mm_lock_variable;
|
||||
typedef struct mm_variable_info mm_variable_info;
|
||||
typedef struct mm_get_payload_size mm_get_payload_size;
|
||||
|
||||
/* SMM_VARIABLE_COMMUNICATE_HEADER */
|
||||
struct mm_variable {
|
||||
uint64_t function;
|
||||
uint64_t status;
|
||||
};
|
||||
|
||||
/* SMM_VARIABLE_COMMUNICATE_ACCESS_VARIABLE */
|
||||
struct QEMU_PACKED mm_variable_access {
|
||||
QemuUUID guid;
|
||||
uint64_t data_size;
|
||||
uint64_t name_size;
|
||||
uint32_t attributes;
|
||||
/* Name */
|
||||
/* Data */
|
||||
};
|
||||
|
||||
/* SMM_VARIABLE_COMMUNICATE_GET_NEXT_VARIABLE_NAME */
|
||||
struct mm_next_variable {
|
||||
QemuUUID guid;
|
||||
uint64_t name_size;
|
||||
/* Name */
|
||||
};
|
||||
|
||||
/* SMM_VARIABLE_COMMUNICATE_QUERY_VARIABLE_INFO */
|
||||
struct QEMU_PACKED mm_variable_info {
|
||||
uint64_t max_storage_size;
|
||||
uint64_t free_storage_size;
|
||||
uint64_t max_variable_size;
|
||||
uint32_t attributes;
|
||||
};
|
||||
|
||||
/* SMM_VARIABLE_COMMUNICATE_GET_PAYLOAD_SIZE */
|
||||
struct mm_get_payload_size {
|
||||
uint64_t payload_size;
|
||||
};
|
||||
|
||||
/* --- VarCheckPolicyLibMmiHandler ----------------------------------- */
|
||||
|
||||
#define VAR_CHECK_POLICY_COMMAND_DISABLE 0x01
|
||||
#define VAR_CHECK_POLICY_COMMAND_IS_ENABLED 0x02
|
||||
#define VAR_CHECK_POLICY_COMMAND_REGISTER 0x03
|
||||
#define VAR_CHECK_POLICY_COMMAND_DUMP 0x04
|
||||
#define VAR_CHECK_POLICY_COMMAND_LOCK 0x05
|
||||
|
||||
typedef struct mm_check_policy mm_check_policy;
|
||||
typedef struct mm_check_policy_is_enabled mm_check_policy_is_enabled;
|
||||
typedef struct mm_check_policy_dump_params mm_check_policy_dump_params;
|
||||
|
||||
/* VAR_CHECK_POLICY_COMM_HEADER */
|
||||
struct QEMU_PACKED mm_check_policy {
|
||||
uint32_t signature;
|
||||
uint32_t revision;
|
||||
uint32_t command;
|
||||
uint64_t result;
|
||||
};
|
||||
|
||||
/* VAR_CHECK_POLICY_COMM_IS_ENABLED_PARAMS */
|
||||
struct QEMU_PACKED mm_check_policy_is_enabled {
|
||||
uint8_t state;
|
||||
};
|
||||
|
||||
/* VAR_CHECK_POLICY_COMM_DUMP_PARAMS */
|
||||
struct QEMU_PACKED mm_check_policy_dump_params {
|
||||
uint32_t page_requested;
|
||||
uint32_t total_size;
|
||||
uint32_t page_size;
|
||||
uint8_t has_more;
|
||||
};
|
||||
|
||||
/* --- Edk2VariablePolicyProtocol ------------------------------------ */
|
||||
|
||||
#define VARIABLE_POLICY_ENTRY_REVISION 0x00010000
|
||||
|
||||
#define VARIABLE_POLICY_TYPE_NO_LOCK 0
|
||||
#define VARIABLE_POLICY_TYPE_LOCK_NOW 1
|
||||
#define VARIABLE_POLICY_TYPE_LOCK_ON_CREATE 2
|
||||
#define VARIABLE_POLICY_TYPE_LOCK_ON_VAR_STATE 3
|
||||
|
||||
typedef struct variable_policy_entry variable_policy_entry;
|
||||
typedef struct variable_lock_on_var_state variable_lock_on_var_state;
|
||||
|
||||
/* VARIABLE_POLICY_ENTRY */
|
||||
struct variable_policy_entry {
|
||||
uint32_t version;
|
||||
uint16_t size;
|
||||
uint16_t offset_to_name;
|
||||
QemuUUID namespace;
|
||||
uint32_t min_size;
|
||||
uint32_t max_size;
|
||||
uint32_t attributes_must_have;
|
||||
uint32_t attributes_cant_have;
|
||||
uint8_t lock_policy_type;
|
||||
uint8_t padding[3];
|
||||
/* LockPolicy */
|
||||
/* Name */
|
||||
};
|
||||
|
||||
/* VARIABLE_LOCK_ON_VAR_STATE_POLICY */
|
||||
struct variable_lock_on_var_state {
|
||||
QemuUUID namespace;
|
||||
uint8_t value;
|
||||
uint8_t padding;
|
||||
/* Name */
|
||||
};
|
||||
|
||||
/* --- variable authentication --------------------------------------- */
|
||||
|
||||
#define WIN_CERT_TYPE_EFI_GUID 0x0EF1
|
||||
|
||||
typedef struct efi_time efi_time;
|
||||
typedef struct efi_siglist efi_siglist;
|
||||
typedef struct variable_auth_2 variable_auth_2;
|
||||
|
||||
/* EFI_TIME */
|
||||
struct efi_time {
|
||||
uint16_t year;
|
||||
uint8_t month;
|
||||
uint8_t day;
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
uint8_t second;
|
||||
uint8_t pad1;
|
||||
uint32_t nanosecond;
|
||||
int16_t timezone;
|
||||
uint8_t daylight;
|
||||
uint8_t pad2;
|
||||
};
|
||||
|
||||
/* EFI_SIGNATURE_LIST */
|
||||
struct efi_siglist {
|
||||
QemuUUID guid_type;
|
||||
uint32_t siglist_size;
|
||||
uint32_t header_size;
|
||||
uint32_t sig_size;
|
||||
};
|
||||
|
||||
/* EFI_VARIABLE_AUTHENTICATION_2 */
|
||||
struct variable_auth_2 {
|
||||
struct efi_time timestamp;
|
||||
|
||||
/* WIN_CERTIFICATE_UEFI_GUID */
|
||||
uint32_t hdr_length;
|
||||
uint16_t hdr_revision;
|
||||
uint16_t hdr_cert_type;
|
||||
QemuUUID guid_cert_type;
|
||||
uint8_t cert_data[];
|
||||
};
|
||||
|
||||
#endif /* QEMU_UEFI_VAR_SERVICE_EDK2_H */
|
191
include/hw/uefi/var-service.h
Normal file
191
include/hw/uefi/var-service.h
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*
|
||||
* uefi-vars device - state struct and function prototypes
|
||||
*/
|
||||
#ifndef QEMU_UEFI_VAR_SERVICE_H
|
||||
#define QEMU_UEFI_VAR_SERVICE_H
|
||||
|
||||
#include "qemu/uuid.h"
|
||||
#include "qemu/queue.h"
|
||||
|
||||
#include "hw/uefi/var-service-edk2.h"
|
||||
|
||||
#define MAX_BUFFER_SIZE (64 * 1024)
|
||||
|
||||
typedef struct uefi_variable uefi_variable;
|
||||
typedef struct uefi_var_policy uefi_var_policy;
|
||||
typedef struct uefi_vars_state uefi_vars_state;
|
||||
|
||||
typedef struct uefi_vars_cert uefi_vars_cert;
|
||||
typedef struct uefi_vars_hash uefi_vars_hash;
|
||||
typedef struct uefi_vars_siglist uefi_vars_siglist;
|
||||
|
||||
struct uefi_variable {
|
||||
QemuUUID guid;
|
||||
uint16_t *name;
|
||||
uint32_t name_size;
|
||||
uint32_t attributes;
|
||||
void *data;
|
||||
uint32_t data_size;
|
||||
efi_time time;
|
||||
void *digest;
|
||||
uint32_t digest_size;
|
||||
QTAILQ_ENTRY(uefi_variable) next;
|
||||
};
|
||||
|
||||
struct uefi_var_policy {
|
||||
variable_policy_entry *entry;
|
||||
uint32_t entry_size;
|
||||
uint16_t *name;
|
||||
uint32_t name_size;
|
||||
|
||||
/* number of hashmarks (wildcard character) in name */
|
||||
uint32_t hashmarks;
|
||||
|
||||
QTAILQ_ENTRY(uefi_var_policy) next;
|
||||
};
|
||||
|
||||
struct uefi_vars_state {
|
||||
MemoryRegion mr;
|
||||
uint16_t sts;
|
||||
uint32_t buf_size;
|
||||
uint32_t buf_addr_lo;
|
||||
uint32_t buf_addr_hi;
|
||||
uint8_t *buffer;
|
||||
QTAILQ_HEAD(, uefi_variable) variables;
|
||||
QTAILQ_HEAD(, uefi_var_policy) var_policies;
|
||||
|
||||
/* pio transfer buffer */
|
||||
uint32_t pio_xfer_offset;
|
||||
uint8_t *pio_xfer_buffer;
|
||||
|
||||
/* boot phases */
|
||||
bool end_of_dxe;
|
||||
bool ready_to_boot;
|
||||
bool exit_boot_service;
|
||||
bool policy_locked;
|
||||
|
||||
/* storage accounting */
|
||||
uint64_t max_storage;
|
||||
uint64_t used_storage;
|
||||
|
||||
/* config options */
|
||||
char *jsonfile;
|
||||
int jsonfd;
|
||||
bool force_secure_boot;
|
||||
bool disable_custom_mode;
|
||||
bool use_pio;
|
||||
};
|
||||
|
||||
struct uefi_vars_cert {
|
||||
QTAILQ_ENTRY(uefi_vars_cert) next;
|
||||
QemuUUID owner;
|
||||
uint64_t size;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
struct uefi_vars_hash {
|
||||
QTAILQ_ENTRY(uefi_vars_hash) next;
|
||||
QemuUUID owner;
|
||||
uint8_t data[];
|
||||
};
|
||||
|
||||
struct uefi_vars_siglist {
|
||||
QTAILQ_HEAD(, uefi_vars_cert) x509;
|
||||
QTAILQ_HEAD(, uefi_vars_hash) sha256;
|
||||
};
|
||||
|
||||
/* vars-service-guid.c */
|
||||
extern const QemuUUID EfiGlobalVariable;
|
||||
extern const QemuUUID EfiImageSecurityDatabase;
|
||||
extern const QemuUUID EfiCustomModeEnable;
|
||||
extern const QemuUUID EfiSecureBootEnableDisable;
|
||||
|
||||
extern const QemuUUID EfiCertSha256Guid;
|
||||
extern const QemuUUID EfiCertSha384Guid;
|
||||
extern const QemuUUID EfiCertSha512Guid;
|
||||
extern const QemuUUID EfiCertRsa2048Guid;
|
||||
extern const QemuUUID EfiCertX509Guid;
|
||||
extern const QemuUUID EfiCertTypePkcs7Guid;
|
||||
|
||||
extern const QemuUUID EfiSmmVariableProtocolGuid;
|
||||
extern const QemuUUID VarCheckPolicyLibMmiHandlerGuid;
|
||||
|
||||
extern const QemuUUID EfiEndOfDxeEventGroupGuid;
|
||||
extern const QemuUUID EfiEventReadyToBootGuid;
|
||||
extern const QemuUUID EfiEventExitBootServicesGuid;
|
||||
|
||||
/* vars-service-utils.c */
|
||||
gboolean uefi_str_is_valid(const uint16_t *str, size_t len,
|
||||
gboolean must_be_null_terminated);
|
||||
size_t uefi_strlen(const uint16_t *str, size_t len);
|
||||
gboolean uefi_str_equal_ex(const uint16_t *a, size_t alen,
|
||||
const uint16_t *b, size_t blen,
|
||||
gboolean wildcards_in_a);
|
||||
gboolean uefi_str_equal(const uint16_t *a, size_t alen,
|
||||
const uint16_t *b, size_t blen);
|
||||
char *uefi_ucs2_to_ascii(const uint16_t *ucs2, uint64_t ucs2_size);
|
||||
int uefi_time_compare(efi_time *a, efi_time *b);
|
||||
void uefi_trace_variable(const char *action, QemuUUID guid,
|
||||
const uint16_t *name, uint64_t name_size);
|
||||
void uefi_trace_status(const char *action, efi_status status);
|
||||
|
||||
/* vars-service-core.c */
|
||||
extern const VMStateDescription vmstate_uefi_vars;
|
||||
void uefi_vars_init(Object *obj, uefi_vars_state *uv);
|
||||
void uefi_vars_realize(uefi_vars_state *uv, Error **errp);
|
||||
void uefi_vars_hard_reset(uefi_vars_state *uv);
|
||||
|
||||
/* vars-service-json.c */
|
||||
void uefi_vars_json_init(uefi_vars_state *uv, Error **errp);
|
||||
void uefi_vars_json_save(uefi_vars_state *uv);
|
||||
void uefi_vars_json_load(uefi_vars_state *uv, Error **errp);
|
||||
|
||||
/* vars-service-vars.c */
|
||||
extern const VMStateDescription vmstate_uefi_variable;
|
||||
uefi_variable *uefi_vars_find_variable(uefi_vars_state *uv, QemuUUID guid,
|
||||
const uint16_t *name,
|
||||
uint64_t name_size);
|
||||
void uefi_vars_set_variable(uefi_vars_state *uv, QemuUUID guid,
|
||||
const uint16_t *name, uint64_t name_size,
|
||||
uint32_t attributes,
|
||||
void *data, uint64_t data_size);
|
||||
void uefi_vars_clear_volatile(uefi_vars_state *uv);
|
||||
void uefi_vars_clear_all(uefi_vars_state *uv);
|
||||
void uefi_vars_update_storage(uefi_vars_state *uv);
|
||||
uint32_t uefi_vars_mm_vars_proto(uefi_vars_state *uv);
|
||||
|
||||
/* vars-service-auth.c */
|
||||
bool uefi_vars_is_sb_pk(uefi_variable *var);
|
||||
bool uefi_vars_is_sb_any(uefi_variable *var);
|
||||
efi_status uefi_vars_check_auth_2(uefi_vars_state *uv, uefi_variable *var,
|
||||
mm_variable_access *va, void *data);
|
||||
efi_status uefi_vars_check_secure_boot(uefi_vars_state *uv, uefi_variable *var);
|
||||
void uefi_vars_auth_init(uefi_vars_state *uv);
|
||||
|
||||
/* vars-service-pkcs7.c */
|
||||
efi_status uefi_vars_check_pkcs7_2(uefi_variable *siglist,
|
||||
void **digest, uint32_t *digest_size,
|
||||
mm_variable_access *va, void *data);
|
||||
|
||||
/* vars-service-siglist.c */
|
||||
void uefi_vars_siglist_init(uefi_vars_siglist *siglist);
|
||||
void uefi_vars_siglist_free(uefi_vars_siglist *siglist);
|
||||
void uefi_vars_siglist_parse(uefi_vars_siglist *siglist,
|
||||
void *data, uint64_t size);
|
||||
uint64_t uefi_vars_siglist_blob_size(uefi_vars_siglist *siglist);
|
||||
void uefi_vars_siglist_blob_generate(uefi_vars_siglist *siglist,
|
||||
void *data, uint64_t size);
|
||||
|
||||
/* vars-service-policy.c */
|
||||
extern const VMStateDescription vmstate_uefi_var_policy;
|
||||
efi_status uefi_vars_policy_check(uefi_vars_state *uv,
|
||||
uefi_variable *var,
|
||||
gboolean is_newvar);
|
||||
void uefi_vars_policies_clear(uefi_vars_state *uv);
|
||||
uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv,
|
||||
variable_policy_entry *pe);
|
||||
uint32_t uefi_vars_mm_check_policy_proto(uefi_vars_state *uv);
|
||||
|
||||
#endif /* QEMU_UEFI_VAR_SERVICE_H */
|
|
@ -3601,6 +3601,7 @@ if have_system
|
|||
'hw/ssi',
|
||||
'hw/timer',
|
||||
'hw/tpm',
|
||||
'hw/uefi',
|
||||
'hw/ufs',
|
||||
'hw/usb',
|
||||
'hw/vfio',
|
||||
|
|
|
@ -65,6 +65,7 @@ if have_system
|
|||
'pci',
|
||||
'rocker',
|
||||
'tpm',
|
||||
'uefi',
|
||||
]
|
||||
endif
|
||||
if have_system or have_tools
|
||||
|
|
|
@ -81,3 +81,4 @@
|
|||
{ 'include': 'vfio.json' }
|
||||
{ 'include': 'cryptodev.json' }
|
||||
{ 'include': 'cxl.json' }
|
||||
{ 'include': 'uefi.json' }
|
||||
|
|
64
qapi/uefi.json
Normal file
64
qapi/uefi.json
Normal file
|
@ -0,0 +1,64 @@
|
|||
# -*- Mode: Python -*-
|
||||
# vim: filetype=python
|
||||
#
|
||||
|
||||
##
|
||||
# = UEFI Variable Store
|
||||
#
|
||||
# The qemu efi variable store implementation (hw/uefi/) uses this to
|
||||
# store non-volatile variables in json format on disk.
|
||||
#
|
||||
# This is an existing format already supported by (at least) two other
|
||||
# projects, specifically https://gitlab.com/kraxel/virt-firmware and
|
||||
# https://github.com/awslabs/python-uefivars.
|
||||
##
|
||||
|
||||
##
|
||||
# @UefiVariable:
|
||||
#
|
||||
# UEFI Variable. Check the UEFI specifification for more detailed
|
||||
# information on the fields.
|
||||
#
|
||||
# @guid: variable namespace GUID
|
||||
#
|
||||
# @name: variable name, in UTF-8 encoding.
|
||||
#
|
||||
# @attr: variable attributes.
|
||||
#
|
||||
# @data: variable value, encoded as hex string.
|
||||
#
|
||||
# @time: variable modification time. EFI_TIME struct, encoded as hex
|
||||
# string. Used only for authenticated variables, where the
|
||||
# EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute bit
|
||||
# is set.
|
||||
#
|
||||
# @digest: variable certificate digest. Used to verify the signature
|
||||
# of updates for authenticated variables. UEFI has two kinds of
|
||||
# authenticated variables. The secure boot variables ('PK',
|
||||
# 'KEK', 'db' and 'dbx') have hard coded signature checking rules.
|
||||
# For other authenticated variables the firmware stores a digest
|
||||
# of the signing certificate at variable creation time, and any
|
||||
# updates must be signed with the same certificate.
|
||||
#
|
||||
# Since: 10.0
|
||||
##
|
||||
{ 'struct' : 'UefiVariable',
|
||||
'data' : { 'guid' : 'str',
|
||||
'name' : 'str',
|
||||
'attr' : 'int',
|
||||
'data' : 'str',
|
||||
'*time' : 'str',
|
||||
'*digest' : 'str'}}
|
||||
|
||||
##
|
||||
# @UefiVarStore:
|
||||
#
|
||||
# @version: currently always 2
|
||||
#
|
||||
# @variables: list of UEFI variables
|
||||
#
|
||||
# Since: 10.0
|
||||
##
|
||||
{ 'struct' : 'UefiVarStore',
|
||||
'data' : { 'version' : 'int',
|
||||
'variables' : [ 'UefiVariable' ] }}
|
Loading…
Add table
Add a link
Reference in a new issue