parallels_open() read the whole header+BAT table with a single bdrv_pread() call sized s->header_size. For an image whose catalog approaches the "Catalog too large" bound (INT_MAX / sizeof(uint32_t) entries), that size approaches BDRV_REQUEST_MAX_BYTES, and the block layer legitimately refuses a single request that large, so the image failed to open with a generic I/O error even though the catalog size itself is within the format's documented limit.
Read the header and BAT table in fixed-size chunks instead, so the maximum catalog size parallels_open() can actually address matches the bound it already enforces, independent of the file's block-layer alignment requirements. Signed-off-by: Denis V. Lunev <[email protected]> CC: Thomas Huth <[email protected]> CC: Stefan Hajnoczi <[email protected]> --- block/parallels.c | 17 ++++++++++---- tests/qemu-iotests/tests/parallels-checks | 23 +++++++++++++++++++ tests/qemu-iotests/tests/parallels-checks.out | 11 +++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/block/parallels.c b/block/parallels.c index 59f00c64a6..0f655b58d5 100644 --- a/block/parallels.c +++ b/block/parallels.c @@ -52,6 +52,7 @@ #define HEADER_VERSION 2 #define HEADER_INUSE_MAGIC (0x746F6E59) #define MAX_PARALLELS_IMAGE_FACTOR (1ull << 32) +#define PARALLELS_HEADER_READ_CHUNK (64 * 1024 * 1024) static QEnumLookup prealloc_mode_lookup = { .array = (const char *const[]) { @@ -1241,7 +1242,7 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags, BDRVParallelsState *s = bs->opaque; ParallelsHeader ph; int ret, i; - uint32_t size; + uint32_t size, header_off; int64_t file_nb_sectors, sector; uint32_t data_start; bool need_check = false; @@ -1311,9 +1312,17 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags, return -ENOMEM; } - ret = bdrv_pread(bs->file, 0, s->header_size, s->header, 0); - if (ret < 0) { - goto fail; + /* A single request s->header_size large exceeds BDRV_REQUEST_MAX_BYTES. */ + for (header_off = 0; header_off < s->header_size; + header_off += PARALLELS_HEADER_READ_CHUNK) { + uint32_t chunk = MIN(s->header_size - header_off, + PARALLELS_HEADER_READ_CHUNK); + + ret = bdrv_pread(bs->file, header_off, chunk, + (uint8_t *)s->header + header_off, 0); + if (ret < 0) { + goto fail; + } } s->bat_bitmap = (uint32_t *)(s->header + 1); diff --git a/tests/qemu-iotests/tests/parallels-checks b/tests/qemu-iotests/tests/parallels-checks index b281246a42..9535024885 100755 --- a/tests/qemu-iotests/tests/parallels-checks +++ b/tests/qemu-iotests/tests/parallels-checks @@ -44,6 +44,7 @@ _supported_os Linux SIZE=$((4 * 1024 * 1024)) IMGFMT=parallels CLUSTER_SIZE_OFFSET=28 +BAT_ENTRIES_OFFSET=32 DATA_OFF_OFFSET=48 BAT_OFFSET=64 @@ -199,6 +200,28 @@ _check_test_img -r all echo "== check first cluster ==" { $QEMU_IO -r -c "read -P 0x55 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir +# Clear image +_make_test_img $SIZE + +echo "== TEST HUGE BAT TABLE OPEN ==" + +# Overflows a single read request, but stays under parallels_open()'s +# own catalog-size cap. +BAT_ENTRIES=536870896 +HEADER_SIZE=$((64 + 4 * BAT_ENTRIES)) + +echo "== advertise a BAT table larger than BDRV_REQUEST_MAX_BYTES ==" +poke_file "$TEST_IMG" "$BAT_ENTRIES_OFFSET" "\xf0\xff\xff\x1f" + +echo "== grow the file to match, without writing real data ==" +truncate -s $HEADER_SIZE "$TEST_IMG" + +echo "== open must succeed: the header/BAT read is chunked ==" +_img_info + +echo "== an unallocated cluster still reads as zeroes ==" +{ $QEMU_IO -r -c "read -P 0x00 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir + # success, all done echo "*** done" rm -f $seq.full diff --git a/tests/qemu-iotests/tests/parallels-checks.out b/tests/qemu-iotests/tests/parallels-checks.out index 9793423111..6fb2014e8e 100644 --- a/tests/qemu-iotests/tests/parallels-checks.out +++ b/tests/qemu-iotests/tests/parallels-checks.out @@ -129,4 +129,15 @@ No errors were found on the image. == check first cluster == read 1048576/1048576 bytes at offset 0 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304 +== TEST HUGE BAT TABLE OPEN == +== advertise a BAT table larger than BDRV_REQUEST_MAX_BYTES == +== grow the file to match, without writing real data == +== open must succeed: the header/BAT read is chunked == +image: TEST_DIR/t.IMGFMT +file format: IMGFMT +virtual size: 4 MiB (4194304 bytes) +== an unallocated cluster still reads as zeroes == +read 1048576/1048576 bytes at offset 0 +1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec) *** done -- 2.53.0
