@samitouri / QOSamiQemu / commits / e8a28c2e0e

parallels: validate BAT capacity against advertised disk size

parallels_open() copied nb_sectors, tracks, and bat_entries from the image header without checking that the BAT actually covers the advertised virtual disk size. An image whose header claims more sectors than its BAT covers passes the generic block-layer bounds check on open. A write into the gap between BAT coverage and the advertised size then reaches allocate_clusters(), whose internal assert(idx < s->bat_size && idx + to_allocate <= s->bat_size) aborts the process instead of returning a normal I/O error. Reject such images at open time by requiring bat_size * tracks >= total_sectors, matching the invariant that allocate_clusters() already assumes. Reported-by: Feifan Qian <bea1e@proton.me> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3804 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 e8a28c2e0e1faf20713938f549fa4c9d9a94cebb
3 files changed +34
block/parallels.c
+6
@@ -1328,6 +1328,12 @@ static int parallels_open(BlockDriverState *bs, QDict *options, int flags,
1328 return -EFBIG;
1329 }
1330
1331 + if ((uint64_t)s->bat_size * s->tracks < bs->total_sectors) {
1332 + error_setg(errp, "Invalid image: Catalog size too small for "
1333 + "advertised disk size");
1334 + return -EINVAL;
1335 + }
1336 +
1337 size = bat_entry_off(s->bat_size);
1338 s->header_size = ROUND_UP(size, bdrv_opt_mem_align(bs->file->bs));
1339 s->header = qemu_try_blockalign(bs->file->bs, s->header_size);
tests/qemu-iotests/tests/parallels-checks
+21
@@ -225,6 +225,27 @@ echo "== an unallocated cluster still reads as zeroes =="
225 # Clear image
226 _make_test_img $SIZE
227
228 +echo "== TEST OVERSIZED VIRTUAL DISK CHECK =="
229 +
230 +BAT_ENTRIES_OFFSET=32
231 +NB_SECTORS_OFFSET=36
232 +
233 +TRACKS=$(peek_file_le $TEST_IMG $CLUSTER_SIZE_OFFSET 4)
234 +BAT_ENTRIES=$(peek_file_le $TEST_IMG $BAT_ENTRIES_OFFSET 4)
235 +COVERED_SECTORS=$((BAT_ENTRIES * TRACKS))
236 +
237 +echo "== advertise one more cluster than the BAT covers =="
238 +poke_file_le "$TEST_IMG" $NB_SECTORS_OFFSET 8 $((COVERED_SECTORS + TRACKS))
239 +
240 +echo "== open must fail cleanly instead of aborting =="
241 +_img_info
242 +
243 +echo "== write into the uncovered range must fail cleanly too =="
244 +{ $QEMU_IO -c "write -P 0x41 $((COVERED_SECTORS * 512)) $CLUSTER_SIZE" "$TEST_IMG"; } 2>&1 | _filter_qemu_io | _filter_testdir
245 +
246 +# Clear image
247 +_make_test_img $SIZE
248 +
249 echo "== TEST BAT ENTRY POINTING OUTSIDE IMAGE =="
250
251 echo "== corrupt image: point first cluster far outside the file =="
tests/qemu-iotests/tests/parallels-checks.out
+7
@@ -141,6 +141,13 @@ virtual size: 4 MiB (4194304 bytes)
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 Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
144 +== TEST OVERSIZED VIRTUAL DISK CHECK ==
145 +== advertise one more cluster than the BAT covers ==
146 +== open must fail cleanly instead of aborting ==
147 +qemu-img: Could not open 'TEST_DIR/t.IMGFMT': Invalid image: Catalog size too small for advertised disk size
148 +== write into the uncovered range must fail cleanly too ==
149 +qemu-io: can't open device TEST_DIR/t.parallels: Invalid image: Catalog size too small for advertised disk size
150 +Formatting 'TEST_DIR/t.IMGFMT', fmt=IMGFMT size=4194304
151 == TEST BAT ENTRY POINTING OUTSIDE IMAGE ==
152 == corrupt image: point first cluster far outside the file ==
153 == read-only read must return zeroes, not an I/O error ==