HID: core: prevent out-of-bound readings
[pandora-kernel.git] / drivers / hid / hid-core.c
index 611aafc..1ec8825 100644 (file)
@@ -59,6 +59,8 @@ struct hid_report *hid_register_report(struct hid_device *device, unsigned type,
        struct hid_report_enum *report_enum = device->report_enum + type;
        struct hid_report *report;
 
+       if (id >= HID_MAX_IDS)
+               return NULL;
        if (report_enum->report_id_hash[id])
                return report_enum->report_id_hash[id];
 
@@ -216,9 +218,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) {
@@ -237,7 +239,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)
@@ -248,7 +251,7 @@ 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;
@@ -380,8 +383,10 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
 
        case HID_GLOBAL_ITEM_TAG_REPORT_ID:
                parser->global.report_id = item_udata(item);
-               if (parser->global.report_id == 0) {
-                       dbg_hid("report_id 0 is invalid\n");
+               if (parser->global.report_id == 0 ||
+                   parser->global.report_id >= HID_MAX_IDS) {
+                       dbg_hid("report_id %u is invalid\n",
+                               parser->global.report_id);
                        return -1;
                }
                return 0;
@@ -552,7 +557,7 @@ static void hid_device_release(struct device *dev)
        for (i = 0; i < HID_REPORT_TYPES; i++) {
                struct hid_report_enum *report_enum = device->report_enum + i;
 
-               for (j = 0; j < 256; j++) {
+               for (j = 0; j < HID_MAX_IDS; j++) {
                        struct hid_report *report = report_enum->report_id_hash[j];
                        if (report)
                                hid_free_report(report);
@@ -710,6 +715,74 @@ err:
 }
 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.
+        */
+       if (id == 0) {
+               /*
+                * Validating on id 0 means we should examine the first
+                * report in the list.
+                */
+               report = list_entry(
+                               hid->report_enum[type].report_list.next,
+                               struct hid_report, list);
+       } else {
+               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);
+
 /*
  * Convert a signed n-bit integer to signed 32-bit integer. Common
  * cases are done through the compiler, the screwed things has to be
@@ -916,6 +989,7 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field,
                /* Ignore report if ErrorRollOver */
                if (!(field->flags & HID_MAIN_ITEM_VARIABLE) &&
                    value[n] >= min && value[n] <= max &&
+                   value[n] - min < field->maxusage &&
                    field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1)
                        goto exit;
        }
@@ -928,11 +1002,13 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field,
                }
 
                if (field->value[n] >= min && field->value[n] <= max
+                       && field->value[n] - min < field->maxusage
                        && field->usage[field->value[n] - min].hid
                        && search(value, field->value[n], count))
                                hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, interrupt);
 
                if (value[n] >= min && value[n] <= max
+                       && value[n] - min < field->maxusage
                        && field->usage[value[n] - min].hid
                        && search(field->value, value[n], count))
                                hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt);
@@ -990,7 +1066,12 @@ EXPORT_SYMBOL_GPL(hid_output_report);
 
 int hid_set_field(struct hid_field *field, unsigned offset, __s32 value)
 {
-       unsigned size = field->report_size;
+       unsigned size;
+
+       if (!field)
+               return -1;
+
+       size = field->report_size;
 
        hid_dump_input(field->report->device, field->usage + offset, value);
 
@@ -1217,7 +1298,7 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
                "Multi-Axis Controller"
        };
        const char *type, *bus;
-       char buf[64];
+       char buf[64] = "";
        unsigned int i;
        int len;
        int ret;