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;