Add sanity checks to uefi-vars state loaded from live migration data stream. Fail migration if invalid data or inconsistencies are found.
Fixes: CVE-2026-61404 Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3837 Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3838 Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3839 Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3885 Signed-off-by: Gerd Hoffmann <[email protected]> --- hw/uefi/var-service-core.c | 14 +++++++++++++- hw/uefi/var-service-json.c | 5 +++++ hw/uefi/var-service-policy.c | 28 +++++++++++++++++++++++----- hw/uefi/var-service-vars.c | 15 +++++++++++++++ 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/hw/uefi/var-service-core.c b/hw/uefi/var-service-core.c index 828d76007318..acecf7a8a9b1 100644 --- a/hw/uefi/var-service-core.c +++ b/hw/uefi/var-service-core.c @@ -5,6 +5,7 @@ */ #include "qemu/osdep.h" #include "qemu/crc32c.h" +#include "qemu/error-report.h" #include "system/dma.h" #include "migration/vmstate.h" @@ -28,9 +29,20 @@ static int uefi_vars_post_load(void *opaque, int version_id) { uefi_vars_state *uv = opaque; + if (uv->buf_size > MAX_BUFFER_SIZE) { + error_report("invalid buffer size"); + return -1; + } + uv->buffer = g_malloc(uv->buf_size); + uefi_vars_update_storage(uv); + if (uv->used_storage > uv->max_storage) { + error_report("out of variable memory (%ld > %ld)", + uv->used_storage, uv->max_storage); + return -1; + } + uefi_vars_json_save(uv); - uv->buffer = g_malloc(uv->buf_size); return 0; } diff --git a/hw/uefi/var-service-json.c b/hw/uefi/var-service-json.c index 8621b86c5c5f..f90deef79b76 100644 --- a/hw/uefi/var-service-json.c +++ b/hw/uefi/var-service-json.c @@ -18,6 +18,7 @@ #include "qobject/qobject.h" #include "qobject/qjson.h" +#include "qapi/error.h" #include "qapi/dealloc-visitor.h" #include "qapi/qobject-input-visitor.h" #include "qapi/qobject-output-visitor.h" @@ -249,6 +250,10 @@ void uefi_vars_json_load(uefi_vars_state *uv, Error **errp) if (!(*errp)) { uefi_vars_from_qapi(uv, vs); uefi_vars_update_storage(uv); + if (uv->used_storage > uv->max_storage) { + error_setg(errp, "out of variable memory (%ld > %ld)", + uv->used_storage, uv->max_storage); + } } qapi_free_UefiVarStore(vs); diff --git a/hw/uefi/var-service-policy.c b/hw/uefi/var-service-policy.c index 46c4ff2dacae..d43cc9b2500b 100644 --- a/hw/uefi/var-service-policy.c +++ b/hw/uefi/var-service-policy.c @@ -7,6 +7,7 @@ * https://github.com/tianocore/edk2/blob/master/MdeModulePkg/Library/VariablePolicyLib/ReadMe.md */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "system/dma.h" #include "migration/vmstate.h" @@ -16,14 +17,18 @@ #include "trace.h" -static void calc_policy(uefi_var_policy *pol); +static int check_calc_policy(uefi_var_policy *pol); static int uefi_var_policy_post_load(void *opaque, int version_id) { uefi_var_policy *pol = opaque; + int rc; - calc_policy(pol); - return 0; + rc = check_calc_policy(pol); + if (rc != 0) { + error_report("invalid uefi variable policy"); + } + return rc; } const VMStateDescription vmstate_uefi_var_policy = { @@ -80,32 +85,45 @@ static uefi_var_policy *wildcard_find_policy(uefi_vars_state *uv, return NULL; } -static void calc_policy(uefi_var_policy *pol) +static int check_calc_policy(uefi_var_policy *pol) { variable_policy_entry *pe = pol->entry; unsigned int i; + if (pol->entry_size != pe->size || + pe->offset_to_name >= pe->size) { + return -1; + } + pol->name = (void *)pol->entry + pe->offset_to_name; pol->name_size = pe->size - pe->offset_to_name; + if (!uefi_str_is_valid(pol->name, pol->name_size, false)) { + return -1; + } + for (i = 0; i < pol->name_size / 2; i++) { if (pol->name[i] == '#') { pol->hashmarks++; } } + + return 0; } uefi_var_policy *uefi_vars_add_policy(uefi_vars_state *uv, variable_policy_entry *pe) { uefi_var_policy *pol, *p; + int rc; 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); + rc = check_calc_policy(pol); + g_assert(rc == 0); /* keep list sorted by priority, add to tail of priority group */ QTAILQ_FOREACH(p, &uv->var_policies, next) { diff --git a/hw/uefi/var-service-vars.c b/hw/uefi/var-service-vars.c index fd07782f1e0d..e0ea3d532eb6 100644 --- a/hw/uefi/var-service-vars.c +++ b/hw/uefi/var-service-vars.c @@ -47,6 +47,20 @@ static int uefi_vars_pre_load(void *opaque) return 0; } +static int uefi_vars_post_load(void *opaque, int version_id) +{ + uefi_variable *var = opaque; + + if (!uefi_str_is_valid(var->name, var->name_size, true) || + var->attributes & ~EFI_VARIABLE_ATTRIBUTE_SUPPORTED || + (var->digest_size != 0 && + var->digest_size != 32 /* AUTHVAR_DIGEST_SIZE */)) { + error_report("invalid uefi variable"); + return -1; + } + return 0; +} + static bool uefi_vars_digest_is_needed(void *opaque) { uefi_variable *var = opaque; @@ -72,6 +86,7 @@ const VMStateDescription vmstate_uefi_variable_digest = { const VMStateDescription vmstate_uefi_variable = { .name = "uefi-variable", .pre_load = uefi_vars_pre_load, + .post_load = uefi_vars_post_load, .fields = (VMStateField[]) { VMSTATE_UINT8_ARRAY_V(guid.data, uefi_variable, sizeof(QemuUUID), 0), VMSTATE_UINT32(name_size, uefi_variable), -- 2.55.0
