Chrome · CameraCapture
CVE-2026-14090
OOB in CameraCapture
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
forservices/video_capture/test/video_capture_service_unittest.cc |
modified | |
ifservices/video_capture/test/video_capture_service_unittest.cc |
modified | |
TEST_Fservices/video_capture/test/video_capture_service_unittest.cc |
modified |
Files Changed
services/video_capture/test/video_capture_service_unittest.cc
Patch
From f5f020cf8f3edc1924e6b2da39b1587186e55170 Mon Sep 17 00:00:00 2001 From: Markus Handell <[email protected]> Date: Tue, 19 May 2026 12:56:16 -0700 Subject: [PATCH] Capture: prevent virtual devices shadowing real ones. Bug: 513194241, 513186670 Change-Id: Ie5827d50767ae0eb7845329b8111747e65e3f2a1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7852499 Reviewed-by: Ilya Nikolaevskiy <[email protected]> Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Markus Handell <[email protected]> Cr-Commit-Position: refs/heads/main@{#1633066} --- diff --git a/services/video_capture/test/video_capture_service_unittest.cc b/services/video_capture/test/video_capture_service_unittest.cc index 66110d17..d6d00d1 100644 --- a/services/video_capture/test/video_capture_service_unittest.cc +++ b/services/video_capture/test/video_capture_service_unittest.cc @@ -211,7 +211,6 @@ create_push_subscription_remote_callback; EXPECT_CALL(create_push_subscription_remote_callback, Run) - .Times(1) .WillOnce( [&wait_loop](mojom::CreatePushSubscriptionResultCodePtr result_code, const media::VideoCaptureParams& param) { @@ -231,4 +230,179 @@ wait_loop.Run(); } +// Tests that a virtual device cannot be added with the same ID as an active +// physical device. +TEST_F(VideoCaptureServiceTest, + CannotAddVirtualDeviceWhilePhysicalDeviceActive) { + // Retrieve the active physical devices. + base::RunLoop get_infos_loop; + std::vector<media::VideoCaptureDeviceInfo> physical_devices; + EXPECT_CALL(device_info_receiver_, Run) + .WillOnce([&get_infos_loop, &physical_devices]( + GetSourceInfosResult result, + const std::vector<media::VideoCaptureDeviceInfo>& infos) { + physical_devices = infos; + get_infos_loop.Quit(); + }); + video_source_provider_->GetSourceInfos(device_info_receiver_.Get()); + get_infos_loop.Run(); + const std::string physical_device_id = + physical_devices[0].descriptor.device_id; + + // Start the physical device by creating a subscription. + mojo::Remote<mojom::VideoSource> video_source_remote; + video_source_provider_->GetVideoSource( + physical_device_id, video_source_remote.BindNewPipeAndPassReceiver()); + + mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber; + MockVideoFrameHandler mock_video_frame_handler( + subscriber.InitWithNewPipeAndPassReceiver()); + mojo::Remote<video_capture::mojom::PushVideoStreamSubscription> subscription; + + base::RunLoop start_device_loop; + base::MockCallback<mojom::VideoSource::CreatePushSubscriptionCallback> + create_push_subscription_callback; + EXPECT_CALL(create_push_subscription_callback, Run) + .WillOnce([&start_device_loop]( + mojom::CreatePushSubscriptionResultCodePtr result_code, + const media::VideoCaptureParams& param) { + start_device_loop.Quit(); + }); + + video_source_remote->CreatePushSubscription( + std::move(subscriber), requestable_settings_, + /*force_reopen_with_new_settings=*/false, + subscription.BindNewPipeAndPassReceiver(), + create_push_subscription_callback.Get()); + start_device_loop.Run(); + + // Attempt to add a virtual device with the same ID. + auto virtual_device_context = + AddSharedMemoryVirtualDevice(physical_device_id); + + // Verify that the virtual device is rejected and its pipe is disconnected. + base::RunLoop disconnect_loop; + virtual_device_context->device.set_disconnect_handler( + disconnect_loop.QuitClosure()); + disconnect_loop.Run(); + + EXPECT_FALSE(virtual_device_context->device.is_connected()); + + // Verify that GetSourceInfos() returns ONLY 1 entry for the device ID + // (registration failed). + base::RunLoop verify_infos_loop; + EXPECT_CALL(device_info_receiver_, Run) + .WillOnce([&verify_infos_loop, physical_device_id]( + GetSourceInfosResult result, + const std::vector<media::VideoCaptureDeviceInfo>& infos) { + size_t occurrences = 0; + for (const auto& info : infos) { + if (info.descriptor.device_id == physical_device_id) { + occurrences++; + } + } + EXPECT_EQ(1u, occurrences); + verify_infos_loop.Quit(); + }); + video_source_provider_->GetSourceInfos(device_info_receiver_.Get()); + verify_infos_loop.Run(); + + // Cleanup + subscription.reset(); + video_source_remote.reset(); + base::RunLoop cleanup_loop; + video_source_provider_->GetSourceInfos(base::BindOnce( + [](base::RunLoop* run_loop, GetSourceInfosResult result, + const std::vector<media::VideoCaptureDeviceInfo>& infos) { + run_loop->Quit(); + }, + &cleanup_loop)); + cleanup_loop.Run(); +} + +// Tests that a virtual device cannot be added with the same ID as an idle +// physical device. +TEST_F(VideoCaptureServiceTest, CannotAddVirtualDeviceWhilePhysicalDeviceIdle) { + // Retrieve physical device IDs. + base::RunLoop get_infos_loop; + std::vector<media::VideoCaptureDeviceInfo> physical_devices; + EXPECT_CALL(device_info_receiver_, Run) + .WillOnce([&get_infos_loop, &physical_devices]( + GetSourceInfosResult result, + const std::vector<media::VideoCaptureDeviceInfo>& infos) { + physical_devices = infos; + get_infos_loop.Quit(); + }); + video_source_provider_->GetSourceInfos(device_info_receiver_.Get()); + get_infos_loop.Run(); + const std::string physical_device_id = + physical_devices[0].descriptor.device_id; + + // Add a virtual device with the same ID. + auto virtual_device_context = + AddSharedMemoryVirtualDevice(physical_device_id); + + // Verify that the virtual device is rejected and its pipe is disconnected. + base::RunLoop disconnect_loop; + virtual_device_context->device.set_disconnect_handler( + disconnect_loop.QuitClosure()); + disconnect_loop.Run(); + + EXPECT_FALSE(virtual_device_context->device.is_connected()); + + // Verify that GetSourceInfos() returns ONLY 1 entry for the device ID + // (registration failed). + base::RunLoop verify_infos_loop; + EXPECT_CALL(device_info_receiver_, Run) + .WillOnce([&verify_infos_loop, physical_device_id]( + GetSourceInfosResult result, + const std::vector<media::VideoCaptureDeviceInfo>& infos) { + size_t occurrences = 0; + for (const auto& info : infos) { + if (info.descriptor.device_id == physical_device_id) { + occurrences++; + } + } + EXPECT_EQ(1u, occurrences); + verify_infos_loop.Quit(); + }); + video_source_provider_->GetSourceInfos(device_info_receiver_.Get()); + verify_infos_loop.Run(); +} + +// Tests that a texture virtual device cannot be added with the same ID as an +// idle physical device. +TEST_F(VideoCaptureServiceTest, + CannotAddTextureVirtualDeviceWhilePhysicalDeviceIdle) { + // Retrieve physical device IDs. + base::RunLoop get_infos_loop; + std::vector<media::VideoCaptureDeviceInfo> physical_devices; + EXPECT_CALL(device_info_receiver_, Run) + .WillOnce([&get_infos_loop, &physical_devices]( + GetSourceInfosResult result, + const std::vector<media::VideoCaptureDeviceInfo>& infos) { + physical_devices = infos; + get_infos_loop.Quit(); + }); + video_source_provider_->GetSourceInfos(device_info_receiver_.Get()); + get_infos_loop.Run(); + + ASSERT_FALSE(physical_devices.empty()); + const std::string physical_device_id = + physical_devices[0].descriptor.device_id; + + // Add a texture virtual device with the same ID. + auto texture_device = AddTextureVirtualDevice(physical_device_id); + + // Verify that the texture virtual device is rejected and its pipe is + // disconnected. + mojo::Remote<mojom::TextureVirtualDevice> remote_device( + std::move(texture_device)); + base::RunLoop disconnect_loop; + remote_device.set_disconnect_handler(disconnect_loop.QuitClosure());
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/services/video_capture/test/video_capture_service_unittest.cc b/services/video_capture/test/video_capture_service_unittest.cc
index 66110d17..d6d00d1 100644
--- a/services/video_capture/test/video_capture_service_unittest.cc
+++ b/services/video_capture/test/video_capture_service_unittest.cc
@@ -211,7 +211,6 @@
create_push_subscription_remote_callback;
EXPECT_CALL(create_push_subscription_remote_callback, Run)
- .Times(1)
.WillOnce(
[&wait_loop](mojom::CreatePushSubscriptionResultCodePtr result_code,
const media::VideoCaptureParams& param) {
@@ -231,4 +230,179 @@
wait_loop.Run();
}
+// Tests that a virtual device cannot be added with the same ID as an active
+// physical device.
+TEST_F(VideoCaptureServiceTest,
+ CannotAddVirtualDeviceWhilePhysicalDeviceActive) {
+ // Retrieve the active physical devices.
+ base::RunLoop get_infos_loop;
+ std::vector<media::VideoCaptureDeviceInfo> physical_devices;
+ EXPECT_CALL(device_info_receiver_, Run)
+ .WillOnce([&get_infos_loop, &physical_devices](
+ GetSourceInfosResult result,
+ const std::vector<media::VideoCaptureDeviceInfo>& infos) {
+ physical_devices = infos;
+ get_infos_loop.Quit();
+ });
+ video_source_provider_->GetSourceInfos(device_info_receiver_.Get());
+ get_infos_loop.Run();
+ const std::string physical_device_id =
+ physical_devices[0].descriptor.device_id;
+
+ // Start the physical device by creating a subscription.
+ mojo::Remote<mojom::VideoSource> video_source_remote;
+ video_source_provider_->GetVideoSource(
+ physical_device_id, video_source_remote.BindNewPipeAndPassReceiver());
+
+ mojo::PendingRemote<video_capture::mojom::VideoFrameHandler> subscriber;
+ MockVideoFrameHandler mock_video_frame_handler(
+ subscriber.InitWithNewPipeAndPassReceiver());
+ mojo::Remote<video_capture::mojom::PushVideoStreamSubscription> subscription;
+
+ base::RunLoop start_device_loop;
+ base::MockCallback<mojom::VideoSource::CreatePushSubscriptionCallback>
+ create_push_subscription_callback;
+ EXPECT_CALL(create_push_subscription_callback, Run)
+ .WillOnce([&start_device_loop](
+ mojom::CreatePushSubscriptionResultCodePtr result_code,
+ const media::VideoCaptureParams& param) {
+ start_device_loop.Quit();
+ });
+
+ video_source_remote->CreatePushSubscription(
+ std::move(subscriber), requestable_settings_,
+ /*force_reopen_with_new_settings=*/false,
+ subscription.BindNewPipeAndPassReceiver(),
+ create_push_subscription_callback.Get());
+ start_device_loop.Run();
+
+ // Attempt to add a virtual device with the same ID.
+ auto virtual_device_context =
+ AddSharedMemoryVirtualDevice(physical_device_id);
+
+ // Verify that the virtual device is rejected and its pipe is disconnected.
+ base::RunLoop disconnect_loop;
+ virtual_device_context->device.set_disconnect_handler(
+ disconnect_loop.QuitClosure());
+ disconnect_loop.Run();
+
+ EXPECT_FALSE(virtual_device_context->device.is_connected());
+
+ // Verify that GetSourceInfos() returns ONLY 1 entry for the device ID
+ // (registration failed).
+ base::RunLoop verify_infos_loop;
+ EXPECT_CALL(device_info_receiver_, Run)
+ .WillOnce([&verify_infos_loop, physical_device_id](
+ GetSourceInfosResult result,
+ const std::vector<media::VideoCaptureDeviceInfo>& infos) {
+ size_t occurrences = 0;
+ for (const auto& info : infos) {
+ if (info.descriptor.device_id == physical_device_id) {
+ occurrences++;
+ }
+ }
+ EXPECT_EQ(1u, occurrences);
+ verify_infos_loop.Quit();
+ });
+ video_source_provider_->GetSourceInfos(device_info_receiver_.Get());
+ verify_infos_loop.Run();
+
+ // Cleanup
+ subscription.reset();
+ video_source_remote.reset();
+ base::RunLoop cleanup_loop;
+ video_source_provider_->GetSourceInfos(base::BindOnce(
+ [](base::RunLoop* run_loop, GetSourceInfosResult result,
+ const std::vector<media::VideoCaptureDeviceInfo>& infos) {
+ run_loop->Quit();
+ },
+ &cleanup_loop));
+ cleanup_loop.Run();
+}
+
+// Tests that a virtual device cannot be added with the same ID as an idle
+// physical device.
+TEST_F(VideoCaptureServiceTest, CannotAddVirtualDeviceWhilePhysicalDeviceIdle) {
+ // Retrieve physical device IDs.
+ base::RunLoop get_infos_loop;
+ std::vector<media::VideoCaptureDeviceInfo> physical_devices;
+ EXPECT_CALL(device_info_receiver_, Run)
+ .WillOnce([&get_infos_loop, &physical_devices](
+ GetSourceInfosResult result,
+ const std::vector<media::VideoCaptureDeviceInfo>& infos) {
+ physical_devices = infos;
+ get_infos_loop.Quit();
+ });
+ video_source_provider_->GetSourceInfos(device_info_receiver_.Get());
+ get_infos_loop.Run();
+ const std::string physical_device_id =
+ physical_devices[0].descriptor.device_id;
+
+ // Add a virtual device with the same ID.
+ auto virtual_device_context =
+ AddSharedMemoryVirtualDevice(physical_device_id);
+
+ // Verify that the virtual device is rejected and its pipe is disconnected.
+ base::RunLoop disconnect_loop;
+ virtual_device_context->device.set_disconnect_handler(
+ disconnect_loop.QuitClosure());
+ disconnect_loop.Run();
+
+ EXPECT_FALSE(virtual_device_context->device.is_connected());
+
+ // Verify that GetSourceInfos() returns ONLY 1 entry for the device ID
+ // (registration failed).
+ base::RunLoop verify_infos_loop;
+ EXPECT_CALL(device_info_receiver_, Run)
+ .WillOnce([&verify_infos_loop, physical_device_id](
+ GetSourceInfosResult result,
+ const std::vector<media::VideoCaptureDeviceInfo>& infos) {
+ size_t occurrences = 0;
+ for (const auto& info : infos) {
+ if (info.descriptor.device_id == physical_device_id) {
+ occurrences++;
+ }
+ }
+ EXPECT_EQ(1u, occurrences);
+ verify_infos_loop.Quit();
+ });
+ video_source_provider_->GetSourceInfos(device_info_receiver_.Get());
+ verify_infos_loop.Run();
+}
+
+// Tests that a texture virtual device cannot be added with the same ID as an
+// idle physical device.
+TEST_F(VideoCaptureServiceTest,
+ CannotAddTextureVirtualDeviceWhilePhysicalDeviceIdle) {
+ // Retrieve physical device IDs.
+ base::RunLoop get_infos_loop;
+ std::vector<media::VideoCaptureDeviceInfo> physical_devices;
+ EXPECT_CALL(device_info_receiver_, Run)
+ .WillOnce([&get_infos_loop, &physical_devices](
+ GetSourceInfosResult result,
+ const std::vector<media::VideoCaptureDeviceInfo>& infos) {
+ physical_devices = infos;
+ get_infos_loop.Quit();
+ });
+ video_source_provider_->GetSourceInfos(device_info_receiver_.Get());
+ get_infos_loop.Run();
+
+ ASSERT_FALSE(physical_devices.empty());
+ const std::string physical_device_id =
+ physical_devices[0].descriptor.device_id;
+
+ // Add a texture virtual device with the same ID.
+ auto texture_device = AddTextureVirtualDevice(physical_device_id);
+
+ // Verify that the texture virtual device is rejected and its pipe is
+ // disconnected.
+ mojo::Remote<mojom::TextureVirtualDevice> remote_device(
+ std::move(texture_device));
+ base::RunLoop disconnect_loop;
+ remote_device.set_disconnect_handler(disconnect_loop.QuitClosure());
+ disconnect_loop.Run();
+
+ EXPECT_FALSE(remote_device.is_connected());
+}
+
} // namespace video_capture
diff --git a/services/video_capture/test/virtual_device_unittest.cc b/services/video_capture/test/virtual_device_unittest.cc
index de54a5c..6c46aca 100644
--- a/services/video_capture/test/virtual_device_unittest.cc
+++ b/services/video_capture/test/virtual_device_unittest.cc
@@ -5,6 +5,7 @@
#include <algorithm>
#include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/test/mock_callback.h"
@@ -15,6 +16,7 @@
#include "services/video_capture/public/cpp/mock_video_frame_handler.h"
#include "services/video_capture/public/mojom/video_frame_handler.mojom.h"
#include "services/video_capture/shared_memory_virtual_device_mojo_adapter.h"
+#include "services/video_capture/virtual_device_enabled_device_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
@@ -159,7 +161,7 @@
base::MockCallback<
mojom::SharedMemoryVirtualDevice::RequestFrameBufferCallback>
request_frame_buffer_callback;
- EXPECT_CALL(request_frame_buffer_callback, Run(_))
+ EXPECT_CALL(request_frame_buffer_callback, Run)
.Times(1)
.WillOnce([this](int32_t buffer_id) {
// Verify that the returned |buffer_id| is a known buffer ID.
@@ -174,7 +176,7 @@
base::RunLoop wait_for_stopped_loop;
{
testing::InSequence s;
- EXPECT_CALL(video_frame_handler, DoOnBufferRetired(_))
+ EXPECT_CALL(video_frame_handler, DoOnBufferRetired)
.Times(SharedMemoryVirtualDeviceMojoAdapter::
max_buffer_pool_buffer_count());
EXPECT_CALL(video_frame_handler, OnStopped())
@@ -184,4 +186,259 @@
wait_for_stopped_loop.Run();
}
+class MockDeviceFactory : public DeviceFactory {
+ public:
+ MockDeviceFactory() = default;
+ ~MockDeviceFactory() override = default;
+
+ MOCK_METHOD(void,
+ GetDeviceInfos,
+ (GetDeviceInfosCallback callback),
+ (override));
+ MOCK_METHOD(void,
+ CreateDevice,
+ (const std::string& device_id, CreateDeviceCallback callback),
+ (override));
+ MOCK_METHOD(void, StopDevice, (const std::string device_id), (override));
+ MOCK_METHOD(void,
+ AddSharedMemoryVirtualDevice,
+ (const media::VideoCaptureDeviceInfo& device_info,
+ mojo::PendingRemote<mojom::Producer> producer,
+ mojo::PendingReceiver<mojom::SharedMemoryVirtualDevice>
+ virtual_device_receiver),
+ (override));
+ MOCK_METHOD(void,
+ AddTextureVirtualDevice,
+ (const media::VideoCaptureDeviceInfo& device_info,
+ mojo::PendingReceiver<mojom::TextureVirtualDevice>
+ virtual_device_receiver),
+ (override));
+ MOCK_METHOD(void,
+ AddGpuMemoryBufferVirtualDevice,
+ (const media::VideoCaptureDeviceInfo& device_info,
+ mojo::PendingReceiver<mojom::GpuMemoryBufferVirtualDevice>
+ virtual_device_receiver),
+ (override));
+ MOCK_METHOD(void,
+ RegisterVirtualDevicesChangedObserver,
+ (mojo::PendingRemote<mojom::DevicesChangedObserver> observer,
+ bool raise_event_if_virtual_devices_already_present),
+ (override));
+#if BUILDFLAG(IS_WIN)
+ MOCK_METHOD(void, OnGpuInfoUpdate, (const CHROME_LUID& luid), (override));
+#endif
+};
+
+class VirtualDeviceEnabledDeviceFactoryTest : public ::testing::Test {
+ protected:
+ VirtualDeviceEnabledDeviceFactoryTest() {
+ auto mock_factory = std::make_unique<MockDeviceFactory>();
+ mock_factory_ = mock_factory.get();
+ factory_ = std::make_unique<VirtualDeviceEnabledDeviceFactory>(
+ std::move(mock_factory));
+ }
+
+ ~VirtualDeviceEnabledDeviceFactoryTest() override = default;
+
+ base::test::SingleThreadTaskEnvironment task_environment_;
+ std::unique_ptr<VirtualDeviceEnabledDeviceFactory> factory_;
+ raw_ptr<MockDeviceFactory> mock_factory_ = nullptr;
+};
+
+// Tests that when an underlying device creation succeeds, any old virtual
+// device connection with the same device ID is severed.
+TEST_F(VirtualDeviceEnabledDeviceFactoryTest,
+ CreateDeviceSucceedsOldVirtualDeviceConnectionSevered) {
+ const std::string device_id = "/test/device";
+
+ // Register the virtual device first by having GetDeviceInfos return empty
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page