VegarLH / google-security-research

Automatically exported from code.google.com/p/google-security-research
0 stars 0 forks source link

Chrome heap overflow in Linux HID device handler #407

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Heap overflow due to 64-32 integer truncation issue in 
device/hid/hid_connection_linux.cc

The following code treats the size_t values retrieved from the USB HID report 
descriptor
inconsistently, resulting in a heap overflow due to an integer truncation issue 
on 64-bit
builds.

device/hid/hid_connection_linux.cc

// base::MessagePumpLibevent::Watcher implementation.
  void OnFileCanReadWithoutBlocking(int fd) override {
    DCHECK(thread_checker_.CalledOnValidThread());
    DCHECK_EQ(fd, platform_file_);

    scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_)); // <---- TRUNCATION
    char* data = buffer->data();                                                 // <---- Note here...
    size_t length = report_buffer_size_;                                         // <---- NO TRUNCATION
    if (!has_report_id_) {
      // Linux will not prefix the buffer with a report ID if report IDs are not
      // used by the device. Prefix the buffer with 0.
      *data++ = 0;
      length--;
    }

    ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length));       // <---- OVERFLOW
    if (bytes_read < 0) {
      if (errno != EAGAIN) {
        HID_PLOG(EVENT) << "Read failed";
        // This assumes that the error is unrecoverable and disables reading
        // from the device until it has been re-opened.
        // TODO(reillyg): Investigate starting and stopping the file descriptor
        // watcher in response to pending read requests so that per-request
        // errors can be returned to the client.
        file_watcher_.StopWatchingFileDescriptor();
      }
      return;
    }
    if (!has_report_id_) {
      // Behave as if the byte prefixed above as the the report ID was read.
      bytes_read++;
    }

    task_runner_->PostTask(FROM_HERE,
                           base::Bind(&HidConnectionLinux::ProcessInputReport,
                                      connection_, buffer, bytes_read));
  }

So, we need to reach OnFileCanReadWithoutBlocking with a value above
0xffffffff in report_buffer_size_, which is only set in one place:

  FileThreadHelper(base::PlatformFile platform_file,
                   scoped_refptr<HidDeviceInfo> device_info,
                   base::WeakPtr<HidConnectionLinux> connection,
                   scoped_refptr<base::SingleThreadTaskRunner> task_runner)
      : platform_file_(platform_file),
        connection_(connection),
        task_runner_(task_runner) {
    // Report buffers must always have room for the report ID.
    report_buffer_size_ = device_info->max_input_report_size() + 1; // <---- Need to control device_info->max_input_report_size()
    has_report_id_ = device_info->has_report_id();
  }

This is a member variable of the HidDeviceInfo, which is set in two places; one
which appears uninteresting, as the argument to the constructor; and in the 
second where it is parsed from the device info returned from the HID device.

device/hid/hid_device_info.cc

HidDeviceInfo::HidDeviceInfo(const HidDeviceId& device_id,
                             uint16_t vendor_id,
                             uint16_t product_id,
                             const std::string& product_name,
                             const std::string& serial_number,
                             HidBusType bus_type,
                             const std::vector<uint8> report_descriptor)
    : device_id_(device_id),
      vendor_id_(vendor_id),
      product_id_(product_id),
      product_name_(product_name),
      serial_number_(serial_number),
      bus_type_(bus_type),
      report_descriptor_(report_descriptor) {
  HidReportDescriptor descriptor_parser(report_descriptor_);
  descriptor_parser.GetDetails(
      &collections_, &has_report_id_, &max_input_report_size_,
      &max_output_report_size_, &max_feature_report_size_);
}

So we need to look at the following code, parts snipped for brevity

device/hid/hid_report_descriptor.cc

