diff --git a/block.c b/block.c index eb16fb48c6..8f177504d4 100644 --- a/block.c +++ b/block.c @@ -962,6 +962,11 @@ int refresh_total_sectors(BlockDriverState *bs, int64_t hint) } bs->total_sectors = hint; + + if (bs->total_sectors * BDRV_SECTOR_SIZE > BDRV_MAX_LENGTH) { + return -EFBIG; + } + return 0; } @@ -5535,6 +5540,7 @@ void bdrv_get_backing_filename(BlockDriverState *bs, int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) { + int ret; BlockDriver *drv = bs->drv; /* if bs->drv == NULL, bs is closed, so there's nothing to do here */ if (!drv) { @@ -5548,7 +5554,16 @@ int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) return -ENOTSUP; } memset(bdi, 0, sizeof(*bdi)); - return drv->bdrv_get_info(bs, bdi); + ret = drv->bdrv_get_info(bs, bdi); + if (ret < 0) { + return ret; + } + + if (bdi->cluster_size > BDRV_MAX_ALIGNMENT) { + return -EINVAL; + } + + return 0; } ImageInfoSpecific *bdrv_get_specific_info(BlockDriverState *bs, diff --git a/block/file-posix.c b/block/file-posix.c index 9bee3d88d0..83e2cc5530 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -2926,7 +2926,6 @@ raw_do_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int bytes, #ifdef CONFIG_FALLOCATE if (offset + bytes > bs->total_sectors * BDRV_SECTOR_SIZE) { BdrvTrackedRequest *req; - uint64_t end; /* * This is a workaround for a bug in the Linux XFS driver, @@ -2950,8 +2949,9 @@ raw_do_pwrite_zeroes(BlockDriverState *bs, int64_t offset, int bytes, assert(req->offset <= offset); assert(req->offset + req->bytes >= offset + bytes); - end = INT64_MAX & -(uint64_t)bs->bl.request_alignment; - req->bytes = end - req->offset; + req->bytes = BDRV_MAX_LENGTH - req->offset; + + assert(bdrv_check_request(req->offset, req->bytes) == 0); bdrv_mark_request_serialising(req, bs->bl.request_alignment); } diff --git a/block/io.c b/block/io.c index ef75a5abb4..6343d85476 100644 --- a/block/io.c +++ b/block/io.c @@ -176,6 +176,13 @@ void bdrv_refresh_limits(BlockDriverState *bs, Error **errp) /* Then let the driver override it */ if (drv->bdrv_refresh_limits) { drv->bdrv_refresh_limits(bs, errp); + if (*errp) { + return; + } + } + + if (bs->bl.request_alignment > BDRV_MAX_ALIGNMENT) { + error_setg(errp, "Driver requires too large request alignment"); } } @@ -884,13 +891,31 @@ static bool coroutine_fn bdrv_wait_serialising_requests(BdrvTrackedRequest *self return waited; } -static int bdrv_check_byte_request(int64_t offset, size_t size) +int bdrv_check_request(int64_t offset, int64_t bytes) { - if (size > BDRV_REQUEST_MAX_BYTES) { + if (offset < 0 || bytes < 0) { return -EIO; } - if (offset < 0) { + if (bytes > BDRV_MAX_LENGTH) { + return -EIO; + } + + if (offset > BDRV_MAX_LENGTH - bytes) { + return -EIO; + } + + return 0; +} + +static int bdrv_check_request32(int64_t offset, int64_t bytes) +{ + int ret = bdrv_check_request(offset, bytes); + if (ret < 0) { + return ret; + } + + if (bytes > BDRV_REQUEST_MAX_BYTES) { return -EIO; } @@ -1641,7 +1666,7 @@ int coroutine_fn bdrv_co_preadv_part(BdrvChild *child, return -ENOMEDIUM; } - ret = bdrv_check_byte_request(offset, bytes); + ret = bdrv_check_request32(offset, bytes); if (ret < 0) { return ret; } @@ -2057,7 +2082,7 @@ int coroutine_fn bdrv_co_pwritev_part(BdrvChild *child, return -ENOMEDIUM; } - ret = bdrv_check_byte_request(offset, bytes); + ret = bdrv_check_request32(offset, bytes); if (ret < 0) { return ret; } @@ -2787,8 +2812,9 @@ int coroutine_fn bdrv_co_pdiscard(BdrvChild *child, int64_t offset, return -EPERM; } - if (offset < 0 || bytes < 0 || bytes > INT64_MAX - offset) { - return -EIO; + ret = bdrv_check_request(offset, bytes); + if (ret < 0) { + return ret; } /* Do nothing if disabled. */ @@ -3047,7 +3073,7 @@ static int coroutine_fn bdrv_co_copy_range_internal( if (!dst || !dst->bs || !bdrv_is_inserted(dst->bs)) { return -ENOMEDIUM; } - ret = bdrv_check_byte_request(dst_offset, bytes); + ret = bdrv_check_request32(dst_offset, bytes); if (ret) { return ret; } @@ -3058,7 +3084,7 @@ static int coroutine_fn bdrv_co_copy_range_internal( if (!src || !src->bs || !bdrv_is_inserted(src->bs)) { return -ENOMEDIUM; } - ret = bdrv_check_byte_request(src_offset, bytes); + ret = bdrv_check_request32(src_offset, bytes); if (ret) { return ret; } @@ -3188,6 +3214,13 @@ int coroutine_fn bdrv_co_truncate(BdrvChild *child, int64_t offset, bool exact, return -EINVAL; } + ret = bdrv_check_request(offset, 0); + if (ret < 0) { + error_setg(errp, "Required too big image size, it must be not greater " + "than %" PRId64, BDRV_MAX_LENGTH); + return ret; + } + old_size = bdrv_getlength(bs); if (old_size < 0) { error_setg_errno(errp, -old_size, "Failed to get old image size"); diff --git a/include/block/block.h b/include/block/block.h index c9d7c58765..5b81e33e94 100644 --- a/include/block/block.h +++ b/include/block/block.h @@ -142,6 +142,16 @@ typedef struct HDGeometry { INT_MAX >> BDRV_SECTOR_BITS) #define BDRV_REQUEST_MAX_BYTES (BDRV_REQUEST_MAX_SECTORS << BDRV_SECTOR_BITS) +/* + * We want allow aligning requests and disk length up to any 32bit alignment + * and don't afraid of overflow. + * To achieve it, and in the same time use some pretty number as maximum disk + * size, let's define maximum "length" (a limit for any offset/bytes request and + * for disk size) to be the greatest power of 2 less than INT64_MAX. + */ +#define BDRV_MAX_ALIGNMENT (1L << 30) +#define BDRV_MAX_LENGTH (QEMU_ALIGN_DOWN(INT64_MAX, BDRV_MAX_ALIGNMENT)) + /* * Allocation status flags for bdrv_block_status() and friends. * diff --git a/include/block/block_int.h b/include/block/block_int.h index 95d9333be1..1eeafc118c 100644 --- a/include/block/block_int.h +++ b/include/block/block_int.h @@ -70,6 +70,12 @@ enum BdrvTrackedRequestType { BDRV_TRACKED_TRUNCATE, }; +/* + * That is not quite good that BdrvTrackedRequest structure is public, + * as block/io.c is very careful about incoming offset/bytes being + * correct. Be sure to assert bdrv_check_request() succeeded after any + * modification of BdrvTrackedRequest object out of block/io.c + */ typedef struct BdrvTrackedRequest { BlockDriverState *bs; int64_t offset; @@ -87,6 +93,8 @@ typedef struct BdrvTrackedRequest { struct BdrvTrackedRequest *waiting_for; } BdrvTrackedRequest; +int bdrv_check_request(int64_t offset, int64_t bytes); + struct BlockDriver { const char *format_name; int instance_size; diff --git a/tests/qemu-iotests/206 b/tests/qemu-iotests/206 index 0a3ee5ef00..d12d7cb566 100755 --- a/tests/qemu-iotests/206 +++ b/tests/qemu-iotests/206 @@ -202,7 +202,7 @@ with iotests.FilePath('t.qcow2') as disk_path, \ vm.launch() for size in [ 1234, 18446744073709551104, 9223372036854775808, - 9223372036854775296 ]: + 9223372036854775296, 9223372035781033984 ]: vm.blockdev_create({ 'driver': imgfmt, 'file': 'node0', 'size': size }) diff --git a/tests/qemu-iotests/206.out b/tests/qemu-iotests/206.out index a100849fcb..e8a36de00b 100644 --- a/tests/qemu-iotests/206.out +++ b/tests/qemu-iotests/206.out @@ -180,6 +180,12 @@ Job failed: Could not resize image: Image size cannot be negative {"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "qcow2", "file": "node0", "size": 9223372036854775296}}} {"return": {}} +Job failed: Could not resize image: Required too big image size, it must be not greater than 9223372035781033984 +{"execute": "job-dismiss", "arguments": {"id": "job0"}} +{"return": {}} + +{"execute": "blockdev-create", "arguments": {"job-id": "job0", "options": {"driver": "qcow2", "file": "node0", "size": 9223372035781033984}}} +{"return": {}} Job failed: Could not resize image: Failed to grow the L1 table: File too large {"execute": "job-dismiss", "arguments": {"id": "job0"}} {"return": {}} diff --git a/tests/test-write-threshold.c b/tests/test-write-threshold.c index 97ca12f710..4cf032652d 100644 --- a/tests/test-write-threshold.c +++ b/tests/test-write-threshold.c @@ -64,6 +64,8 @@ static void test_threshold_not_trigger(void) req.offset = 1024; req.bytes = 1024; + assert(bdrv_check_request(req.offset, req.bytes) == 0); + bdrv_write_threshold_set(&bs, threshold); amount = bdrv_write_threshold_exceeded(&bs, &req); g_assert_cmpuint(amount, ==, 0); @@ -82,6 +84,8 @@ static void test_threshold_trigger(void) req.offset = (4 * 1024 * 1024) - 1024; req.bytes = 2 * 1024; + assert(bdrv_check_request(req.offset, req.bytes) == 0); + bdrv_write_threshold_set(&bs, threshold); amount = bdrv_write_threshold_exceeded(&bs, &req); g_assert_cmpuint(amount, >=, 1024);