Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in FoldableAPIs
DescriptionInformation leak in FoldableAPIs
ComponentFoldableAPIs
Bug ClassLogic Error
Tracker522803735
Fix commita743780616a3 (chromium/src) +258/-17
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
WebContentsObserver
content/browser/device_posture/device_posture_provider_impl.cc
modified
for
content/browser/device_posture/device_posture_provider_impl.cc
modified
if
content/browser/device_posture/device_posture_provider_impl.cc
modified
DevicePostureProviderImpl
content/browser/device_posture/device_posture_provider_impl.h
modified
CONTENT_EXPORT
content/browser/device_posture/device_posture_provider_impl.h
modified

Files Changed

  • content/browser/device_posture/device_posture_provider_impl.cc
  • content/browser/device_posture/device_posture_provider_impl.h
From a743780616a342356187a6693431edc81a172073 Mon Sep 17 00:00:00 2001
From: Alvin Ji <alvinji@chromium.org>
Date: Tue, 07 Jul 2026 16:09:17 -0700
Subject: [PATCH] Fix Privacy Side-Channel in Device Posture API via Background Tabs

This CL defers device posture change events when the WebContents is
hidden or occluded. DevicePostureProviderImpl now inherits from
WebContentsObserver and observes visibility changes. Updates are only
dispatched when the WebContents is visible.

Bug: 522803735
Change-Id: Ie5c757727cf7787c9968c816e1ff2eb50679a08c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7927408
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Alvin Ji <alvinji@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1658356}
---

diff --git a/content/browser/device_posture/device_posture_provider_impl.cc b/content/browser/device_posture/device_posture_provider_impl.cc
index 6ce8c665..f592bb27 100644
--- a/content/browser/device_posture/device_posture_provider_impl.cc
+++ b/content/browser/device_posture/device_posture_provider_impl.cc
@@ -22,7 +22,8 @@
 }
 
 DevicePostureProviderImpl::DevicePostureProviderImpl(WebContents* web_contents)
