mirror of https://github.com/torvalds/linux.git
HID: hidraw: tighten ioctl command parsing
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>
This commit is contained in:
parent
8c62074fa8
commit
75d5546f60
|
|
@ -394,15 +394,127 @@ static int hidraw_revoke(struct hidraw_list *list)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static long hidraw_ioctl(struct file *file, unsigned int cmd,
|
static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd,
|
||||||
unsigned long arg)
|
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);
|
struct inode *inode = file_inode(file);
|
||||||
unsigned int minor = iminor(inode);
|
unsigned int minor = iminor(inode);
|
||||||
long ret = 0;
|
|
||||||
struct hidraw *dev;
|
struct hidraw *dev;
|
||||||
struct hidraw_list *list = file->private_data;
|
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);
|
down_read(&minors_rwsem);
|
||||||
dev = hidraw_table[minor];
|
dev = hidraw_table[minor];
|
||||||
|
|
@ -411,124 +523,32 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd,
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (cmd) {
|
if (_IOC_TYPE(cmd) != 'H') {
|
||||||
case HIDIOCGRDESCSIZE:
|
ret = -EINVAL;
|
||||||
if (put_user(dev->hid->rsize, (int __user *)arg))
|
goto out;
|
||||||
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_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:
|
out:
|
||||||
up_read(&minors_rwsem);
|
up_read(&minors_rwsem);
|
||||||
return ret;
|
return ret;
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ struct hidraw_devinfo {
|
||||||
#define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len)
|
#define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len)
|
||||||
#define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */
|
#define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */
|
||||||
|
|
||||||
|
#define HIDIOCTL_LAST _IOC_NR(HIDIOCREVOKE)
|
||||||
|
|
||||||
#define HIDRAW_FIRST_MINOR 0
|
#define HIDRAW_FIRST_MINOR 0
|
||||||
#define HIDRAW_MAX_DEVICES 64
|
#define HIDRAW_MAX_DEVICES 64
|
||||||
/* number of reports to buffer */
|
/* number of reports to buffer */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue