|
|
Chrome heap overflow in Linux HID device handler | |
| Project Member Reported by markbrand@google.com, May 22 2015 | Back to list | |
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.
Project Member
Comment 1
by
markbrand@google.com,
May 22 2015
,
May 26 2015
Chrome are tracking fixes for all of these under https://code.google.com/p/chromium/issues/detail?id=491216
,
Aug 18 2015
Fixes released in M44; derestricting. |
||
| ► Sign in to add a comment | ||