-    : WebContentsUserData<DevicePostureProviderImpl>(*web_contents) {
+    : WebContentsUserData<DevicePostureProviderImpl>(*web_contents),
+      WebContentsObserver(web_contents) {
   platform_provider_ = DevicePosturePlatformProvider::Create(web_contents);
   // We need to  listen to disconnections so that if there is nobody interested
   // in posture changes we can shutdown the native backends.
@@ -42,6 +43,13 @@
   return platform_provider_.get();
 }
 
+blink::mojom::DevicePostureType DevicePostureProviderImpl::GetCurrentPosture()
+    const {
+  return (is_posture_emulated_ && emulated_posture_)
+             ? *emulated_posture_
+             : platform_provider_->GetDevicePosture();
+}
+
 void DevicePostureProviderImpl::AddListenerAndGetCurrentPosture(
     mojo::PendingRemote<blink::mojom::DevicePostureClient> client,
     AddListenerAndGetCurrentPostureCallback callback) {
@@ -49,17 +57,27 @@
     platform_provider_->AddObserver(this);
   }
   posture_clients_.Add(std::move(client));
-  blink::mojom::DevicePostureType posture =
-      platform_provider_->GetDevicePosture();
+  blink::mojom::DevicePostureType posture = GetCurrentPosture();
   std::move(callback).Run(posture);
+  last_dispatched_posture_ = posture;
 }
 
 void DevicePostureProviderImpl::OverrideDevicePostureForEmulation(
     blink::mojom::DevicePostureType emulated_posture) {
-  // Notify the related clients about the new posture.
   is_posture_emulated_ = true;
-  for (auto& client : posture_clients_) {
-    client->OnPostureChanged(emulated_posture);
+  emulated_posture_ = emulated_posture;
+  // If the page is hidden, we store the emulated posture but defer dispatching
+  // the event until the page becomes visible again (handled in
+  // OnVisibilityChanged).
+  if (web_contents()->GetVisibility() != Visibility::VISIBLE) {
+    return;
+  }
+  if (!last_dispatched_posture_ ||
+      *last_dispatched_posture_ != emulated_posture) {
+    for (auto& client : posture_clients_) {
+      client->OnPostureChanged(emulated_posture);
+    }
+    last_dispatched_posture_ = emulated_posture;
   }
 }
 
@@ -72,21 +90,52 @@
 
   // Restore the original posture from the platform.
   is_posture_emulated_ = false;
-  for (auto& client : posture_clients_) {
-    client->OnPostureChanged(platform_provider_->GetDevicePosture());
+  emulated_posture_.reset();
+  if (web_contents()->GetVisibility() != Visibility::VISIBLE) {
+    return;
+  }
+  blink::mojom::DevicePostureType posture =
+      platform_provider_->GetDevicePosture();
+  if (!last_dispatched_posture_ || *last_dispatched_posture_ != posture) {
+    for (auto& client : posture_clients_) {
+      client->OnPostureChanged(posture);
+    }
+    last_dispatched_posture_ = posture;
   }
 }
 
 void DevicePostureProviderImpl::OnDevicePostureChanged(
     const blink::mojom::DevicePostureType& posture) {
-  // If we receive a posture change from the platform but we're emulating it we
-  // shouldn't notify the clients.
-  if (is_posture_emulated_) {
+  // If we receive a posture change from the platform but we're emulating it,
+  // or if the page is not visible, we shouldn't notify the clients.
+  if (is_posture_emulated_ ||
+      web_contents()->GetVisibility() != Visibility::VISIBLE) {
     return;
   }
 
-  for (auto& client : posture_clients_) {
-    client->OnPostureChanged(posture);
+  if (!last_dispatched_posture_ || *last_dispatched_posture_ != posture) {
+    for (auto& client : posture_clients_) {
+      client->OnPostureChanged(posture);
+    }
+    last_dispatched_posture_ = posture;
+  }
+}
+
+void DevicePostureProviderImpl::OnVisibilityChanged(Visibility visibility) {
+  if (visibility != Visibility::VISIBLE || posture_clients_.empty()) {
+    return;
+  }
+
+  // When the tab regains visibility, dispatch the latest posture to the
+  // clients if it changed while the tab was hidden.
+  blink::mojom::DevicePostureType current_posture = GetCurrentPosture();
+
+  if (!last_dispatched_posture_ ||
+      *last_dispatched_posture_ != current_posture) {
+    for (auto& client : posture_clients_) {
+      client->OnPostureChanged(current_posture);
+    }
+    last_dispatched_posture_ = current_posture;
   }
 }
 
@@ -95,6 +144,7 @@
   if (posture_clients_.empty()) {
     // We're not interested in receiving posture changes.
     platform_provider_->RemoveObserver(this);
+    last_dispatched_posture_.reset();
   }
 }
 
diff --git a/content/browser/device_posture/device_posture_provider_impl.h b/content/browser/device_posture/device_posture_provider_impl.h
index ce621efe..e91786ae 100644
--- a/content/browser/device_posture/device_posture_provider_impl.h
+++ b/content/browser/device_posture/device_posture_provider_impl.h
@@ -7,6 +7,7 @@
 
 #include "content/browser/device_posture/device_posture_platform_provider.h"
 #include "content/common/content_export.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
@@ -16,10 +17,11 @@
 
 namespace content {
 
-class DevicePostureProviderImpl final
+class CONTENT_EXPORT DevicePostureProviderImpl final
     : public blink::mojom::DevicePostureProvider,
       public WebContentsUserData<DevicePostureProviderImpl>,
-      public DevicePosturePlatformProvider::Observer {
+      public DevicePosturePlatformProvider::Observer,
+      public WebContentsObserver {
  public:
   static DevicePostureProviderImpl* GetOrCreate(WebContents*);
   explicit DevicePostureProviderImpl(content::WebContents* web_contents);
@@ -32,15 +34,18 @@
       mojo::PendingReceiver<blink::mojom::DevicePostureProvider> receiver);
   DevicePosturePlatformProvider* platform_provider() const;
 
-  CONTENT_EXPORT void OverrideDevicePostureForEmulation(
+  void OverrideDevicePostureForEmulation(
       blink::mojom::DevicePostureType posture);
-  CONTENT_EXPORT void DisableDevicePostureOverrideForEmulation();
+  void DisableDevicePostureOverrideForEmulation();
 
  private:
   // DevicePostureClient implementation.
   void OnDevicePostureChanged(
       const blink::mojom::DevicePostureType& posture) override;
 
+  // WebContentsObserver implementation.
+  void OnVisibilityChanged(Visibility visibility) override;
+
   // DevicePostureProvider implementation.
   void AddListenerAndGetCurrentPosture(
       mojo::PendingRemote<blink::mojom::DevicePostureClient> client,
@@ -48,8 +53,12 @@
 
   void OnRemoteDisconnect(mojo::RemoteSetElementId);
 
+  blink::mojom::DevicePostureType GetCurrentPosture() const;
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/device_posture/device_posture_provider_impl_unittest.cc b/content/browser/device_posture/device_posture_provider_impl_unittest.cc
new file mode 100644
index 0000000..ca934abf
--- /dev/null
+++ b/content/browser/device_posture/device_posture_provider_impl_unittest.cc
@@ -0,0 +1,181 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/device_posture/device_posture_provider_impl.h"
+
+#include <optional>
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/memory/raw_ptr.h"
+#include "base/test/test_future.h"
+#include "content/public/test/test_renderer_host.h"
+#include "content/test/test_web_contents.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/device_posture/device_posture_provider.mojom.h"
+
+namespace content {
+
+using ::testing::_;
+
+class MockDevicePostureClient : public blink::mojom::DevicePostureClient {
+ public:
+  MockDevicePostureClient() = default;
+  ~MockDevicePostureClient() override = default;
+
+  mojo::PendingRemote<blink::mojom::DevicePostureClient> BindAndGetRemote() {
+    return receiver_.BindNewPipeAndPassRemote();
+  }
+
+  // blink::mojom::DevicePostureClient:
+  MOCK_METHOD(void,
+              OnPostureChanged,
+              (blink::mojom::DevicePostureType posture),
+              (override));
+
+  void Flush() { receiver_.FlushForTesting(); }
+
+ private:
+  mojo::Receiver<blink::mojom::DevicePostureClient> receiver_{this};
+};
+
+class DevicePostureProviderImplTest : public RenderViewHostTestHarness {
+ public:
+  DevicePostureProviderImplTest() = default;
+
+  void SetUp() override {
+    RenderViewHostTestHarness::SetUp();
+    DevicePostureProviderImpl::GetOrCreate(web_contents());
+  }
+
+  DevicePostureProviderImpl* provider() {
+    return DevicePostureProviderImpl::FromWebContents(web_contents());
+  }
+
+  TestWebContents* test_web_contents() {
+    return static_cast<TestWebContents*>(web_contents());
+  }
+};
+
+TEST_F(DevicePostureProviderImplTest, DeferUpdatesWhileHidden) {
+  MockDevicePostureClient client;
+
+  // Initially visible.
+  test_web_contents()->WasShown();
+
+  // Add listener.
+  base::test::TestFuture<blink::mojom::DevicePostureType> posture_future;
+  blink::mojom::DevicePostureProvider* posture_provider = provider();
+  posture_provider->AddListenerAndGetCurrentPosture(
+      client.BindAndGetRemote(), posture_future.GetCallback());
+  EXPECT_EQ(posture_future.Get(), blink::mojom::DevicePostureType::kContinuous);
+
+  // Trigger posture change while visible.
+  DevicePosturePlatformProvider::Observer* observer = provider();
+  base::test::TestFuture<blink::mojom::DevicePostureType> change_future_1;
+  EXPECT_CALL(client, OnPostureChanged(_))
+      .WillOnce(base::test::InvokeFuture(change_future_1));
+  observer->OnDevicePostureChanged(blink::mojom::DevicePostureType::kFolded);
+  EXPECT_EQ(change_future_1.Take(), blink::mojom::DevicePostureType::kFolded);
+
+  // Hide the web contents.
+  test_web_contents()->WasHidden();
+
+  // Trigger posture change while hidden.
+  EXPECT_CALL(client, OnPostureChanged(_)).Times(0);
+  observer->OnDevicePostureChanged(
+      blink::mojom::DevicePostureType::kContinuous);
+  client.Flush();
+
+  // Show the web contents again.
+  base::test::TestFuture<blink::mojom::DevicePostureType> change_future_2;
+  EXPECT_CALL(client, OnPostureChanged(_))
+      .WillOnce(base::test::InvokeFuture(change_future_2));
+  test_web_contents()->WasShown();
+  EXPECT_EQ(change_future_2.Take(),
+            blink::mojom::DevicePostureType::kContinuous);
+}
+
+TEST_F(DevicePostureProviderImplTest, DeferUpdatesWhileOccluded) {
+  MockDevicePostureClient client;
+
+  // Initially visible.
+  test_web_contents()->WasShown();
+
+  // Add listener.
+  base::test::TestFuture<blink::mojom::DevicePostureType> posture_future;
+  blink::mojom::DevicePostureProvider* posture_provider = provider();
+  posture_provider->AddListenerAndGetCurrentPosture(
+      client.BindAndGetRemote(), posture_future.GetCallback());
+  EXPECT_EQ(posture_future.Get(), blink::mojom::DevicePostureType::kContinuous);
+
+  // Trigger posture change while visible.
+  DevicePosturePlatformProvider::Observer* observer = provider();
+  base::test::TestFuture<blink::mojom::DevicePostureType> change_future_1;
+  EXPECT_CALL(client, OnPostureChanged(_))
+      .WillOnce(base::test::InvokeFuture(change_future_1));
+  observer->OnDevicePostureChanged(blink::mojom::DevicePostureType::kFolded);
+  EXPECT_EQ(change_future_1.Take(), blink::mojom::DevicePostureType::kFolded);
+
+  // Occlude the web contents.
+  test_web_contents()->WasOccluded();
+
+  // Trigger posture change while occluded.
+  EXPECT_CALL(client, OnPostureChanged(_)).Times(0);
+  observer->OnDevicePostureChanged(
+      blink::mojom::DevicePostureType::kContinuous);
+  client.Flush();
+
+  // Show the web contents again.
+  base::test::TestFuture<blink::mojom::DevicePostureType> change_future_2;
+  EXPECT_CALL(client, OnPostureChanged(_))
+      .WillOnce(base::test::InvokeFuture(change_future_2));
+  test_web_contents()->WasShown();
+  EXPECT_EQ(change_future_2.Take(),
+            blink::mojom::DevicePostureType::kContinuous);
+}
+
+TEST_F(DevicePostureProviderImplTest, DeferUpdatesWhileHiddenEmulation) {
+  MockDevicePostureClient client;
+
+  // Initially visible.
+  test_web_contents()->WasShown();
+
+  // Add listener.
+  base::test::TestFuture<blink::mojom::DevicePostureType> posture_future;
+  blink::mojom::DevicePostureProvider* posture_provider = provider();
+  posture_provider->AddListenerAndGetCurrentPosture(
+      client.BindAndGetRemote(), posture_future.GetCallback());
+  EXPECT_EQ(posture_future.Get(), blink::mojom::DevicePostureType::kContinuous);
+
+  // Enable emulation and override.
+  base::test::TestFuture<blink::mojom::DevicePostureType> change_future_1;
+  EXPECT_CALL(client, OnPostureChanged(_))
+      .WillOnce(base::test::InvokeFuture(change_future_1));
+  provider()->OverrideDevicePostureForEmulation(
+      blink::mojom::DevicePostureType::kFolded);
+  EXPECT_EQ(change_future_1.Take(), blink::mojom::DevicePostureType::kFolded);
+
+  // Hide the web contents.
+  test_web_contents()->WasHidden();
+
+  // Override while hidden.
+  EXPECT_CALL(client, OnPostureChanged(_)).Times(0);
+  provider()->OverrideDevicePostureForEmulation(
+      blink::mojom::DevicePostureType::kContinuous);
+  client.Flush();
+
+  // Show the web contents again.
+  base::test::TestFuture<blink::mojom::DevicePostureType> change_future_2;
+  EXPECT_CALL(client, OnPostureChanged(_))
+      .WillOnce(base::test::InvokeFuture(change_future_2));
+  test_web_contents()->WasShown();
+  EXPECT_EQ(change_future_2.Take(),
+            blink::mojom::DevicePostureType::kContinuous);
+}
+
+}  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 131e19a..e593cfc3 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2701,6 +2701,7 @@
     "../browser/cpu_performance/cpu_performance_unittest.cc",
     "../browser/declarative_performance_observer/declarative_performance_observer_store_unittest.cc",
     "../browser/declarative_performance_observer/declarative_performance_observer_unittest.cc",
+    "../browser/device_posture/device_posture_provider_impl_unittest.cc",
     "../browser/devtools/devtools_background_services_context_impl_unittest.cc",
     "../browser/devtools/devtools_frontend_host_impl_unittest.cc",
     "../browser/devtools/devtools_http_handler_unittest.cc",
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.