@samitouri / QOSamiQemu / commits / a47e3c26b1

linux-user: Validate guest-passed dm_ioctl data_size

In do_ioctl_dm() we work with a struct dm_ioctl from the guest. This has a fixed initial part, and then a variable data part; the guest tells us how long that part is by setting the data_size field. The data_size is supposed to include the length of the fixed parts of the struct dm_ioctl. Currently we don't validate anything about the guest-provided data_size, and we use it to allocate a buffer which we then copy the fixed part of the dm_ioctl struct into. This means that if the guest passes a very small data_size the copy of the fixed part will overrun the buffer. Perform the same sanitizing of the minimum and maximum limits of the data_size that the kernel does in drivers/md/dm-ioctl.c in the copy_params() function. Cc: qemu-stable@nongnu.org Fixes: 56e904ecb2018 ("linux-user: implement device mapper ioctls") Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3736 Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Helge Deller <deller@gmx.de> Signed-off-by: Helge Deller <deller@gmx.de>

Peter Maydell committed Jul 7, 2026 at 11:41 UTC a47e3c26b1a07292e18658e9d33425f61b4716d1
1 file changed +26 -4
linux-user/syscall.c
+26 -4
@@ -5088,6 +5088,9 @@ do_ioctl_usbdevfs_submiturb(const IOCTLEntry *ie, uint8_t *buf_temp,
5088 }
5089 #endif /* CONFIG_USBFS */
5090
5091 +#define DM_MAX_TARGETS 1048576
5092 +#define DM_MAX_TARGET_PARAMS 1024
5093 +
5094 static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
5095 int cmd, abi_long arg)
5096 {
@@ -5100,6 +5103,7 @@ static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
5103 abi_long ret;
5104 void *big_buf = NULL;
5105 char *host_data;
5106 + const size_t minimum_data_size = offsetof(struct dm_ioctl, data);
5107
5108 arg_type++;
5109 target_size = thunk_type_size(arg_type, 0);
@@ -5111,9 +5115,26 @@ static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
5115 thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST);
5116 unlock_user(argptr, arg, 0);
5117
5114 - /* buf_temp is too small, so fetch things into a bigger buffer */
5115 - big_buf = g_malloc0(((struct dm_ioctl*)buf_temp)->data_size * 2);
5116 - memcpy(big_buf, buf_temp, target_size);
5118 + /* At this point this includes the size of the fixed dm_ioctl parts */
5119 + guest_data_size = ((struct dm_ioctl *)buf_temp)->data_size;
5120 +
5121 + if (guest_data_size < minimum_data_size ||
5122 + guest_data_size > DM_MAX_TARGETS * DM_MAX_TARGET_PARAMS) {
5123 + ret = -TARGET_EINVAL;
5124 + goto out;
5125 + }
5126 +
5127 + /*
5128 + * buf_temp is too small, so fetch things into a bigger buffer. Here
5129 + * we copy all of the fixed parts of struct dm_ioctl but not the
5130 + * data at the end (which in the struct is "char data[7]" but in
5131 + * reality is command-specific and might be nothing or might be
5132 + * much larger, as defined by data_size). We know struct dm_ioctl's
5133 + * size is not target specific so we don't need to distinguish between
5134 + * its minimum size for the host vs the target.
5135 + */
5136 + big_buf = g_malloc0(guest_data_size * 2);
5137 + memcpy(big_buf, buf_temp, minimum_data_size);
5138 buf_temp = big_buf;
5139 host_dm = big_buf;
5140
@@ -5122,7 +5143,8 @@ static abi_long do_ioctl_dm(const IOCTLEntry *ie, uint8_t *buf_temp, int fd,
5143 ret = -TARGET_EINVAL;
5144 goto out;
5145 }
5125 - guest_data_size = host_dm->data_size - host_dm->data_start;
5146 + /* Adjust down to only the size of the payload */
5147 + guest_data_size -= host_dm->data_start;
5148 host_data = (char*)host_dm + host_dm->data_start;
5149
5150 argptr = lock_user(VERIFY_READ, guest_data, guest_data_size, 1);