On Thu, Jul 09, 2026 at 12:42:20PM +0200, Gerd Hoffmann wrote:
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)",

nit: what about replacing %ld with PRIu64?

+                     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 */)) {

is it worth moving that define to a common header and using it here?

+        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




Reply via email to