diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-17 21:54:05 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-09-17 21:54:05 -0400 |
commit | 84fca9f38c5d646e95cdeef70e41cf15db549b95 (patch) | |
tree | c904715c9b70a2f0716c84931e2d58b1a226a1e0 /drivers/hid | |
parent | 03e1261778cca782d41a3d8e3945ca88cf93e01e (diff) | |
parent | 0ccdd9e7476680c16113131264ad6597bd10299d (diff) |
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
Pull HID updates from Jiri Kosina:
"Fixes for CVE-2013-2897, CVE-2013-2895, CVE-2013-2897, CVE-2013-2894,
CVE-2013-2893, CVE-2013-2891, CVE-2013-2890, CVE-2013-2889.
All the bugs are triggerable only by specially crafted evil-on-purpose
HW devices. Fixes by Kees Cook and Benjamin Tissoires"
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid:
HID: lenovo-tpkbd: fix leak if tpkbd_probe_tp fails
HID: multitouch: validate indexes details
HID: logitech-dj: validate output report details
HID: validate feature and input report details
HID: lenovo-tpkbd: validate output report details
HID: LG: validate HID output report details
HID: steelseries: validate output report details
HID: sony: validate HID output report details
HID: zeroplus: validate output report details
HID: provide a helper for validating hid reports
Diffstat (limited to 'drivers/hid')
-rw-r--r-- | drivers/hid/hid-core.c | 74 | ||||
-rw-r--r-- | drivers/hid/hid-input.c | 11 | ||||
-rw-r--r-- | drivers/hid/hid-lenovo-tpkbd.c | 25 | ||||
-rw-r--r-- | drivers/hid/hid-lg2ff.c | 19 | ||||
-rw-r--r-- | drivers/hid/hid-lg3ff.c | 29 | ||||
-rw-r--r-- | drivers/hid/hid-lg4ff.c | 20 | ||||
-rw-r--r-- | drivers/hid/hid-lgff.c | 17 | ||||
-rw-r--r-- | drivers/hid/hid-logitech-dj.c | 10 | ||||
-rw-r--r-- | drivers/hid/hid-multitouch.c | 26 | ||||
-rw-r--r-- | drivers/hid/hid-sony.c | 4 | ||||
-rw-r--r-- | drivers/hid/hid-steelseries.c | 5 | ||||
-rw-r--r-- | drivers/hid/hid-zpff.c | 18 |
12 files changed, 142 insertions, 116 deletions
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index ae88a97f976..b8470b1a10f 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -94,7 +94,6 @@ EXPORT_SYMBOL_GPL(hid_register_report); static struct hid_field *hid_register_field(struct hid_report *report, unsigned usages, unsigned values) { struct hid_field *field; - int i; if (report->maxfield == HID_MAX_FIELDS) { hid_err(report->device, "too many fields in report\n"); @@ -113,9 +112,6 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned field->value = (s32 *)(field->usage + usages); field->report = report; - for (i = 0; i < usages; i++) - field->usage[i].usage_index = i; - return field; } @@ -226,9 +222,9 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign { struct hid_report *report; struct hid_field *field; - int usages; + unsigned usages; unsigned offset; - int i; + unsigned i; report = hid_register_report(parser->device, report_type, parser->global.report_id); if (!report) { @@ -255,7 +251,8 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign if (!parser->local.usage_index) /* Ignore padding fields */ return 0; - usages = max_t(int, parser->local.usage_index, parser->global.report_count); + usages = max_t(unsigned, parser->local.usage_index, + parser->global.report_count); field = hid_register_field(report, usages, parser->global.report_count); if (!field) @@ -266,13 +263,14 @@ static int hid_add_field(struct hid_parser *parser, unsigned report_type, unsign field->application = hid_lookup_collection(parser, HID_COLLECTION_APPLICATION); for (i = 0; i < usages; i++) { - int j = i; + unsigned j = i; /* Duplicate the last usage we parsed if we have excess values */ if (i >= parser->local.usage_index) j = parser->local.usage_index - 1; field->usage[i].hid = parser->local.usage[j]; field->usage[i].collection_index = parser->local.collection_index[j]; + field->usage[i].usage_index = i; } field->maxusage = usages; @@ -801,6 +799,64 @@ int hid_parse_report(struct hid_device *hid, __u8 *start, unsigned size) } EXPORT_SYMBOL_GPL(hid_parse_report); +static const char * const hid_report_names[] = { + "HID_INPUT_REPORT", + "HID_OUTPUT_REPORT", + "HID_FEATURE_REPORT", +}; +/** + * hid_validate_values - validate existing device report's value indexes + * + * @device: hid device + * @type: which report type to examine + * @id: which report ID to examine (0 for first) + * @field_index: which report field to examine + * @report_counts: expected number of values + * + * Validate the number of values in a given field of a given report, after + * parsing. + */ +struct hid_report *hid_validate_values(struct hid_device *hid, + unsigned int type, unsigned int id, + unsigned int field_index, + unsigned int report_counts) +{ + struct hid_report *report; + + if (type > HID_FEATURE_REPORT) { + hid_err(hid, "invalid HID report type %u\n", type); + return NULL; + } + + if (id >= HID_MAX_IDS) { + hid_err(hid, "invalid HID report id %u\n", id); + return NULL; + } + + /* + * Explicitly not using hid_get_report() here since it depends on + * ->numbered being checked, which may not always be the case when + * drivers go to access report values. + */ + report = hid->report_enum[type].report_id_hash[id]; + if (!report) { + hid_err(hid, "missing %s %u\n", hid_report_names[type], id); + return NULL; + } + if (report->maxfield <= field_index) { + hid_err(hid, "not enough fields in %s %u\n", + hid_report_names[type], id); + return NULL; + } + if (report->field[field_index]->report_count < report_counts) { + hid_err(hid, "not enough values in %s %u field %u\n", + hid_report_names[type], id, field_index); + return NULL; + } + return report; +} +EXPORT_SYMBOL_GPL(hid_validate_values); + /** * hid_open_report - open a driver-specific device report * @@ -1296,7 +1352,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, int size, goto out; } - if (hid->claimed != HID_CLAIMED_HIDRAW) { + if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) { for (a = 0; a < report->maxfield; a++) hid_input_field(hid, report->field[a], cdata, interrupt); hdrv = hid->driver; diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index b420f4a0fd2..8741d953dcc 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -485,6 +485,10 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel if (field->flags & HID_MAIN_ITEM_CONSTANT) goto ignore; + /* Ignore if report count is out of bounds. */ + if (field->report_count < 1) + goto ignore; + /* only LED usages are supported in output fields */ if (field->report_type == HID_OUTPUT_REPORT && (usage->hid & HID_USAGE_PAGE) != HID_UP_LED) { @@ -1236,7 +1240,11 @@ static void report_features(struct hid_device *hid) rep_enum = &hid->report_enum[HID_FEATURE_REPORT]; list_for_each_entry(rep, &rep_enum->report_list, list) - for (i = 0; i < rep->maxfield; i++) + for (i = 0; i < rep->maxfield; i++) { + /* Ignore if report count is out of bounds. */ + if (rep->field[i]->report_count < 1) + continue; + for (j = 0; j < rep->field[i]->maxusage; j++) { /* Verify if Battery Strength feature is available */ hidinput_setup_battery(hid, HID_FEATURE_REPORT, rep->field[i]); @@ -1245,6 +1253,7 @@ static void report_features(struct hid_device *hid) drv->feature_mapping(hid, rep->field[i], rep->field[i]->usage + j); } + } } static struct hid_input *hidinput_allocate(struct hid_device *hid) diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c index 07837f5a4eb..31cf29a6ba1 100644 --- a/drivers/hid/hid-lenovo-tpkbd.c +++ b/drivers/hid/hid-lenovo-tpkbd.c @@ -339,7 +339,15 @@ static int tpkbd_probe_tp(struct hid_device *hdev) struct tpkbd_data_pointer *data_pointer; size_t name_sz = strlen(dev_name(dev)) + 16; char *name_mute, *name_micmute; - int ret; + int i, ret; + + /* Validate required reports. */ + for (i = 0; i < 4; i++) { + if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1)) + return -ENODEV; + } + if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2)) + return -ENODEV; if (sysfs_create_group(&hdev->dev.kobj, &tpkbd_attr_group_pointer)) { @@ -406,22 +414,27 @@ static int tpkbd_probe(struct hid_device *hdev, ret = hid_parse(hdev); if (ret) { hid_err(hdev, "hid_parse failed\n"); - goto err_free; + goto err; } ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hid_hw_start failed\n"); - goto err_free; + goto err; } uhdev = (struct usbhid_device *) hdev->driver_data; - if (uhdev->ifnum == 1) - return tpkbd_probe_tp(hdev); + if (uhdev->ifnum == 1) { + ret = tpkbd_probe_tp(hdev); + if (ret) + goto err_hid; + } return 0; -err_free: +err_hid: + hid_hw_stop(hdev); +err: return ret; } diff --git a/drivers/hid/hid-lg2ff.c b/drivers/hid/hid-lg2ff.c index b3cd1507dda..1a42eaa6ca0 100644 --- a/drivers/hid/hid-lg2ff.c +++ b/drivers/hid/hid-lg2ff.c @@ -64,26 +64,13 @@ int lg2ff_init(struct hid_device *hid) struct hid_report *report; struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct list_head *report_list = - &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct input_dev *dev = hidinput->input; int error; - if (list_empty(report_list)) { - hid_err(hid, "no output report found\n"); + /* Check that the report looks ok */ + report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7); + if (!report) return -ENODEV; - } - - report = list_entry(report_list->next, struct hid_report, list); - - if (report->maxfield < 1) { - hid_err(hid, "output report is empty\n"); - return -ENODEV; - } - if (report->field[0]->report_count < 7) { - hid_err(hid, "not enough values in the field\n"); - return -ENODEV; - } lg2ff = kmalloc(sizeof(struct lg2ff_device), GFP_KERNEL); if (!lg2ff) diff --git a/drivers/hid/hid-lg3ff.c b/drivers/hid/hid-lg3ff.c index e52f181f6aa..8c2da183d3b 100644 --- a/drivers/hid/hid-lg3ff.c +++ b/drivers/hid/hid-lg3ff.c @@ -66,10 +66,11 @@ static int hid_lg3ff_play(struct input_dev *dev, void *data, int x, y; /* - * Maxusage should always be 63 (maximum fields) - * likely a better way to ensure this data is clean + * Available values in the field should always be 63, but we only use up to + * 35. Instead, clear the entire area, however big it is. */ - memset(report->field[0]->value, 0, sizeof(__s32)*report->field[0]->maxusage); + memset(report->field[0]->value, 0, + sizeof(__s32) * report->field[0]->report_count); switch (effect->type) { case FF_CONSTANT: @@ -129,32 +130,14 @@ static const signed short ff3_joystick_ac[] = { int lg3ff_init(struct hid_device *hid) { struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct input_dev *dev = hidinput->input; - struct hid_report *report; - struct hid_field *field; const signed short *ff_bits = ff3_joystick_ac; int error; int i; - /* Find the report to use */ - if (list_empty(report_list)) { - hid_err(hid, "No output report found\n"); - return -1; - } - /* Check that the report looks ok */ - report = list_entry(report_list->next, struct hid_report, list); - if (!report) { - hid_err(hid, "NULL output report\n"); - return -1; - } - - field = report->field[0]; - if (!field) { - hid_err(hid, "NULL field\n"); - return -1; - } + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 35)) + return -ENODEV; /* Assume single fixed device G940 */ for (i = 0; ff_bits[i] >= 0; i++) diff --git a/drivers/hid/hid-lg4ff.c b/drivers/hid/hid-lg4ff.c index 0ddae2a00d5..8782fe1aaa0 100644 --- a/drivers/hid/hid-lg4ff.c +++ b/drivers/hid/hid-lg4ff.c @@ -484,34 +484,16 @@ static enum led_brightness lg4ff_led_get_brightness(struct led_classdev *led_cde int lg4ff_init(struct hid_device *hid) { struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct input_dev *dev = hidinput->input; - struct hid_report *report; - struct hid_field *field; struct lg4ff_device_entry *entry; struct lg_drv_data *drv_data; struct usb_device_descriptor *udesc; int error, i, j; __u16 bcdDevice, rev_maj, rev_min; - /* Find the report to use */ - if (list_empty(report_list)) { - hid_err(hid, "No output report found\n"); - return -1; - } - /* Check that the report looks ok */ - report = list_entry(report_list->next, struct hid_report, list); - if (!report) { - hid_err(hid, "NULL output report\n"); + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) return -1; - } - - field = report->field[0]; - if (!field) { - hid_err(hid, "NULL field\n"); - return -1; - } /* Check what wheel has been connected */ for (i = 0; i < ARRAY_SIZE(lg4ff_devices); i++) { diff --git a/drivers/hid/hid-lgff.c b/drivers/hid/hid-lgff.c index d7ea8c845b4..e1394af0ae7 100644 --- a/drivers/hid/hid-lgff.c +++ b/drivers/hid/hid-lgff.c @@ -128,27 +128,14 @@ static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude) int lgff_init(struct hid_device* hid) { struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct input_dev *dev = hidinput->input; - struct hid_report *report; - struct hid_field *field; const signed short *ff_bits = ff_joystick; int error; int i; - /* Find the report to use */ - if (list_empty(report_list)) { - hid_err(hid, "No output report found\n"); - return -1; - } - /* Check that the report looks ok */ - report = list_entry(report_list->next, struct hid_report, list); - field = report->field[0]; - if (!field) { - hid_err(hid, "NULL field\n"); - return -1; - } + if (!hid_validate_values(hid, HID_OUTPUT_REPORT, 0, 0, 7)) + return -ENODEV; for (i = 0; i < ARRAY_SIZE(devices); i++) { if (dev->id.vendor == devices[i].idVendor && diff --git a/drivers/hid/hid-logitech-dj.c b/drivers/hid/hid-logitech-dj.c index 7800b141056..2e5302462ef 100644 --- a/drivers/hid/hid-logitech-dj.c +++ b/drivers/hid/hid-logitech-dj.c @@ -461,7 +461,7 @@ static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev, struct hid_report *report; struct hid_report_enum *output_report_enum; u8 *data = (u8 *)(&dj_report->device_index); - int i; + unsigned int i; output_report_enum = &hdev->report_enum[HID_OUTPUT_REPORT]; report = output_report_enum->report_id_hash[REPORT_ID_DJ_SHORT]; @@ -471,7 +471,7 @@ static int logi_dj_recv_send_report(struct dj_receiver_dev *djrcv_dev, return -ENODEV; } - for (i = 0; i < report->field[0]->report_count; i++) + for (i = 0; i < DJREPORT_SHORT_LENGTH - 1; i++) report->field[0]->value[i] = data[i]; hid_hw_request(hdev, report, HID_REQ_SET_REPORT); @@ -791,6 +791,12 @@ static int logi_dj_probe(struct hid_device *hdev, goto hid_parse_fail; } + if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, REPORT_ID_DJ_SHORT, + 0, DJREPORT_SHORT_LENGTH - 1)) { + retval = -ENODEV; + goto hid_parse_fail; + } + /* Starts the usb device and connects to upper interfaces hiddev and * hidraw */ retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index ac28f08c386..5e5fe1b8eeb 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -101,9 +101,9 @@ struct mt_device { unsigned last_slot_field; /* the last field of a slot */ unsigned mt_report_id; /* the report ID of the multitouch device */ unsigned pen_report_id; /* the report ID of the pen device */ - __s8 inputmode; /* InputMode HID feature, -1 if non-existent */ - __s8 inputmode_index; /* InputMode HID feature index in the report */ - __s8 maxcontact_report_id; /* Maximum Contact Number HID feature, + __s16 inputmode; /* InputMode HID feature, -1 if non-existent */ + __s16 inputmode_index; /* InputMode HID feature index in the report */ + __s16 maxcontact_report_id; /* Maximum Contact Number HID feature, -1 if non-existent */ __u8 num_received; /* how many contacts we received */ __u8 num_expected; /* expected last contact index */ @@ -312,20 +312,18 @@ static void mt_feature_mapping(struct hid_device *hdev, struct hid_field *field, struct hid_usage *usage) { struct mt_device *td = hid_get_drvdata(hdev); - int i; switch (usage->hid) { case HID_DG_INPUTMODE: - td->inputmode = field->report->id; - td->inputmode_index = 0; /* has to be updated below */ - - for (i=0; i < field->maxusage; i++) { - if (field->usage[i].hid == usage->hid) { - td->inputmode_index = i; - break; - } + /* Ignore if value index is out of bounds. */ + if (usage->usage_index >= field->report_count) { + dev_err(&hdev->dev, "HID_DG_INPUTMODE out of range\n"); + break; } + td->inputmode = field->report->id; + td->inputmode_index = usage->usage_index; + break; case HID_DG_CONTACTMAX: td->maxcontact_report_id = field->report->id; @@ -511,6 +509,10 @@ static int mt_touch_input_mapping(struct hid_device *hdev, struct hid_input *hi, mt_store_field(usage, td, hi); return 1; case HID_DG_CONTACTCOUNT: + /* Ignore if indexes are out of bounds. */ + if (field->index >= field->report->maxfield || + usage->usage_index >= field->report_count) + return 1; td->cc_index = field->index; td->cc_value_index = usage->usage_index; return 1; diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 30dbb6b40bb..b18320db5f7 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -537,6 +537,10 @@ static int buzz_init(struct hid_device *hdev) drv_data = hid_get_drvdata(hdev); BUG_ON(!(drv_data->quirks & BUZZ_CONTROLLER)); + /* Validate expected report characteristics. */ + if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 7)) + return -ENODEV; + buzz = kzalloc(sizeof(*buzz), GFP_KERNEL); if (!buzz) { hid_err(hdev, "Insufficient memory, cannot allocate driver data\n"); diff --git a/drivers/hid/hid-steelseries.c b/drivers/hid/hid-steelseries.c index d1649119211..29f328f411f 100644 --- a/drivers/hid/hid-steelseries.c +++ b/drivers/hid/hid-steelseries.c @@ -249,6 +249,11 @@ static int steelseries_srws1_probe(struct hid_device *hdev, goto err_free; } + if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 0, 0, 16)) { + ret = -ENODEV; + goto err_free; + } + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (ret) { hid_err(hdev, "hw start failed\n"); diff --git a/drivers/hid/hid-zpff.c b/drivers/hid/hid-zpff.c index 6ec28a37c14..a29756c6ca0 100644 --- a/drivers/hid/hid-zpff.c +++ b/drivers/hid/hid-zpff.c @@ -68,21 +68,13 @@ static int zpff_init(struct hid_device *hid) struct hid_report *report; struct hid_input *hidinput = list_entry(hid->inputs.next, struct hid_input, list); - struct list_head *report_list = - &hid->report_enum[HID_OUTPUT_REPORT].report_list; struct input_dev *dev = hidinput->input; - int error; + int i, error; - if (list_empty(report_list)) { - hid_err(hid, "no output report found\n"); - return -ENODEV; - } - - report = list_entry(report_list->next, struct hid_report, list); - - if (report->maxfield < 4) { - hid_err(hid, "not enough fields in report\n"); - return -ENODEV; + for (i = 0; i < 4; i++) { + report = hid_validate_values(hid, HID_OUTPUT_REPORT, 0, i, 1); + if (!report) + return -ENODEV; } zpff = kzalloc(sizeof(struct zpff_device), GFP_KERNEL); |