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 <[email protected]> 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 <[email protected]> --- block/vhdx.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/vhdx.c b/block/vhdx.c index c16e4a00c8d..8013a90a1a4 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; -- 2.56.0
