@samitouri / QOSamiQemu / commits / af777e817a

parallels: read header/BAT table in bounded chunks

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 <den@openvz.org> CC: Thomas Huth <thuth@redhat.com> CC: Stefan Hajnoczi <stefanha@redhat.com>

Denis V. Lunev committed Jul 22, 2026 at 18:54 UTC af777e817a7c16d4eaf02989e838e8e0aaf173c5
3 files changed +47 -4
block/parallels.c
+13 -4
@@ -52,6 +52,7 @@
52 #define HEADER_VERSION 2
53 #define HEADER_INUSE_MAGIC (0x746F6E59)
54 #define MAX_PARALLELS_IMAGE_FACTOR (1ull << 32)
55 +#define PARALLELS_HEADER_READ_CHUNK (64 * 1024 * 1024)
56
57 static QEnumLookup prealloc_mode_lookup = {
58 .array = (const char *const[]) {
@@ -1241,7 +1242,7 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
1242 BDRVParallelsState *s = bs->opaque;
1243 ParallelsHeader ph;
1244 int ret, i;
1244 - uint32_t size;
1245 + uint32_t size, header_off;
1246 int64_t file_nb_sectors, sector;
1247 uint32_t data_start;
1248 bool need_check = false;
@@ -1311,9 +1312,17 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
1312 return -ENOMEM;
1313 }
1314
1314 - ret = bdrv_pread(bs->file, 0, s->header_size, s->header, 0);
1315 - if (ret < 0) {
1316 - goto fail;
1315 + /* A single request s->header_size large exceeds BDRV_REQUEST_MAX_BYTES. */
1316 + for (header_off = 0; header_off < s->header_size;
1317 + header_off += PARALLELS_HEADER_READ_CHUNK) {
1318 + uint32_t chunk = MIN(s->header_size - header_off,
1319 + PARALLELS_HEADER_READ_CHUNK);
1320 +
1321 + ret = bdrv_pread(bs->file, header_off, chunk,
1322 + (uint8_t *)s->header + header_off, 0);
1323 + if (ret < 0) {
1324 + goto fail;
1325 + }
1326 }
1327 s->bat_bitmap = (uint32_t *)(s->header + 1);
1328
tests/qemu-iotests/tests/parallels-checks
+23
@@ -44,6 +44,7 @@ _supported_os Linux
44 SIZE=$((4 * 1024 * 1024))
45 IMGFMT=parallels
46 CLUSTER_SIZE_OFFSET=28
47 +BAT_ENTRIES_OFFSET=32
48 DATA_OFF_OFFSET=48
49 BAT_OFFSET=64
50
@@ -199,6 +200,28 @@ _check_test_img -r all
200 echo "== check first cluster =="
201 { $QEMU_IO -r -c "read -P 0x55 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
202
203 +# Clear image
204 +_make_test_img $SIZE
205 +
206 +echo "== TEST HUGE BAT TABLE OPEN =="
207 +
208 +# Overflows a single read request, but stays under parallels_open()'s
209 +# own catalog-size cap.
210 +BAT_ENTRIES=536870896
211 +HEADER_SIZE=$((64 + 4 * BAT_ENTRIES))
212 +
213 +echo "== advertise a BAT table larger than BDRV_REQUEST_MAX_BYTES =="
214 +poke_file "$TEST_IMG" "$BAT_ENTRIES_OFFSET" "\xf0\xff\xff\x1f"
215 +
216 +echo "== grow the file to match, without writing real data =="
217 +truncate -s $HEADER_SIZE "$TEST_IMG"
218 +
219 +echo "== open must succeed: the header/BAT read is chunked =="
220 +_img_info
221 +
222 +echo "== an unallocated cluster still reads as zeroes =="
223 +{ $QEMU_IO -r -c "read -P 0x00 0 $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
224 +
225 # success, all done
226 echo "*** done"
227 rm -f $seq.full
tests/qemu-iotests/tests/parallels-checks.out
+11
@@ -129,4 +129,15 @@ No errors were found on the image.
129 == check first cluster ==
130 read 1048576/1048576 bytes at offset 0
131 1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
132 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
133 +== TEST HUGE BAT TABLE OPEN ==
134 +== advertise a BAT table larger than BDRV_REQUEST_MAX_BYTES ==
135 +== grow the file to match, without writing real data ==
136 +== open must succeed: the header/BAT read is chunked ==
137 +image: TEST_DIR/t.IMGFMT
138 +file format: IMGFMT
139 +virtual size: 4 MiB (4194304 bytes)
140 +== an unallocated cluster still reads as zeroes ==
141 +read 1048576/1048576 bytes at offset 0
142 +1 MiB, X ops; XX:XX:XX.X (XXX YYY/sec and XXX ops/sec)
143 *** done