Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit 27ce405

Browse files
author
Jiri Kosina
committed
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/[email protected]/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 <[email protected]> Acked-by: Gustavo Padovan <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent 0adb9c2 commit 27ce405

File tree

6 files changed

+52
-22
lines changed

6 files changed

+52
-22
lines changed

drivers/hid/hid-core.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1128,7 +1128,8 @@ static void hid_output_field(const struct hid_device *hid,
11281128
}
11291129

11301130
/*
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.
11321133
*/
11331134

11341135
void hid_output_report(struct hid_report *report, __u8 *data)
@@ -1144,6 +1145,22 @@ void hid_output_report(struct hid_report *report, __u8 *data)
11441145
}
11451146
EXPORT_SYMBOL_GPL(hid_output_report);
11461147

1148+
/*
1149+
* Allocator for buffer that is going to be passed to hid_output_report()
1150+
*/
1151+
u8 *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+
}
1162+
EXPORT_SYMBOL_GPL(hid_alloc_report_buf);
1163+
11471164
/*
11481165
* Set a field value. The report this field belongs to has to be
11491166
* created and transferred to the device, to set this value in the

drivers/hid/hid-logitech-dj.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ static int logi_dj_ll_input_event(struct input_dev *dev, unsigned int type,
574574

575575
struct hid_field *field;
576576
struct hid_report *report;
577-
unsigned char data[8];
577+
unsigned char *data;
578578
int offset;
579579

580580
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,
590590
return -1;
591591
}
592592
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+
593600
hid_output_report(field->report, &data[0]);
594601

595602
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,
600607

601608
hid_hw_request(dj_rcv_hiddev, report, HID_REQ_SET_REPORT);
602609

603-
return 0;
610+
kfree(data);
604611

612+
return 0;
605613
}
606614

607615
static int logi_dj_ll_start(struct hid_device *hid)

drivers/hid/hid-picolcd_debugfs.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ static void dump_buff_as_hex(char *dst, size_t dst_sz, const u8 *data,
394394
void picolcd_debug_out_report(struct picolcd_data *data,
395395
struct hid_device *hdev, struct hid_report *report)
396396
{
397-
u8 raw_data[70];
397+
u8 *raw_data;
398398
int raw_size = (report->size >> 3) + 1;
399399
char *buff;
400400
#define BUFF_SZ 256
@@ -407,20 +407,20 @@ void picolcd_debug_out_report(struct picolcd_data *data,
407407
if (!buff)
408408
return;
409409

410-
snprintf(buff, BUFF_SZ, "\nout report %d (size %d) = ",
411-
report->id, raw_size);
412-
hid_debug_event(hdev, buff);
413-
if (raw_size + 5 > sizeof(raw_data)) {
410+
raw_data = hid_alloc_report_buf(report, GFP_ATOMIC);
411+
if (!raw_data) {
414412
kfree(buff);
415-
hid_debug_event(hdev, " TOO BIG\n");
416413
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);
422414
}
423415

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+
424424
switch (report->id) {
425425
case REPORT_LED_STATE:
426426
/* 1 data byte with GPO state */
@@ -644,6 +644,7 @@ void picolcd_debug_out_report(struct picolcd_data *data,
644644
break;
645645
}
646646
wake_up_interruptible(&hdev->debug_wait);
647+
kfree(raw_data);
647648
kfree(buff);
648649
}
649650

drivers/hid/usbhid/hid-core.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,6 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
535535
{
536536
int head;
537537
struct usbhid_device *usbhid = hid->driver_data;
538-
int len = ((report->size - 1) >> 3) + 1 + (report->id > 0);
539538

540539
if ((hid->quirks & HID_QUIRK_NOGET) && dir == USB_DIR_IN)
541540
return;
@@ -546,7 +545,7 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
546545
return;
547546
}
548547

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);
550549
if (!usbhid->out[usbhid->outhead].raw_report) {
551550
hid_warn(hid, "output queueing failed\n");
552551
return;
@@ -595,7 +594,7 @@ static void __usbhid_submit_report(struct hid_device *hid, struct hid_report *re
595594
}
596595

597596
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);
599598
if (!usbhid->ctrl[usbhid->ctrlhead].raw_report) {
600599
hid_warn(hid, "control queueing failed\n");
601600
return;

include/linux/hid.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,7 @@ struct hid_field *hidinput_get_led_field(struct hid_device *hid);
744744
unsigned int hidinput_count_leds(struct hid_device *hid);
745745
__s32 hidinput_calc_abs_res(const struct hid_field *field, __u16 code);
746746
void hid_output_report(struct hid_report *report, __u8 *data);
747+
u8 *hid_alloc_report_buf(struct hid_report *report, gfp_t flags);
747748
struct hid_device *hid_allocate_device(void);
748749
struct hid_report *hid_register_report(struct hid_device *device, unsigned type, unsigned id);
749750
int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size);

net/bluetooth/hidp/core.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,21 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
231231

232232
static int hidp_send_report(struct hidp_session *session, struct hid_report *report)
233233
{
234-
unsigned char buf[32], hdr;
235-
int rsize;
234+
unsigned char hdr;
235+
u8 *buf;
236+
int rsize, ret;
236237

237-
rsize = ((report->size - 1) >> 3) + 1 + (report->id > 0);
238-
if (rsize > sizeof(buf))
238+
buf = hid_alloc_report_buf(report, GFP_ATOMIC);
239+
if (!buf)
239240
return -EIO;
240241

241242
hid_output_report(report, buf);
242243
hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUPUT;
243244

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;
245249
}
246250

247251
static int hidp_get_raw_report(struct hid_device *hid,

0 commit comments

Comments
 (0)