void HidReportDescriptor::GetDetails(
    std::vector<HidCollectionInfo>* top_level_collections,
    bool* has_report_id,
    size_t* max_input_report_size,
    size_t* max_output_report_size,
    size_t* max_feature_report_size) {
  DCHECK(top_level_collections);
  DCHECK(max_input_report_size);
  DCHECK(max_output_report_size);
  DCHECK(max_feature_report_size);
  STLClearObject(top_level_collections);

  *has_report_id = false;
  *max_input_report_size = 0;
  *max_output_report_size = 0;
  *max_feature_report_size = 0;

  // Global tags data:
  HidUsageAndPage::Page current_usage_page = HidUsageAndPage::kPageUndefined;
  size_t current_report_count = 0;
  size_t cached_report_count = 0;
  size_t current_report_size = 0;
  size_t cached_report_size = 0;
  size_t current_input_report_size = 0;
  size_t current_output_report_size = 0;
  size_t current_feature_report_size = 0;

  // Local tags data:
  uint32_t current_usage = 0;

  for (std::vector<linked_ptr<HidReportDescriptorItem> >::const_iterator
           items_iter = items().begin();
       items_iter != items().end();
       ++items_iter) {
    linked_ptr<HidReportDescriptorItem> current_item = *items_iter;

    switch (current_item->tag()) {

...

      case HidReportDescriptorItem::kTagInput:
        current_input_report_size += current_report_count * current_report_size; // <---- We control these two values
        break;

...

      case HidReportDescriptorItem::kTagReportId:
        if (top_level_collections->size() > 0) {
          // Store report ID.
          top_level_collections->back().report_ids.insert(
              current_item->GetShortData());
          *has_report_id = true;

          // Update max report sizes.
          *max_input_report_size =
              std::max(*max_input_report_size, current_input_report_size);
          *max_output_report_size =
              std::max(*max_output_report_size, current_output_report_size);
          *max_feature_report_size =
              std::max(*max_feature_report_size, current_feature_report_size);

          // Reset the report sizes for the next report ID.
          current_input_report_size = 0;
          current_output_report_size = 0;
          current_feature_report_size = 0;
        }
        break;
      case HidReportDescriptorItem::kTagReportCount:
        current_report_count = current_item->GetShortData();
        break;
      case HidReportDescriptorItem::kTagReportSize:
        current_report_size = current_item->GetShortData();
        break;

...

    }
  }

  // Update max report sizes
  *max_input_report_size =
      std::max(*max_input_report_size, current_input_report_size);
  *max_output_report_size =
      std::max(*max_output_report_size, current_output_report_size);
  *max_feature_report_size =
      std::max(*max_feature_report_size, current_feature_report_size);

  // Convert bits into bytes
  *max_input_report_size /= kBitsPerByte;
  *max_output_report_size /= kBitsPerByte;
  *max_feature_report_size /= kBitsPerByte;
}

So it would appear that to hit this case, we can provide a device with 
descriptor 
setting an input report count 0x800001, input report size of 0x1000

To verify that this condition can be met, I've added a test to 
hid_report_descriptor_unittest.cc;
see attached patch.

out/Release/device_unittests 
--gtest_filter=HidReportDescriptorTest.ValidateDetails_Overflow 
IMPORTANT DEBUGGING NOTE: batches of tests are run inside their
own process. For debugging a test inside a debugger, use the
--gtest_filter=<your_test_name> flag along with
--single-process-tests.
Using sharding settings from environment. This is shard 0/1
Using 1 parallel jobs.
Note: Google Test filter = HidReportDescriptorTest.ValidateDetails_Overflow
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from HidReportDescriptorTest
[ RUN      ] HidReportDescriptorTest.ValidateDetails_Overflow
kTagInput: count 0000000000000006 size 0000000000000008
kTagInput: count 0000000000000013 size 0000000000000008
kTagInput: count 000000000000000e size 0000000000001000
kTagInput: count 0000000000800001 size 0000000000001000
max input report 100000200     <---- Gives us an allocation of 0x200 bytes, 
into which we will try to read 0x100000200 bytes
max output report 100000200
max feature report 0

I haven't followed through to determine the exact route from here to trigger 
the read condition,
since I don't have a setup for USB device emulation.

See https://code.google.com/p/chromium/issues/detail?id=491212 for the chromium 
issue.

This bug is subject to a 90 day disclosure deadline. If 90 days elapse
without a broadly available patch, then the bug report will automatically
become visible to the public.

Original issue reported on code.google.com by markbr...@google.com on 22 May 2015 at 4:33

Attachments:

GoogleCodeExporter commented 9 years ago
See also related issues (not believed exploitable at present, so no separate 
bugs in this tracker):

https://code.google.com/p/chromium/issues/detail?id=491216
https://code.google.com/p/chromium/issues/detail?id=491218
https://code.google.com/p/chromium/issues/detail?id=491220
https://code.google.com/p/chromium/issues/detail?id=491222
https://code.google.com/p/chromium/issues/detail?id=491224
https://code.google.com/p/chromium/issues/detail?id=491227
https://code.google.com/p/chromium/issues/detail?id=491229
https://code.google.com/p/chromium/issues/detail?id=491231
https://code.google.com/p/chromium/issues/detail?id=491232

Original comment by markbr...@google.com on 22 May 2015 at 5:51

GoogleCodeExporter commented 9 years ago
Chrome are tracking fixes for all of these under 
https://code.google.com/p/chromium/issues/detail?id=491216

Original comment by markbr...@google.com on 26 May 2015 at 8:59

GoogleCodeExporter commented 9 years ago
Fixes released in M44; derestricting.

Original comment by markbr...@google.com on 18 Aug 2015 at 2:47