Commit 67198583a1 for qemu.org
commit 67198583a1ef3432c784f2802c5cda914bc14ca8
Author: Thomas Huth <thuth@redhat.com>
Date: Fri Oct 23 15:00:23 2026 +0200
block/vhdx: Prevent out-of-bounds BAT accesses with malicious vhdx files
virtual_disk_size is read directly from a VHDX image's metadata region
and never checked for sane sizes. The 64 TiB limit (VHDX_MAX_IMAGE_SIZE)
is only enforced in vhdx_co_create(), so it limits images QEMU creates
but never images QEMU opens.
Without the bound, vhdx_calc_bat_entries() computes the block count
into a uint32_t, so a huge image size gets truncated and the BAT is
undersized relative to total_sectors.
vhdx_block_translate() then converts sector numbers into BAT indices
with no bounds check, allowing out-of-bounds access on the BAT array
from both the read and write paths.
To fix the problem, add a VHDX_MAX_IMAGE_SIZE check to vhdx_parse_metadata()
so images opened for reading are subject to the same limit as images
created by QEMU.
With this limit, the calculation in vhdx_calc_bat_entries() cannot get
truncated anymore (block_size is at least 1 MiB):
VHDX_MAX_IMAGE_SIZE / block_size = 64 TiB / 1 MiB = 64 MiB
... and 64 MiB fit nicely into 32 bits.
To be really on the safe side, let's also add an assertion in
vhdx_block_translate() to catch accesses that exceeds the allocated
entry count.
Buglink: https://gitlab.com/qemu-project/qemu/-/work_items/3762
Reported-by: Daniel Wade <danjwade95@gmail.com>
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4470
Reported-by: Mayank Jangid (OpenSec)
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4487
Reported-by: Ken Hsu, Royce Lu, and Bo Qu of Palo Alto Networks
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4638
Reported-by: Thai Duong (Calif.io)
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4733
Reported-by: Xiang Chen
Signed-off-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20261023130023.539252-1-thuth@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
diff --git a/block/vhdx.c b/block/vhdx.c
index c16e4a00c8..8013a90a1a 100644
--- a/block/vhdx.c
+++ b/block/vhdx.c
@@ -815,6 +815,11 @@ vhdx_parse_metadata(BlockDriverState *bs, BDRVVHDXState *s)
}
s->virtual_disk_size = le64_to_cpu(s->virtual_disk_size);
+ if (s->virtual_disk_size > VHDX_MAX_IMAGE_SIZE) {
+ ret = -EFBIG;
+ goto exit;
+ }
+
s->logical_sector_size = le32_to_cpu(s->logical_sector_size);
s->physical_sector_size = le32_to_cpu(s->physical_sector_size);
@@ -1149,6 +1154,7 @@ static void vhdx_block_translate(BDRVVHDXState *s, int64_t sector_num,
sinfo->bytes_avail = sinfo->sectors_avail << s->logical_sector_size_bits;
+ assert(sinfo->bat_idx < s->bat_entries);
sinfo->file_offset = s->bat[sinfo->bat_idx] & VHDX_BAT_FILE_OFF_MASK;
sinfo->block_offset = block_offset << s->logical_sector_size_bits;