Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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: 9188e79ec3fd ("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>

authored by

Benjamin Tissoires and committed by
Jiri Kosina
75d5546f 8c62074f

+143 -121
+141 -121
drivers/hid/hidraw.c
··· 394 394 return 0; 395 395 } 396 396 397 - static long hidraw_ioctl(struct file *file, unsigned int cmd, 398 - unsigned long arg) 397 + static long hidraw_fixed_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, 398 + void __user *arg) 399 + { 400 + struct hid_device *hid = dev->hid; 401 + 402 + switch (cmd) { 403 + case HIDIOCGRDESCSIZE: 404 + if (put_user(hid->rsize, (int __user *)arg)) 405 + return -EFAULT; 406 + break; 407 + 408 + case HIDIOCGRDESC: 409 + { 410 + __u32 len; 411 + 412 + if (get_user(len, (int __user *)arg)) 413 + return -EFAULT; 414 + 415 + if (len > HID_MAX_DESCRIPTOR_SIZE - 1) 416 + return -EINVAL; 417 + 418 + if (copy_to_user(arg + offsetof( 419 + struct hidraw_report_descriptor, 420 + value[0]), 421 + hid->rdesc, 422 + min(hid->rsize, len))) 423 + return -EFAULT; 424 + 425 + break; 426 + } 427 + case HIDIOCGRAWINFO: 428 + { 429 + struct hidraw_devinfo dinfo; 430 + 431 + dinfo.bustype = hid->bus; 432 + dinfo.vendor = hid->vendor; 433 + dinfo.product = hid->product; 434 + if (copy_to_user(arg, &dinfo, sizeof(dinfo))) 435 + return -EFAULT; 436 + break; 437 + } 438 + case HIDIOCREVOKE: 439 + { 440 + struct hidraw_list *list = file->private_data; 441 + 442 + if (arg) 443 + return -EINVAL; 444 + 445 + return hidraw_revoke(list); 446 + } 447 + default: 448 + /* 449 + * None of the above ioctls can return -EAGAIN, so 450 + * use it as a marker that we need to check variable 451 + * length ioctls. 452 + */ 453 + return -EAGAIN; 454 + } 455 + 456 + return 0; 457 + } 458 + 459 + static long hidraw_rw_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, 460 + void __user *user_arg) 461 + { 462 + int len = _IOC_SIZE(cmd); 463 + 464 + switch (cmd & ~IOCSIZE_MASK) { 465 + case HIDIOCSFEATURE(0): 466 + return hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); 467 + case HIDIOCGFEATURE(0): 468 + return hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); 469 + case HIDIOCSINPUT(0): 470 + return hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); 471 + case HIDIOCGINPUT(0): 472 + return hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); 473 + case HIDIOCSOUTPUT(0): 474 + return hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); 475 + case HIDIOCGOUTPUT(0): 476 + return hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); 477 + } 478 + 479 + return -EINVAL; 480 + } 481 + 482 + static long hidraw_ro_variable_size_ioctl(struct file *file, struct hidraw *dev, unsigned int cmd, 483 + void __user *user_arg) 484 + { 485 + struct hid_device *hid = dev->hid; 486 + int len = _IOC_SIZE(cmd); 487 + int field_len; 488 + 489 + switch (cmd & ~IOCSIZE_MASK) { 490 + case HIDIOCGRAWNAME(0): 491 + field_len = strlen(hid->name) + 1; 492 + if (len > field_len) 493 + len = field_len; 494 + return copy_to_user(user_arg, hid->name, len) ? -EFAULT : len; 495 + case HIDIOCGRAWPHYS(0): 496 + field_len = strlen(hid->phys) + 1; 497 + if (len > field_len) 498 + len = field_len; 499 + return copy_to_user(user_arg, hid->phys, len) ? -EFAULT : len; 500 + case HIDIOCGRAWUNIQ(0): 501 + field_len = strlen(hid->uniq) + 1; 502 + if (len > field_len) 503 + len = field_len; 504 + return copy_to_user(user_arg, hid->uniq, len) ? -EFAULT : len; 505 + } 506 + 507 + return -EINVAL; 508 + } 509 + 510 + static long hidraw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 399 511 { 400 512 struct inode *inode = file_inode(file); 401 513 unsigned int minor = iminor(inode); 402 - long ret = 0; 403 514 struct hidraw *dev; 404 515 struct hidraw_list *list = file->private_data; 405 - void __user *user_arg = (void __user*) arg; 516 + void __user *user_arg = (void __user *)arg; 517 + int ret; 406 518 407 519 down_read(&minors_rwsem); 408 520 dev = hidraw_table[minor]; ··· 523 411 goto out; 524 412 } 525 413 526 - switch (cmd) { 527 - case HIDIOCGRDESCSIZE: 528 - if (put_user(dev->hid->rsize, (int __user *)arg)) 529 - ret = -EFAULT; 530 - break; 531 - 532 - case HIDIOCGRDESC: 533 - { 534 - __u32 len; 535 - 536 - if (get_user(len, (int __user *)arg)) 537 - ret = -EFAULT; 538 - else if (len > HID_MAX_DESCRIPTOR_SIZE - 1) 539 - ret = -EINVAL; 540 - else if (copy_to_user(user_arg + offsetof( 541 - struct hidraw_report_descriptor, 542 - value[0]), 543 - dev->hid->rdesc, 544 - min(dev->hid->rsize, len))) 545 - ret = -EFAULT; 546 - break; 547 - } 548 - case HIDIOCGRAWINFO: 549 - { 550 - struct hidraw_devinfo dinfo; 551 - 552 - dinfo.bustype = dev->hid->bus; 553 - dinfo.vendor = dev->hid->vendor; 554 - dinfo.product = dev->hid->product; 555 - if (copy_to_user(user_arg, &dinfo, sizeof(dinfo))) 556 - ret = -EFAULT; 557 - break; 558 - } 559 - case HIDIOCREVOKE: 560 - { 561 - if (user_arg) 562 - ret = -EINVAL; 563 - else 564 - ret = hidraw_revoke(list); 565 - break; 566 - } 567 - default: 568 - { 569 - struct hid_device *hid = dev->hid; 570 - if (_IOC_TYPE(cmd) != 'H') { 571 - ret = -EINVAL; 572 - break; 573 - } 574 - 575 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) { 576 - int len = _IOC_SIZE(cmd); 577 - ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); 578 - break; 579 - } 580 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) { 581 - int len = _IOC_SIZE(cmd); 582 - ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); 583 - break; 584 - } 585 - 586 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) { 587 - int len = _IOC_SIZE(cmd); 588 - ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT); 589 - break; 590 - } 591 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) { 592 - int len = _IOC_SIZE(cmd); 593 - ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT); 594 - break; 595 - } 596 - 597 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) { 598 - int len = _IOC_SIZE(cmd); 599 - ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT); 600 - break; 601 - } 602 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) { 603 - int len = _IOC_SIZE(cmd); 604 - ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT); 605 - break; 606 - } 607 - 608 - /* Begin Read-only ioctls. */ 609 - if (_IOC_DIR(cmd) != _IOC_READ) { 610 - ret = -EINVAL; 611 - break; 612 - } 613 - 614 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWNAME(0))) { 615 - int len = strlen(hid->name) + 1; 616 - if (len > _IOC_SIZE(cmd)) 617 - len = _IOC_SIZE(cmd); 618 - ret = copy_to_user(user_arg, hid->name, len) ? 619 - -EFAULT : len; 620 - break; 621 - } 622 - 623 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWPHYS(0))) { 624 - int len = strlen(hid->phys) + 1; 625 - if (len > _IOC_SIZE(cmd)) 626 - len = _IOC_SIZE(cmd); 627 - ret = copy_to_user(user_arg, hid->phys, len) ? 628 - -EFAULT : len; 629 - break; 630 - } 631 - 632 - if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGRAWUNIQ(0))) { 633 - int len = strlen(hid->uniq) + 1; 634 - if (len > _IOC_SIZE(cmd)) 635 - len = _IOC_SIZE(cmd); 636 - ret = copy_to_user(user_arg, hid->uniq, len) ? 637 - -EFAULT : len; 638 - break; 639 - } 640 - } 641 - 642 - ret = -ENOTTY; 414 + if (_IOC_TYPE(cmd) != 'H') { 415 + ret = -EINVAL; 416 + goto out; 643 417 } 418 + 419 + if (_IOC_NR(cmd) > HIDIOCTL_LAST || _IOC_NR(cmd) == 0) { 420 + ret = -ENOTTY; 421 + goto out; 422 + } 423 + 424 + ret = hidraw_fixed_size_ioctl(file, dev, cmd, user_arg); 425 + if (ret != -EAGAIN) 426 + goto out; 427 + 428 + switch (_IOC_DIR(cmd)) { 429 + case (_IOC_READ | _IOC_WRITE): 430 + ret = hidraw_rw_variable_size_ioctl(file, dev, cmd, user_arg); 431 + break; 432 + case _IOC_READ: 433 + ret = hidraw_ro_variable_size_ioctl(file, dev, cmd, user_arg); 434 + break; 435 + default: 436 + /* Any other IOC_DIR is wrong */ 437 + ret = -EINVAL; 438 + } 439 + 644 440 out: 645 441 up_read(&minors_rwsem); 646 442 return ret;
+2
include/uapi/linux/hidraw.h
··· 48 48 #define HIDIOCGOUTPUT(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len) 49 49 #define HIDIOCREVOKE _IOW('H', 0x0D, int) /* Revoke device access */ 50 50 51 + #define HIDIOCTL_LAST _IOC_NR(HIDIOCREVOKE) 52 + 51 53 #define HIDRAW_FIRST_MINOR 0 52 54 #define HIDRAW_MAX_DEVICES 64 53 55 /* number of reports to buffer */