@samitouri / QOSamiQemu / commits / 3a1e0618ae

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>

Denis V. Lunev committed Jul 22, 2026 at 18:54 UTC 3a1e0618ae3df56128a42f98b6168a4c85598466
3 files changed +31 -10
block/parallels.c
+17 -6
@@ -999,7 +999,8 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
999 BlockdevCreateOptionsParallels *parallels_opts;
1000 BlockDriverState *bs;
1001 BlockBackend *blk;
1002 - int64_t total_size, cl_size;
1002 + int64_t total_size, cl_size, bat_count;
1003 + uint64_t cylinders;
1004 uint32_t bat_entries, bat_sectors;
1005 ParallelsHeader header;
1006 uint8_t tmp[BDRV_SECTOR_SIZE];
@@ -1017,16 +1018,22 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
1018 cl_size = DEFAULT_CLUSTER_SIZE;
1019 }
1020
1020 - /* XXX What is the real limit here? This is an insanely large maximum. */
1021 + /* Bounds cl_size so the multiplication below can't overflow int64_t. */
1022 if (cl_size >= INT64_MAX / MAX_PARALLELS_IMAGE_FACTOR) {
1023 error_setg(errp, "Cluster size is too large");
1024 return -EINVAL;
1025 }
1025 - if (total_size >= MAX_PARALLELS_IMAGE_FACTOR * cl_size) {
1026 + if (cl_size <= 0 || total_size >= MAX_PARALLELS_IMAGE_FACTOR * cl_size) {
1027 error_setg(errp, "Image size is too large for this cluster size");
1028 return -E2BIG;
1029 }
1030
1031 + bat_count = DIV_ROUND_UP(total_size, cl_size);
1032 + if (bat_count > INT_MAX / (int64_t)sizeof(uint32_t)) {
1033 + error_setg(errp, "Catalog too large");
1034 + return -EFBIG;
1035 + }
1036 +
1037 if (!QEMU_IS_ALIGNED(total_size, BDRV_SECTOR_SIZE)) {
1038 error_setg(errp, "Image size must be a multiple of 512 bytes");
1039 return -EINVAL;
@@ -1052,7 +1059,7 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
1059 blk_set_allow_write_beyond_eof(blk, true);
1060
1061 /* Create image format */
1055 - bat_entries = DIV_ROUND_UP(total_size, cl_size);
1062 + bat_entries = bat_count;
1063 bat_sectors = DIV_ROUND_UP(bat_entry_off(bat_entries), cl_size);
1064 bat_sectors = (bat_sectors * cl_size) >> BDRV_SECTOR_BITS;
1065
@@ -1061,8 +1068,12 @@ parallels_co_create(BlockdevCreateOptions* opts, Error **errp)
1068 header.version = cpu_to_le32(HEADER_VERSION);
1069 /* don't care much about geometry, it is not used on image level */
1070 header.heads = cpu_to_le32(HEADS_NUMBER);
1064 - header.cylinders = cpu_to_le32(total_size / BDRV_SECTOR_SIZE
1065 - / HEADS_NUMBER / SEC_IN_CYL);
1071 + cylinders = total_size / BDRV_SECTOR_SIZE / HEADS_NUMBER / SEC_IN_CYL;
1072 + /* Write only by spec, do not care */
1073 + if (cylinders >= UINT32_MAX) {
1074 + cylinders = UINT32_MAX;
1075 + }
1076 + header.cylinders = cpu_to_le32(cylinders);
1077 header.tracks = cpu_to_le32(cl_size >> BDRV_SECTOR_BITS);
1078 header.bat_entries = cpu_to_le32(bat_entries);
1079 header.nb_sectors = cpu_to_le64(DIV_ROUND_UP(total_size, BDRV_SECTOR_SIZE));
tests/qemu-iotests/212
+6 -2
@@ -133,13 +133,15 @@ with iotests.FilePath('t.parallels') as disk_path, \
133 #
134 # Maximum size
135 #
136 + # Largest catalog parallels_open() can address.
137 + #
138 iotests.log("=== Maximum size ===")
139 iotests.log("")
140
141 vm.launch()
142 vm.blockdev_create({ 'driver': imgfmt,
143 'file': 'node0',
142 - 'size': 4503599627369984})
144 + 'size': 562949952372736})
145 vm.shutdown()
146
147 iotests.img_info_log(disk_path)
@@ -158,13 +160,15 @@ with iotests.FilePath('t.parallels') as disk_path, \
160 # 4. 2^63 - 512 (generally valid, but with the image header the file will
161 # exceed 63 bits)
162 # 5. 2^52 (512 bytes more than maximum image size)
163 + # 6. 2^52 - 512 (wraps bat_entries to 0 at the default 1 MiB cluster size)
164
165 iotests.log("=== Invalid sizes ===")
166 iotests.log("")
167
168 vm.launch()
169 for size in [ 1234, 18446744073709551104, 9223372036854775808,
167 - 9223372036854775296, 4503599627370497 ]:
170 + 9223372036854775296, 4503599627370497,
171 + 4503599627369984 ]:
172 vm.blockdev_create({ 'driver': imgfmt,
173 'file': 'node0',
174 'size': size })
tests/qemu-iotests/212.out
+8 -2
@@ -69,14 +69,14 @@ virtual size: 0 B (0 bytes)
69
70 === Maximum size ===
71
72 -{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "node0", "size": 4503599627369984}}}
72 +{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "node0", "size": 562949952372736}}}
73 {"return": {}}
74 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
75 {"return": {}}
76
77 image: TEST_IMG
78 file format: IMGFMT
79 -virtual size: 4 PiB (4503599627369984 bytes)
79 +virtual size: 512 TiB (562949952372736 bytes)
80
81 === Invalid sizes ===
82
@@ -110,6 +110,12 @@ Job failed: Image size is too large for this cluster size
110 {"execute": "job-dismiss", "arguments": {"id": "job0"}}
111 {"return": {}}
112
113 +{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "parallels", "file": "node0", "size": 4503599627369984}}}
114 +{"return": {}}
115 +Job failed: Catalog too large
116 +{"execute": "job-dismiss", "arguments": {"id": "job0"}}
117 +{"return": {}}
118 +
119 === Invalid cluster size ===
120
121 {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"cluster-size": 1234, "driver": "parallels", "file": "node0", "size": 67108864}}}