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 <handellm@google.com>
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 <ilnik@chromium.org>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Commit-Queue: Markus Handell <handellm@google.com>
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