aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJiri Kosina <jkosina@suse.cz>2013-07-10 13:56:27 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-10-13 19:08:28 -0400
commitb2b6cadad699d44a8a5b2a60f3d960e00d6fb3b7 (patch)
tree479d05027135d7d32c7d643a54fda77ce278a375
parent698508f44ad8d2f4289f8fae4488576f2902703a (diff)
HID: fix data access in implement()
commit 27ce405039bfe6d3f4143415c638f56a3df77dca upstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-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 ca959cf572a6..c561293624a7 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1188,7 +1188,8 @@ static void hid_output_field(const struct hid_device *hid,
1188} 1188}
1189 1189
1190/* 1190/*
1191 * Create a report. 1191 * Create a report. 'data' has to be allocated using
1192 * hid_alloc_report_buf() so that it has proper size.
1192 */ 1193 */
1193 1194
1194void hid_output_report(struct hid_report *report, __u8 *data) 1195void hid_output_report(struct hid_report *report, __u8 *data)
@@ -1205,6 +1206,22 @@ void hid_output_report(struct hid_report *report, __u8 *data)
1205EXPORT_SYMBOL_GPL(hid_output_report); 1206EXPORT_SYMBOL_GPL(hid_output_report);
1206 1207
1207/* 1208/*
1209 * Allocator for buffer that is going to be passed to hid_output_report()
1210 */
1211u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags)
1212{
1213 /*
1214 * 7 extra bytes are necessary to achieve proper functionality
1215 * of implement() working on 8 byte chunks
1216 */
1217
1218 int len = ((report->size - 1) >> 3) + 1 + (report->id > 0) + 7;
1219
1220 return kmalloc(len, flags);
1221}
1222EXPORT_SYMBOL_GPL(hid_alloc_report_buf);
1223
1224/*
1208 * Set a field value. The report this field belongs to has to be 1225 * Set a field value. The report this field belongs to has to be
1209 * created and transferred to the device, to set this value in the 1226 * created and transferred to the device, to set this value in the
1210 * device. 1227 * device.
diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c
index 0522b80eab5a..4f762bc9456a 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 6e185509046d..4f8aa4733fb6 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -746,6 +746,7 @@ struct hid_field *hidinput_get_led_field(struct hid_device *hid);
746unsigned int hidinput_count_leds(struct hid_device *hid); 746unsigned int hidinput_count_leds(struct hid_device *hid);
747__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code); 747__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code);
748void hid_output_report(struct hid_report *report, __u8 *data); 748void hid_output_report(struct hid_report *report, __u8 *data);
749u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags);
749struct hid_device *hid_allocate_device(void); 750struct hid_device *hid_allocate_device(void);
750struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id); 751struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id);
751int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size); 752int 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 940f5acb6694..41f154d4a5e1 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,