HID: hidraw: tighten ioctl command parsing

[ Upstream commit 75d5546f60b36900051d75ee623fceccbeb6750c ]

The handling for variable-length ioctl commands in hidraw_ioctl() is
rather complex and the check for the data direction is incomplete.

Simplify this code by factoring out the various ioctls grouped by dir
and size, and using a switch() statement with the size masked out, to
ensure the rest of the command is correctly matched.

Fixes: 9188e79ec3 ("HID: add phys and name ioctls to hidraw")
Reported-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
Signed-off-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:
Benjamin Tissoires 2025-09-12 18:58:51 +02:00 committed by Greg Kroah-Hartman
parent 582e82905d
commit 5b1c071d5e
2 changed files with 143 additions and 121 deletions

View File

@ -394,15 +394,127 @@ static int hidraw_revoke(struct hidraw_list *list)
return 0;
}
static long hidraw_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
void __user *arg)
{
struct hid_device *hid = dev->hid;
switch (cmd) {
case HIDIOCGRDESCSIZE:
if (put_user(hid->rsize, (int __user *)arg))
return -EFAULT;
break;
case HIDIOCGRDESC:
{
__u32 len;
if (get_user(len, (int __user *)arg))
return -EFAULT;
if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
return -EINVAL;
if (copy_to_user(arg + offsetof(
struct hidraw_report_descriptor,
value[0]),
hid->rdesc,
min(hid->rsize, len)))
return -EFAULT;
break;
}
case HIDIOCGRAWINFO:
{
struct hidraw_devinfo dinfo;
dinfo.bustype = hid->bus;
dinfo.vendor = hid->vendor;
dinfo.product = hid->product;
if (copy_to_user(arg, &dinfo, sizeof(dinfo)))
return -EFAULT;
break;
}
case HIDIOCREVOKE:
{
struct hidraw_list *list = file->private_data;
if (arg)
return -EINVAL;
return hidraw_revoke(list);
}
default:
/*
* None of the above ioctls can return -EAGAIN, so
* use it as a marker that we need to check variable
* length ioctls.
*/
return -EAGAIN;
}
return 0;
}
static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
void __user *user_arg)
{
int len = _IOC_SIZE(cmd);
switch (cmd & ~IOCSIZE_MASK) {
case HIDIOCSFEATURE(0):
return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
case HIDIOCGFEATURE(0):
return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
case HIDIOCSINPUT(0):
return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
case HIDIOCGINPUT(0):
return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
case HIDIOCSOUTPUT(0):
return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
case HIDIOCGOUTPUT(0):
return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
}
return -EINVAL;
}
static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
void __user *user_arg)
{
struct hid_device *hid = dev->hid;
int len = _IOC_SIZE(cmd);
int field_len;
switch (cmd & ~IOCSIZE_MASK) {
case HIDIOCGRAWNAME(0):
field_len = strlen(hid->name) + 1;
if (len > field_len)
len = field_len;
return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len;
case HIDIOCGRAWPHYS(0):
field_len = strlen(hid->phys) + 1;
if (len > field_len)
len = field_len;
return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len;
case HIDIOCGRAWUNIQ(0):
field_len = strlen(hid->uniq) + 1;
if (len > field_len)
len = field_len;
return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len;
}
return -EINVAL;
}
static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct inode *inode = file_inode(file);
unsigned int minor = iminor(inode);
long ret = 0;
struct hidraw *dev;
struct hidraw_list *list = file->private_data;
void __user *user_arg = (void __user*) arg;
void __user *user_arg = (void __user *)arg;
int ret;
down_read(&minors_rwsem);
dev = hidraw_table[minor];
@ -411,124 +523,32 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
goto out;
}
switch (cmd) {
case HIDIOCGRDESCSIZE:
if (put_user(dev->hid->rsize, (int __user *)arg))
ret = -EFAULT;
break;
case HIDIOCGRDESC:
{
__u32 len;
if (get_user(len, (int __user *)arg))
ret = -EFAULT;
else if (len > HID_MAX_DESCRIPTOR_SIZE - 1)
ret = -EINVAL;
else if (copy_to_user(user_arg + offsetof(
struct hidraw_report_descriptor,
value[0]),
dev->hid->rdesc,
min(dev->hid->rsize, len)))
ret = -EFAULT;
break;
}
case HIDIOCGRAWINFO:
{
struct hidraw_devinfo dinfo;
dinfo.bustype = dev->hid->bus;
dinfo.vendor = dev->hid->vendor;
dinfo.product = dev->hid->product;
if (copy_to_user(user_arg, &dinfo, sizeof(dinfo)))
ret = -EFAULT;
break;
}
case HIDIOCREVOKE:
{
if (user_arg)
ret = -EINVAL;
else
ret = hidraw_revoke(list);
break;
}
default:
{
struct hid_device *hid = dev->hid;
if (_IOC_TYPE(cmd) != 'H') {
ret = -EINVAL;
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
int len = _IOC_SIZE(cmd);
ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
break;
}
/* Begin Read-only ioctls. */
if (_IOC_DIR(cmd) != _IOC_READ) {
ret = -EINVAL;
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) {
int len = strlen(hid->name) + 1;
if (len > _IOC_SIZE(cmd))
len = _IOC_SIZE(cmd);
ret = copy_to_user(user_arg, hid->name, len) ?
-EFAULT : len;
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) {
int len = strlen(hid->phys) + 1;
if (len > _IOC_SIZE(cmd))
len = _IOC_SIZE(cmd);
ret = copy_to_user(user_arg, hid->phys, len) ?
-EFAULT : len;
break;
}
if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) {
int len = strlen(hid->uniq) + 1;
if (len > _IOC_SIZE(cmd))
len = _IOC_SIZE(cmd);
ret = copy_to_user(user_arg, hid->uniq, len) ?
-EFAULT : len;
break;
}
}
ret = -ENOTTY;
if (_IOC_TYPE(cmd) != 'H') {
ret = -EINVAL;
goto out;
}
if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) {
ret = -ENOTTY;
goto out;
}
ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg);
if (ret != -EAGAIN)
goto out;
switch (_IOC_DIR(cmd)) {
case (_IOC_READ | _IOC_WRITE):
ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg);
break;
case _IOC_READ:
ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg);
break;
default:
/* Any other IOC_DIR is wrong */
ret = -EINVAL;
}
out:
up_read(&minors_rwsem);
return ret;

View File

@ -48,6 +48,8 @@ struct hidraw_devinfo {
#define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len)
#define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */
#define HIDIOCTL_LAST _IOC_NR(HIDIOCREVOKE)
#define HIDRAW_FIRST_MINOR 0
#define HIDRAW_MAX_DEVICES 64
/* number of reports to buffer */