diff --git a/ui/events/devices/x11/device_data_manager_x11.cc b/ui/events/devices/x11/device_data_manager_x11.cc index 0447e08d6c31a..d783f5291397e 100644 --- a/ui/events/devices/x11/device_data_manager_x11.cc +++ b/ui/events/devices/x11/device_data_manager_x11.cc @@ -721,6 +721,17 @@ void DeviceDataManagerX11::SetValuatorDataForTest( *valuators = DoubleToFp3232(value); } +void DeviceDataManagerX11::SetValuatorCountForTest( + x11::Input::DeviceId deviceid, + int count) { + valuator_count_[deviceid] = count; + // In production UpdateDeviceList() resizes data_type_lookup_[dev] to + // valuator_count_[dev]; size it generously here so that the *first* + // OOB ASAN catches is the valuator_mask read, not a secondary access. + data_type_lookup_[deviceid].assign(count + 8, DT_LAST_ENTRY); + valuator_lookup_[deviceid].resize(DT_LAST_ENTRY); +} + void DeviceDataManagerX11::InitializeValuatorsForTest( x11::Input::DeviceId deviceid, int start_valuator, diff --git a/ui/events/devices/x11/device_data_manager_x11.h b/ui/events/devices/x11/device_data_manager_x11.h index 5e14481343aa5..148d814f184d9 100644 --- a/ui/events/devices/x11/device_data_manager_x11.h +++ b/ui/events/devices/x11/device_data_manager_x11.h @@ -269,6 +269,11 @@ class EVENTS_DEVICES_X11_EXPORT DeviceDataManagerX11 DataType type, double value); + // Test-only: simulate the per-device state that UpdateDeviceList() would + // produce after receiving a (malicious) XIQueryDevice reply advertising + // |count| ValuatorClass entries on |deviceid|. + void SetValuatorCountForTest(x11::Input::DeviceId deviceid, int count); + // Sets the keys which are still allowed on a disabled keyboard device. void SetDisabledKeyboardAllowedKeys( std::unique_ptr> excepted_keys); diff --git a/ui/ozone/platform/x11/test/device_data_manager_x11_unittest.cc b/ui/ozone/platform/x11/test/device_data_manager_x11_unittest.cc index 15bbd53c35ffe..afa49a93381ab 100644 --- a/ui/ozone/platform/x11/test/device_data_manager_x11_unittest.cc +++ b/ui/ozone/platform/x11/test/device_data_manager_x11_unittest.cc @@ -13,6 +13,8 @@ #include "ui/events/devices/input_device_event_observer.h" #include "ui/events/devices/keyboard_device.h" #include "ui/events/devices/touchscreen_device.h" +#include "ui/gfx/x/event.h" +#include "ui/gfx/x/xinput.h" namespace ui { namespace test { @@ -176,5 +178,45 @@ TEST_F(DeviceDataManagerX11Test, UnblockOnDeviceUnplugged) { EXPECT_EQ(2u, devices.size()); } +// POC: heap-buffer-overflow when a malicious X server advertises more +// ValuatorClass entries (XIQueryDevice) than the per-event valuator_mask +// covers. GetEventRawData()/GetScrollClassOffsets() iterate to +// valuator_count_[sourceid] and call IsXinputMaskSet(mask.data(), i) with +// i >= mask.size()*32, reading past the heap allocation backing +// xiev->valuator_mask. Each spuriously-set OOB bit then advances +// valuators_iter past axisvalues.end() and Fp3232ToDouble(*valuators_iter) +// reads 8 OOB heap bytes. +TEST_F(DeviceDataManagerX11Test, ValuatorMaskShorterThanValuatorCount_OOB) { + DeviceDataManagerX11* manager = DeviceDataManagerX11::GetInstance(); + + // --- Step 1: simulate malicious XIQueryDevice reply ---------------------- + // A malicious X server advertises a SlavePointer with 40 ValuatorClass + // entries. UpdateDeviceList() would set valuator_count_[dev] = 40 with no + // upper clamp. We use a test hook because UpdateDeviceList() talks to the + // real X server; the resulting state is bit-identical. + const auto dev = static_cast(5); + manager->SetValuatorCountForTest(dev, 40); + + // --- Step 2: malicious XI_Motion DeviceEvent ---------------------------- + // valuators_len = 1 → valuator_mask is a single uint32 (4-byte heap alloc). + // bits 0,1 set → xproto parser (xinput.cc:876-878) yields + // axisvalues.size() == popcount(mask) == 2 (16-byte heap alloc). + x11::Input::DeviceEvent ev; + ev.opcode = x11::Input::DeviceEvent::Motion; + ev.deviceid = dev; + ev.sourceid = dev; + ev.valuator_mask = std::vector{0x00000003u}; + ev.axisvalues = std::vector{{0, 0}, {0, 0}}; + ev.button_mask = std::vector{0u, 0u}; + x11::Event x11_event(false, std::move(ev)); + + // --- Step 3: trigger ---------------------------------------------------- + // GetEventRawData loops i = 0..40. For i >= 32 IsXinputMaskSet() reads + // byte offset i/8 >= 4 of the 4-byte valuator_mask allocation. + // ASAN: heap-buffer-overflow READ of size 1. + DeviceDataManagerX11::EventData data; + manager->GetEventRawData(x11_event, &data); +} + } // namespace test } // namespace ui