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}}}