Low chrome OOB 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in CameraCapture
DescriptionOut of bounds read in CameraCapture
ComponentCameraCapture
Bug ClassOOB
Tracker513194241
Fix commitf5f020cf8f3e (chromium/src) +580/-41
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
for
services/video_capture/test/video_capture_service_unittest.cc
modified
if
services/video_capture/test/video_capture_service_unittest.cc
modified
TEST_F
services/video_capture/test/video_capture_service_unittest.cc
modified

Files Changed

  • services/video_capture/test/video_capture_service_unittest.cc
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.