aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid
diff options
context:
space:
mode:
authorJiri Kosina <jkosina@suse.cz>2013-07-10 13:56:27 -0400
committerJiri Kosina <jkosina@suse.cz>2013-07-22 10:16:40 -0400
commit27ce405039bfe6d3f4143415c638f56a3df77dca (patch)
tree5419dac5ff32335c81bb1d753a2c0df181ed44c7 /drivers/hid
parent0adb9c2c5ed42f199cb2a630c37d18dee385fae2 (diff)
HID: fix data access in implement()
implement() is setting bytes in LE data stream. In case the data is not aligned to 64bits, it reads past the allocated buffer. It doesn't really change any value there (it's properly bitmasked), but in case that this read past the boundary hits a page boundary, pagefault happens when accessing 64bits of 'x' in implement(), and kernel oopses. This happens much more often when numbered reports are in use, as the initial 8bit skip in the buffer makes the whole process work on values which are not aligned to 64bits. This problem dates back to attempts in 2005 and 2006 to make implement() and extract() as generic as possible, and even back then the problem was realized by Adam Kroperlin, but falsely assumed to be impossible to cause any harm: http://www.mail-archive.com/linux-usb-devel@lists.sourceforge.net/msg47690.html I have made several attempts at fixing it "on the spot" directly in implement(), but the results were horrible; the special casing for processing last 64bit chunk and switching to different math makes it unreadable mess. I therefore took a path to allocate a few bytes more which will never make it into final report, but are there as a cushion for all the 64bit math operations happening in implement() and extract(). All callers of hid_output_report() are converted at the same time to allocate the buffer by newly introduced hid_alloc_report_buf() helper. Bruno noticed that the whole raw_size test can be dropped as well, as hid_alloc_report_buf() makes sure that the buffer is always of a proper size. Reviewed-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> Acked-by: Gustavo Padovan <gustavo.padovan@collabora.co.uk> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid')
-rw-r--r--drivers/hid/hid-core.c19
-rw-r--r--drivers/hid/hid-logitech-dj.c12
-rw-r--r--drivers/hid/hid-picolcd_debugfs.c23
-rw-r--r--drivers/hid/usbhid/hid-core.c5
4 files changed, 42 insertions, 17 deletions
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index b0f2f459f59b..a1b248cea5b0 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1128,7 +1128,8 @@ static void hid_output_field(const struct hid_device *hid,
1128} 1128}
1129 1129
1130/* 1130/*
1131 * Create a report. 1131 * Create a report. 'data' has to be allocated using
1132 * hid_alloc_report_buf() so that it has proper size.
1132 */ 1133 */
1133 1134
1134void hid_output_report(struct hid_report *report, __u8 *data) 1135void hid_output_report(struct hid_report *report, __u8 *data)
@@ -1145,6 +1146,22 @@ void hid_output_report(struct hid_report *report, __u8 *data)
1145EXPORT_SYMBOL_GPL(hid_output_report); 1146EXPORT_SYMBOL_GPL(hid_output_report);
1146 1147
1147/* 1148/*
1149 * Allocator for buffer that is going to be passed to hid_output_report()
1150 */
1151u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags)
1152{
1153 /*
1154 * 7 extra bytes are necessary to achieve proper functionality
1155 * of implement() working on 8 byte chunks
1156 */
1157
1158 int len = ((report->size - 1) >> 3) + 1 + (report->id > 0) + 7;
1159
1160 return kmalloc(len, flags);
1161}
1162EXPORT_SYMBOL_GPL(hid_alloc_report_buf);
1163
1164/*
1148 * Set a field value. The report this field belongs to has to be 1165 * Set a field value. The report this field belongs to has to be
1149 * created and transferred to the device, to set this value in the 1166 * created and transferred to the device, to set this value in the
1150 * device. 1167 * device.
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
index 5207591a598c..4d792739dbd1 100644
--- a/drivers/hid/hid-logitech-dj.c
+++ b/drivers/hid/hid-logitech-dj.c
@@ -574,7 +574,7 @@ static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type,
574 574
575 struct hid_field *field; 575 struct hid_field *field;
576 struct hid_report *report; 576 struct hid_report *report;
577 unsigned char data[8]; 577 unsigned char *data;
578 int offset; 578 int offset;
579 579
580 dbg_hid("%s: %s, type:%d | code:%d | value:%d\n", 580 dbg_hid("%s: %s, type:%d | code:%d | value:%d\n",
@@ -590,6 +590,13 @@ static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type,
590 return -1; 590 return -1;
591 } 591 }
592 hid_set_field(field, offset, value); 592 hid_set_field(field, offset, value);
593
594 data = hid_alloc_report_buf(field->report, GFP_KERNEL);
595 if (!data) {
596 dev_warn(&dev->dev, "failed to allocate report buf memory\n");
597 return -1;
598 }
599
593 hid_output_report(field->report, &data[0]); 600 hid_output_report(field->report, &data[0]);
594 601
595 output_report_enum = &dj_rcv_hiddev->report_enum[HID_OUTPUT_REPORT]; 602 output_report_enum = &dj_rcv_hiddev->report_enum[HID_OUTPUT_REPORT];
@@ -600,8 +607,9 @@ static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type,
600 607
601 hid_hw_request(dj_rcv_hiddev, report, HID_REQ_SET_REPORT); 608 hid_hw_request(dj_rcv_hiddev, report, HID_REQ_SET_REPORT);
602 609
603 return 0; 610 kfree(data);
604 611
612 return 0;
605} 613}
606 614
607static int logi_dj_ll_start(struct hid_device *hid) 615static int logi_dj_ll_start(struct hid_device *hid)
diff --git a/drivers/hid/hid-picolcd_debugfs.c b/drivers/hid/hid-picolcd_debugfs.c
index 59ab8e157e6b..024cdf3c2297 100644
--- a/drivers/hid/hid-picolcd_debugfs.c
+++ b/drivers/hid/hid-picolcd_debugfs.c
@@ -394,7 +394,7 @@ static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data,
394void picolcd_debug_out_report(struct picolcd_data *data, 394void picolcd_debug_out_report(struct picolcd_data *data,
395 struct hid_device *hdev, struct hid_report *report) 395 struct hid_device *hdev, struct hid_report *report)
396{ 396{
397 u8 raw_data[70]; 397 u8 *raw_data;
398 int raw_size = (report->size >> 3) + 1; 398 int raw_size = (report->size >> 3) + 1;
399 char *buff; 399 char *buff;
400#define BUFF_SZ 256 400#define BUFF_SZ 256
@@ -407,20 +407,20 @@ void picolcd_debug_out_report(struct picolcd_data *data,
407 if (!buff) 407 if (!buff)
408 return; 408 return;
409 409
410 snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ", 410 raw_data = hid_alloc_report_buf(report, GFP_ATOMIC);
411 report->id, raw_size); 411 if (!raw_data) {
412 hid_debug_event(hdev, buff);
413 if (raw_size + 5 > sizeof(raw_data)) {
414 kfree(buff); 412 kfree(buff);
415 hid_debug_event(hdev, " TOO BIG\n");
416 return; 413 return;
417 } else {
418 raw_data[0] = report->id;
419 hid_output_report(report, raw_data);
420 dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size);
421 hid_debug_event(hdev, buff);
422 } 414 }
423 415
416 snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ",
417 report->id, raw_size);
418 hid_debug_event(hdev, buff);
419 raw_data[0] = report->id;
420 hid_output_report(report, raw_data);
421 dump_buff_as_hex(buff, BUFF_SZ, raw_data, raw_size);
422 hid_debug_event(hdev, buff);
423
424 switch (report->id) { 424 switch (report->id) {
425 case REPORT_LED_STATE: 425 case REPORT_LED_STATE:
426 /* 1 data byte with GPO state */ 426 /* 1 data byte with GPO state */
@@ -644,6 +644,7 @@ void picolcd_debug_out_report(struct picolcd_data *data,
644 break; 644 break;
645 } 645 }
646 wake_up_interruptible(&hdev->debug_wait); 646 wake_up_interruptible(&hdev->debug_wait);
647 kfree(raw_data);
647 kfree(buff); 648 kfree(buff);
648} 649}
649 650
diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c
index 99418285222c..ada164e1b3a1 100644
--- a/drivers/hid/usbhid/hid-core.c
+++ b/drivers/hid/usbhid/hid-core.c
@@ -535,7 +535,6 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
535{ 535{
536 int head; 536 int head;
537 struct usbhid_device *usbhid = hid->driver_data; 537 struct usbhid_device *usbhid = hid->driver_data;
538 int len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
539 538
540 if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN) 539 if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
541 return; 540 return;
@@ -546,7 +545,7 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
546 return; 545 return;
547 } 546 }
548 547
549 usbhid->out[usbhid->outhead].raw_report = kmalloc(len, GFP_ATOMIC); 548 usbhid->out[usbhid->outhead].raw_report = hid_alloc_report_buf(report, GFP_ATOMIC);
550 if (!usbhid->out[usbhid->outhead].raw_report) { 549 if (!usbhid->out[usbhid->outhead].raw_report) {
551 hid_warn(hid, "output queueing failed\n"); 550 hid_warn(hid, "output queueing failed\n");
552 return; 551 return;
@@ -595,7 +594,7 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
595 } 594 }
596 595
597 if (dir == USB_DIR_OUT) { 596 if (dir == USB_DIR_OUT) {
598 usbhid->ctrl[usbhid->ctrlhead].raw_report = kmalloc(len, GFP_ATOMIC); 597 usbhid->ctrl[usbhid->ctrlhead].raw_report = hid_alloc_report_buf(report, GFP_ATOMIC);
599 if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) { 598 if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) {
600 hid_warn(hid, "control queueing failed\n"); 599 hid_warn(hid, "control queueing failed\n");
601 return; 600 return;