aboutsummaryrefslogtreecommitdiffstats
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
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>
-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
-rw-r--r--include/linux/hid.h1
-rw-r--r--net/bluetooth/hidp/core.c14
6 files changed, 52 insertions, 22 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;
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 0c48991b0402..acccdf4eb485 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -744,6 +744,7 @@ struct hid_field *hidinput_get_led_field(struct hid_device *hid);
744unsigned int hidinput_count_leds(struct hid_device *hid); 744unsigned int hidinput_count_leds(struct hid_device *hid);
745__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code); 745__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code);
746void hid_output_report(struct hid_report *report, __u8 *data); 746void hid_output_report(struct hid_report *report, __u8 *data);
747u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags);
747struct hid_device *hid_allocate_device(void); 748struct hid_device *hid_allocate_device(void);
748struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); 749struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id);
749int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size); 750int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size);
diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
index 46c6a148f0b3..212980ff99b9 100644
--- a/net/bluetooth/hidp/core.c
+++ b/net/bluetooth/hidp/core.c
@@ -231,17 +231,21 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
231 231
232static int hidp_send_report(struct hidp_session *session, struct hid_report *report) 232static int hidp_send_report(struct hidp_session *session, struct hid_report *report)
233{ 233{
234 unsigned char buf[32], hdr; 234 unsigned char hdr;
235 int rsize; 235 u8 *buf;
236 int rsize, ret;
236 237
237 rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0); 238 buf = hid_alloc_report_buf(report, GFP_ATOMIC);
238 if (rsize > sizeof(buf)) 239 if (!buf)
239 return -EIO; 240 return -EIO;
240 241
241 hid_output_report(report, buf); 242 hid_output_report(report, buf);
242 hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT; 243 hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT;
243 244
244 return hidp_send_intr_message(session, hdr, buf, rsize); 245 ret = hidp_send_intr_message(session, hdr, buf, rsize);
246
247 kfree(buf);
248 return ret;
245} 249}
246 250
247static int hidp_get_raw_report(struct hid_device *hid, 251static int hidp_get_raw_report(struct hid_device *hid,