diff --git a/components/storage_monitor/BUILD.gn b/components/storage_monitor/BUILD.gn index cf0dd936c6cbe..6de5333d277b8 100644 --- a/components/storage_monitor/BUILD.gn +++ b/components/storage_monitor/BUILD.gn @@ -3,6 +3,7 @@ # found in the LICENSE file. import("//build/config/features.gni") +import("//testing/test.gni") component("storage_monitor") { sources = [ @@ -173,3 +174,19 @@ source_set("unit_tests") { } } } + +# Minimal standalone test executable for the EjectDevice desync POC, so it +# can be built without pulling in all of components_unittests. +if (is_linux && use_udev) { + test("storage_monitor_linux_poc_test") { + sources = [ "storage_monitor_linux_unittest.cc" ] + deps = [ + ":storage_monitor", + ":test_support", + "//base", + "//base/test:run_all_unittests", + "//base/test:test_support", + "//testing/gtest", + ] + } +} diff --git a/components/storage_monitor/storage_monitor_linux_unittest.cc b/components/storage_monitor/storage_monitor_linux_unittest.cc index cbba52f3d1414..0d65a6aaf3ed2 100644 --- a/components/storage_monitor/storage_monitor_linux_unittest.cc +++ b/components/storage_monitor/storage_monitor_linux_unittest.cc @@ -683,6 +683,66 @@ TEST_F(StorageMonitorLinuxTest, DevicePartitionSize) { GetStorageSize(base::FilePath(kInvalidPath))); } +// POC: EjectDevice() erases mount_info_map_[A] but leaves the stale +// mount_priority_map_[D] = {A:true} entry. When the same /dev/sdX is later +// re-mounted at a DIFFERENT mount point B (the OS auto-mount picked a new +// path, or the volume label changed), UpdateMtab() sees B as a new mount, +// IsDeviceAlreadyMounted(D) returns true from the stale priority entry, and +// HandleDeviceMountedMultipleTimes() executes: +// +// other_mount_point = priority->second.begin()->first; // == A (stale) +// priority->second[B] = false; +// mount_info_map_[B] = mount_info_map_.find(A)->second; // end()->second +// +// find(A) returns end() regardless of [B] / find(A) evaluation order, so the +// end() iterator is unconditionally dereferenced. libc++ __tree_iterator has +// no hardening assertion under _LIBCPP_HARDENING_MODE_EXTENSIVE; it +// static_casts the inline __tree end-node (embedded in the heap-allocated +// StorageMonitorLinux singleton) to a full __tree_node and reads +// __value_.second -- an OOB heap read of sizeof(MountPointInfo)==152 bytes +// starting ~56 bytes past the embedded end-node, extending past the end of +// the StorageMonitorLinux heap allocation. +TEST_F(StorageMonitorLinuxTest, EjectDeviceDesyncEndIteratorDeref) { + base::FilePath test_path_a = CreateMountPointWithDCIMDir(kMountPointA); + base::FilePath test_path_b = CreateMountPointWithDCIMDir(kMountPointB); + ASSERT_FALSE(test_path_a.empty()); + ASSERT_FALSE(test_path_b.empty()); + + // Step 1: removable device d1 is auto-mounted at A. + // mount_info_map_ = { A : {d1, info} } + // mount_priority_map_ = { d1 : { A : true } } + MtabTestData mounted_at_a[] = { + MtabTestData(kDeviceDCIM1, test_path_a.value(), kValidFS), + }; + AppendToMtabAndRunLoop(mounted_at_a); + ASSERT_EQ(1, observer().attach_calls()); + + // Step 2: extension calls chrome.system.storage.ejectDevice() -> + // StorageMonitorLinux::EjectDevice(). This synchronously erases + // mount_info_map_[A] (storage_monitor_linux.cc:284) but does NOT touch + // mount_priority_map_. Maps are now desynced: + // mount_info_map_ = { } + // mount_priority_map_ = { d1 : { A : true } } <-- stale + const std::string device_id = GetDeviceId(kDeviceDCIM1); + notifier()->EjectDevice( + device_id, base::BindOnce([](StorageMonitor::EjectStatus) {})); + + // Step 3: the same /dev/sdX is re-inserted; the OS auto-mounts it at a + // DIFFERENT path B. MtabWatcherLinux fires UpdateMtab({B -> d1}): + // - first loop iterates mount_info_map_ (empty) -> never reaps stale entry + // - second loop: mount_info_map_.find(B) == end() + // IsDeviceAlreadyMounted(d1) == true (stale priority entry) + // - HandleDeviceMountedMultipleTimes(d1, B): + // other_mount_point = A (stale) + // mount_info_map_[B] = mount_info_map_.find(A)->second; // end()->second + // + // *** OOB heap read of 152 bytes past the std::map end-node here. *** + MtabTestData mounted_at_b[] = { + MtabTestData(kDeviceDCIM1, test_path_b.value(), kValidFS), + }; + OverwriteMtabAndRunLoop(mounted_at_b); +} + } // namespace } // namespace storage_monitor