Commit 3a1e0618ae for qemu.org
commit 3a1e0618ae3df56128a42f98b6168a4c85598466
Author: Denis V. Lunev <den@openvz.org>
Date: Wed Jul 22 18:54:55 2026 +0200
parallels: fix bat_entries overflow in image creation
parallels_co_create() computed the BAT entry count directly into a
uint32_t, wrapping silently to zero at exactly 2^32 entries and
writing out a header whose BAT no longer matches its advertised
size. Compute it in an int64_t first and reject it once it no longer
fits, matching the cap parallels_open() already enforces. Also
reject cluster-size 0, and clamp header.cylinders instead of letting
it truncate the same way.
Signed-off-by: Denis V. Lunev <den@openvz.org>
CC: Thomas Huth <thuth@redhat.com>
CC: Stefan Hajnoczi <stefanha@redhat.com>
diff --git a/block/parallels.c b/block/parallels.c
index 0f655b58d5..e3d26a6650 100644
--- a/block/parallels.c
+++ b/block/parallels.c
@@ -999,7 +999,8 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
BlockdevCreateOptionsParallels *parallels_opts;
BlockDriverState *bs;
BlockBackend *blk;
- int64_t total_size, cl_size;
+ int64_t total_size, cl_size, bat_count;
+ uint64_t cylinders;
uint32_t bat_entries, bat_sectors;
ParallelsHeader header;
uint8_t tmp[BDRV_SECTOR_SIZE];
@@ -1017,16 +1018,22 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
cl_size = DEFAULT_CLUSTER_SIZE;
}
- /* XXX What is the real limit here? This is an insanely large maximum. */
+ /* Bounds cl_size so the multiplication below can't overflow int64_t. */
if (cl_size >= INT64_MAX / MAX_PARALLELS_IMAGE_FACTOR) {
error_setg(errp, "Cluster size is too large");
return -EINVAL;
}
- if (total_size >= MAX_PARALLELS_IMAGE_FACTOR * cl_size) {
+ if (cl_size <= 0 || total_size >= MAX_PARALLELS_IMAGE_FACTOR * cl_size) {
error_setg(errp, "Image size is too large for this cluster size");
return -E2BIG;
}
+ bat_count = DIV_ROUND_UP(total_size, cl_size);
+ if (bat_count > INT_MAX / (int64_t)sizeof(uint32_t)) {
+ error_setg(errp, "Catalog too large");
+ return -EFBIG;
+ }
+
if (!QEMU_IS_ALIGNED(total_size, BDRV_SECTOR_SIZE)) {
error_setg(errp, "Image size must be a multiple of 512 bytes");
return -EINVAL;
@@ -1052,7 +1059,7 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
blk_set_allow_write_beyond_eof(blk, true);
/* Create image format */
- bat_entries = DIV_ROUND_UP(total_size, cl_size);
+ bat_entries = bat_count;
bat_sectors = DIV_ROUND_UP(bat_entry_off(bat_entries), cl_size);
bat_sectors = (bat_sectors * cl_size) >> BDRV_SECTOR_BITS;
@@ -1061,8 +1068,12 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
header.version = cpu_to_le32(HEADER_VERSION);
/* don't care much about geometry, it is not used on image level */
header.heads = cpu_to_le32(HEADS_NUMBER);
- header.cylinders = cpu_to_le32(total_size / BDRV_SECTOR_SIZE
- / HEADS_NUMBER / SEC_IN_CYL);
+ cylinders = total_size / BDRV_SECTOR_SIZE / HEADS_NUMBER / SEC_IN_CYL;
+ /* Write only by spec, do not care */
+ if (cylinders >= UINT32_MAX) {
+ cylinders = UINT32_MAX;
+ }
+ header.cylinders = cpu_to_le32(cylinders);
header.tracks = cpu_to_le32(cl_size >> BDRV_SECTOR_BITS);
header.bat_entries = cpu_to_le32(bat_entries);
header.nb_sectors = cpu_to_le64(DIV_ROUND_UP(total_size, BDRV_SECTOR_SIZE));
diff --git a/tests/qemu-iotests/212 b/tests/qemu-iotests/212
index d4af0c4ac8..4ca6149b6b 100755
--- a/tests/qemu-iotests/212
+++ b/tests/qemu-iotests/212
@@ -133,13 +133,15 @@ with iotests.FilePath('t.parallels') as disk_path, \
#
# Maximum size
#
+ # Largest catalog parallels_open() can address.
+ #
iotests.log("=== Maximum size ===")
iotests.log("")
vm.launch()
vm.blockdev_create({ 'driver': imgfmt,
'file': 'node0',
- 'size': 4503599627369984})
+ 'size': 562949952372736})
vm.shutdown()
iotests.img_info_log(disk_path)
@@ -158,13 +160,15 @@ with iotests.FilePath('t.parallels') as disk_path, \
# 4. 2^63 - 512 (generally valid, but with the image header the file will
# exceed 63 bits)
# 5. 2^52 (512 bytes more than maximum image size)
+ # 6. 2^52 - 512 (wraps bat_entries to 0 at the default 1 MiB cluster size)
iotests.log("=== Invalid sizes ===")
iotests.log("")
vm.launch()
for size in [ 1234, 18446744073709551104, 9223372036854775808,
- 9223372036854775296, 4503599627370497 ]:
+ 9223372036854775296, 4503599627370497,
+ 4503599627369984 ]:
vm.blockdev_create({ 'driver': imgfmt,
'file': 'node0',
'size': size })
diff --git a/tests/qemu-iotests/212.out b/tests/qemu-iotests/212.out
index 8102033488..d59f8ed44b 100644
--- a/tests/qemu-iotests/212.out
+++ b/tests/qemu-iotests/212.out
@@ -69,14 +69,14 @@ virtual size: 0 B (0 bytes)
=== Maximum size ===
-{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "node0", "size": 4503599627369984}}}
+{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "node0", "size": 562949952372736}}}
{"return": {}}
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}
image: TEST_IMG
file format: IMGFMT
-virtual size: 4 PiB (4503599627369984 bytes)
+virtual size: 512 TiB (562949952372736 bytes)
=== Invalid sizes ===
@@ -110,6 +110,12 @@ Job failed: Image size is too large for this cluster size
{"execute": "job-dismiss", "arguments": {"id": "job0"}}
{"return": {}}
+{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "node0", "size": 4503599627369984}}}
+{"return": {}}
+Job failed: Catalog too large
+{"execute": "job-dismiss", "arguments": {"id": "job0"}}
+{"return": {}}
+
=== Invalid cluster size ===
{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"cluster-size": 1234, "driver": "parallels", "file": "node0", "size": 67108864}}}