Commit d9275e776e for qemu.org
commit d9275e776e95ca04bd47bdf519cf1b1d9101b0ce
Author: Luigi Leonardi <leonardi@redhat.com>
Date: Wed Sep 16 17:10:05 2026 +0200
igvm: validate and honor byte_offset in parameter directives
Parameter directive handlers either ignore param->byte_offset,
overwriting each other when several parameters share one area, or use
it without validating it, letting a malformed IGVM file corrupt QEMU's
memory.
Add qigvm_get_param_data(), which validates byte_offset against the
parameter area size and returns the offset-adjusted pointer together
with the space left after it. Convert all parameter directive handlers
to use it and to check their writes against that remaining space, so
that no parameter can be written past the end of its area.
Fixes: c1d466d267 ("backends/igvm: Add IGVM loader and configuration")
Fixes: dea1f68a5c ("igvm: Fill MADT IGVM parameter field on x86_64")
Fixes: 1c4bd8f13c ("igvm: add device tree parameter support")
Signed-off-by: Luigi Leonardi <leonardi@redhat.com>
Reviewed-by: Stefano Garzarella <sgarzare@redhat.com>
Message-ID: <20260916-fix_offset-v5-1-11cf8ef37854@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
diff --git a/backends/igvm.c b/backends/igvm.c
index 7b7bdc72b7..9a2bafc0ca 100644
--- a/backends/igvm.c
+++ b/backends/igvm.c
@@ -101,6 +101,38 @@ qigvm_find_param_entry(QIgvm *igvm, uint32_t parameter_area_index,
return NULL;
}
+/*
+ * Get parameter area data at byte_offset with bounds validation.
+ * On success, returns offset-adjusted data pointer and sets param_size
+ * to remaining space.
+ * Returns NULL on failure.
+ */
+uint8_t *
+qigvm_get_param_data(QIgvm *igvm, const IGVM_VHS_PARAMETER *param,
+ uint32_t *param_size,
+ Error **errp)
+{
+ QIgvmParameterData *param_entry;
+
+ assert(param_size);
+
+ param_entry = qigvm_find_param_entry(igvm, param->parameter_area_index,
+ errp);
+ if (!param_entry) {
+ return NULL;
+ }
+
+ if (param->byte_offset > param_entry->size) {
+ error_setg(errp,
+ "IGVM: byte_offset 0x%x exceeds parameter area size 0x%x",
+ param->byte_offset, param_entry->size);
+ return NULL;
+ }
+
+ *param_size = param_entry->size - param->byte_offset;
+ return param_entry->data + param->byte_offset;
+}
+
static int qigvm_directive_page_data(QIgvm *ctx, const uint8_t *header_data,
Error **errp);
static int qigvm_directive_vp_context(QIgvm *ctx, const uint8_t *header_data,
@@ -605,7 +637,7 @@ static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data,
const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data;
int (*get_mem_map_entry)(int index, ConfidentialGuestMemoryMapEntry *entry,
Error **errp) = NULL;
- QIgvmParameterData *param_entry;
+ uint32_t param_size;
int max_entry_count;
int entry = 0;
IGVM_VHS_MEMORY_MAP_ENTRY *mm_entry;
@@ -626,14 +658,14 @@ static int qigvm_directive_memory_map(QIgvm *ctx, const uint8_t *header_data,
}
/* Find the parameter area that should hold the memory map */
- param_entry = qigvm_find_param_entry(ctx,
- param->parameter_area_index, errp);
- if (param_entry == NULL) {
+ mm_entry =
+ (IGVM_VHS_MEMORY_MAP_ENTRY *)qigvm_get_param_data(ctx, param,
+ ¶m_size, errp);
+ if (!mm_entry) {
return -1;
}
- max_entry_count = param_entry->size / sizeof(IGVM_VHS_MEMORY_MAP_ENTRY);
- mm_entry = (IGVM_VHS_MEMORY_MAP_ENTRY *)param_entry->data;
+ max_entry_count = param_size / sizeof(IGVM_VHS_MEMORY_MAP_ENTRY);
retval = get_mem_map_entry(entry, &cgmm_entry, errp);
while (retval == 0) {
@@ -682,17 +714,22 @@ static int qigvm_directive_vp_count(QIgvm *ctx, const uint8_t *header_data,
Error **errp)
{
const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data;
- QIgvmParameterData *param_entry;
+ uint32_t param_size;
uint32_t *vp_count;
CPUState *cpu;
- param_entry = qigvm_find_param_entry(ctx,
- param->parameter_area_index, errp);
- if (param_entry == NULL) {
+ vp_count = (uint32_t *)qigvm_get_param_data(ctx, param, ¶m_size, errp);
+ if (!vp_count) {
+ return -1;
+ }
+
+ if (sizeof(*vp_count) > param_size) {
+ error_setg(errp,
+ "IGVM: vp-count parameter exceeds parameter area "
+ "defined in IGVM file");
return -1;
}
- vp_count = (uint32_t *)(param_entry->data + param->byte_offset);
*vp_count = 0;
CPU_FOREACH(cpu)
{
@@ -707,17 +744,23 @@ static int qigvm_directive_environment_info(QIgvm *ctx,
Error **errp)
{
const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data;
- QIgvmParameterData *param_entry;
+ uint32_t param_size;
IgvmEnvironmentInfo *environmental_state;
- param_entry = qigvm_find_param_entry(ctx,
- param->parameter_area_index, errp);
- if (param_entry == NULL) {
+ environmental_state =
+ (IgvmEnvironmentInfo *)qigvm_get_param_data(ctx, param, ¶m_size,
+ errp);
+ if (!environmental_state) {
+ return -1;
+ }
+
+ if (sizeof(*environmental_state) > param_size) {
+ error_setg(errp,
+ "IGVM: environment-info parameter exceeds parameter area "
+ "defined in IGVM file");
return -1;
}
- environmental_state =
- (IgvmEnvironmentInfo *)(param_entry->data + param->byte_offset);
environmental_state->memory_is_shared = 1;
return 0;
@@ -814,12 +857,12 @@ static int qigvm_directive_device_tree(QIgvm *ctx, const uint8_t *header_data,
{
const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data;
g_autofree void *fdt_packed = NULL;
- QIgvmParameterData *param_entry;
+ uint8_t *param_data;
+ uint32_t param_size;
uint32_t fdt_size;
- param_entry = qigvm_find_param_entry(ctx,
- param->parameter_area_index, errp);
- if (param_entry == NULL) {
+ param_data = qigvm_get_param_data(ctx, param, ¶m_size, errp);
+ if (!param_data) {
return -1;
}
@@ -837,14 +880,14 @@ static int qigvm_directive_device_tree(QIgvm *ctx, const uint8_t *header_data,
}
fdt_size = fdt_totalsize(fdt_packed);
- if (fdt_size > param_entry->size) {
+ if (fdt_size > param_size) {
error_setg(errp,
"IGVM: device tree size exceeds parameter area"
" defined in IGVM file");
return -1;
}
- memcpy(param_entry->data, fdt_packed, fdt_size);
+ memcpy(param_data, fdt_packed, fdt_size);
return 0;
}
diff --git a/include/system/igvm-internal.h b/include/system/igvm-internal.h
index 9e9fa1d9af..0467eff0ce 100644
--- a/include/system/igvm-internal.h
+++ b/include/system/igvm-internal.h
@@ -81,4 +81,9 @@ QIgvmParameterData*
qigvm_find_param_entry(QIgvm *igvm, uint32_t parameter_area_index,
Error **errp);
+uint8_t *
+qigvm_get_param_data(QIgvm *igvm, const IGVM_VHS_PARAMETER *param,
+ uint32_t *param_size,
+ Error **errp);
+
#endif
diff --git a/target/i386/igvm.c b/target/i386/igvm.c
index ad9bf87761..9c2f8d5164 100644
--- a/target/i386/igvm.c
+++ b/target/i386/igvm.c
@@ -187,20 +187,20 @@ void qigvm_x86_bsp_reset(CPUX86State *env)
int qigvm_directive_madt(QIgvm *ctx, const uint8_t *header_data, Error **errp)
{
const IGVM_VHS_PARAMETER *param = (const IGVM_VHS_PARAMETER *)header_data;
- QIgvmParameterData *param_entry;
+ uint8_t *param_data;
+ uint32_t param_size;
int result = 0;
/* Find the parameter area that should hold the MADT data */
- param_entry = qigvm_find_param_entry(ctx,
- param->parameter_area_index, errp);
- if (param_entry == NULL) {
+ param_data = qigvm_get_param_data(ctx, param, ¶m_size, errp);
+ if (!param_data) {
return -1;
}
GArray *madt = acpi_build_madt_standalone(ctx->machine_state);
- if (madt->len <= param_entry->size) {
- memcpy(param_entry->data, madt->data, madt->len);
+ if (madt->len <= param_size) {
+ memcpy(param_data, madt->data, madt->len);
} else {
error_setg(
errp,