Low watch chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in WakeLock
DescriptionMissing authorization in WakeLock
ComponentWakeLock
Sub-componentWake-lock
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497344014
Fix commite65d6a695c3e (chromium/src) +132/-1
CWECWE-862
CISA KEVNot listed
CreditedGoogle
Reported2026-03-29 (per vendor advisory)
Fix landed2026-08-26 (public git)
Disclosed2026-09-22
Patch gap27 days — fix public in source before the advisory

Background

Wake lock
a platform primitive that keeps a device awake; a screen wake lock (kPreventDisplaySleep) keeps the display on, while a system wake lock (kPreventAppSuspension) keeps the whole machine from suspending.
`WakeLockServiceImpl`
a browser-process DocumentService that implements the blink::mojom::WakeLockService Mojo interface a renderer talks to, brokering wake-lock requests to the device service via WakeLockContext.
Permissions Policy
a per-document allow/deny mechanism; kScreenWakeLock (the screen-wake-lock feature) governs whether a frame may hold a screen wake lock, checked with RenderFrameHost::IsFeatureEnabled.
Compromised renderer
a threat model in which the sandboxed renderer process is attacker-controlled, so Blink-side (renderer) validation can be bypassed and the browser process must independently enforce every security invariant on Mojo IPC.

Root Cause Analysis

WakeLockServiceImpl::GetWakeLock received a renderer-supplied device::mojom::WakeLockType and forwarded it directly to WebContents::GetWakeLockContext() without any browser-side validation. Because the only checks on the requested type and on the screen-wake-lock Permissions Policy feature lived in Blink (the renderer), a compromised renderer could send any WakeLockType — including the system-level kPreventAppSuspension — or request a screen wake lock from a document where kScreenWakeLock was disabled. This violated the invariant that a web document may only acquire a screen wake lock, and only when its Permissions Policy permits it.

The fix adds two guards at the top of GetWakeLock: it returns early if type != WakeLockType::kPreventDisplaySleep, and returns early if render_frame_host().IsFeatureEnabled(PermissionsPolicyFeature::kScreenWakeLock) is false. On either early return the incoming mojo::PendingReceiver<device::mojom::WakeLock> is dropped (never bound), so the renderer’s wake-lock endpoint is closed rather than serviced, re-establishing the authorization check in the trusted browser process.

Key insight
The core mistake was trusting a renderer-supplied wake-lock type and skipping the Permissions Policy check in the browser process, leaving authorization enforced only in the untrusted renderer. The fix restores that enforcement at the browser-side Mojo entry point by rejecting any non-kPreventDisplaySleep type and any frame where kScreenWakeLock is not allowed.

Attack Path

  1. Compromise the renderer An attacker gains code execution in a sandboxed renderer process (e.g. via a separate renderer bug), giving them the ability to send arbitrary messages over the blink::mojom::WakeLockService Mojo pipe. (inferred)
  2. Skip the Blink checks Because type and Permissions Policy validation existed only in Blink, the attacker bypasses them and directly invokes GetWakeLock on the browser-hosted WakeLockServiceImpl.
  3. Request an unauthorized type The attacker passes WakeLockType::kPreventAppSuspension (a system-level lock) instead of kPreventDisplaySleep, which the pre-patch code forwarded verbatim to WakeLockContext.
  4. Or bypass Permissions Policy Alternatively the attacker requests kPreventDisplaySleep from a frame whose Permissions Policy disables screen-wake-lock, which the pre-patch code also honored.
  5. Acquire the lock The browser process binds the receiver and grants the wake lock, keeping the display on or the system from suspending without the required authorization.

Impact Assessment

An attacker controlling a renderer gains the ability to acquire wake locks in the browser process that a web document should not be allowed to hold — most significantly a system-level kPreventAppSuspension lock or a screen lock in a policy-disabled frame — corresponding to the advisory’s “Missing authorization in WakeLock” (a LogicError). The precondition is a compromised or malicious renderer able to reach the WakeLockService Mojo interface; the observable effect is preventing display sleep or system suspension (battery drain / power denial-of-service). The vendor advisory rates this low severity and does not report in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
content/browser/wake_lock/wake_lock_service_impl.cc
modified
WakeLockServiceImpl
content/browser/wake_lock/wake_lock_service_impl.h
modified
CONTENT_EXPORT
content/browser/wake_lock/wake_lock_service_impl.h
modified
WakeLockServiceImplTest
content/browser/wake_lock/wake_lock_service_impl_unittest.cc
modified
if
content/browser/wake_lock/wake_lock_service_impl_unittest.cc
modified
TEST_F
content/browser/wake_lock/wake_lock_service_impl_unittest.cc
modified

Files Changed

  • content/browser/wake_lock/wake_lock_service_impl.h
  • content/browser/wake_lock/wake_lock_service_impl_unittest.cc
  • content/test/BUILD.gn
  • content/browser/wake_lock/wake_lock_service_impl.cc

Audit Directions

  • Renderer-supplied enum forwarding
    Audit browser-process Mojo handlers that forward a renderer-provided enum (like WakeLockType) or type parameter straight into a privileged capability without validating it against the set permitted for web content.
  • Renderer-only Permissions Policy checks
    Look for features whose Permissions Policy / permission gating is enforced only in Blink; the browser-side DocumentService should independently call RenderFrameHost::IsFeatureEnabled before granting the capability.
  • Device-service brokers
    Review other DocumentService-based brokers to services/device (sensors, geolocation, USB, HID) for the same pattern of binding a PendingReceiver before running all authorization checks.
From e65d6a695c3ea789781529252cfc6266d1cf87bf Mon Sep 17 00:00:00 2001
From: Rob Pitkin <robpitkin@google.com>
Date: Wed, 26 Aug 2026 08:58:03 -0700
Subject: [PATCH] wake-lock: Validate type and permissions in WakeLockServiceImpl

WakeLockServiceImpl::GetWakeLock directly forwarded renderer-requested
WakeLockType parameters to WakeLockContext without verifying that the
requested type is allowed for web documents (kPreventDisplaySleep) or
checking that the frame's Permissions Policy allows kScreenWakeLock.

A compromised renderer could bypass Blink-side checks to request
unauthorized system-level wake locks (kPreventAppSuspension) or acquire
wake locks in contexts where the screen-wake-lock feature is disabled.

This CL updates WakeLockServiceImpl::GetWakeLock to return early and
drop the incoming receiver when the requested type is not
kPreventDisplaySleep or when the frame's Permissions Policy does not
allow kScreenWakeLock.

Bug: 497344014
Test: content_unittests --gtest_filter="WakeLockServiceImplTest.*"
Change-Id: I1df126fd9a17b0b9fd5805b975e3098edaa5d069
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8289909
Reviewed-by: Bo Liu <boliu@chromium.org>
Commit-Queue: Rob Pitkin <robpitkin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1686434}
---
diff --git a/content/browser/wake_lock/wake_lock_service_impl.cc b/content/browser/wake_lock/wake_lock_service_impl.cc
index bcf1c1b5..06fe1ec3 100644
--- a/content/browser/wake_lock/wake_lock_service_impl.cc
+++ b/content/browser/wake_lock/wake_lock_service_impl.cc
@@ -4,8 +4,10 @@
 
 #include "content/browser/wake_lock/wake_lock_service_impl.h"
 
+#include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "services/device/public/mojom/wake_lock_context.mojom.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
 
 namespace content {
 
@@ -22,6 +24,16 @@
     device::mojom::WakeLockReason reason,
     const std::string& description,
     mojo::PendingReceiver<device::mojom::WakeLock> receiver) {
+  // Web content is only permitted to request screen wake locks.
+  if (type != device::mojom::WakeLockType::kPreventDisplaySleep) {
+    return;
+  }
+
+  if (!render_frame_host().IsFeatureEnabled(
+          network::mojom::PermissionsPolicyFeature::kScreenWakeLock)) {
+    return;
+  }
+
   device::mojom::WakeLockContext* wake_lock_context =
       WebContents::FromRenderFrameHost(&render_frame_host())
           ->GetWakeLockContext();
diff --git a/content/browser/wake_lock/wake_lock_service_impl.h b/content/browser/wake_lock/wake_lock_service_impl.h
index 137e107..5477b04 100644
--- a/content/browser/wake_lock/wake_lock_service_impl.h
+++ b/content/browser/wake_lock/wake_lock_service_impl.h
@@ -5,13 +5,14 @@
 #ifndef CONTENT_BROWSER_WAKE_LOCK_WAKE_LOCK_SERVICE_IMPL_H_
 #define CONTENT_BROWSER_WAKE_LOCK_WAKE_LOCK_SERVICE_IMPL_H_
 
+#include "content/common/content_export.h"
 #include "content/public/browser/document_service.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/wake_lock/wake_lock.mojom.h"
 
 namespace content {
 
-class WakeLockServiceImpl final
+class CONTENT_EXPORT WakeLockServiceImpl final
     : public DocumentService<blink::mojom::WakeLockService> {
  public:
   static void Create(RenderFrameHost*,
diff --git a/content/browser/wake_lock/wake_lock_service_impl_unittest.cc b/content/browser/wake_lock/wake_lock_service_impl_unittest.cc
new file mode 100644
index 0000000..d74fc2e
--- /dev/null
+++ b/content/browser/wake_lock/wake_lock_service_impl_unittest.cc
@@ -0,0 +1,117 @@
+// 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/wake_lock/wake_lock_service_impl.h"
+
+#include "content/public/test/navigation_simulator.h"
+#include "content/test/test_render_frame_host.h"
+#include "content/test/test_render_view_host.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/device/public/mojom/wake_lock.mojom.h"
+#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/wake_lock/wake_lock.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+constexpr char kTestUrl[] = "https://example.com";
+
+}  // namespace
+
+class WakeLockServiceImplTest : public RenderViewHostImplTestHarness {
+ public:
+  WakeLockServiceImplTest() = default;
+  ~WakeLockServiceImplTest() override = default;
+
+  WakeLockServiceImplTest(const WakeLockServiceImplTest&) = delete;
+  WakeLockServiceImplTest& operator=(const WakeLockServiceImplTest&) = delete;
+
+  void SetUp() override {
+    RenderViewHostImplTestHarness::SetUp();
+    NavigateAndCommit(GURL(kTestUrl));
+  }
+
+  mojo::Remote<blink::mojom::WakeLockService> BindWakeLockService(
+      RenderFrameHost* rfh = nullptr) {
+    if (!rfh) {
+      rfh = main_rfh();
+    }
+    mojo::Remote<blink::mojom::WakeLockService> service;
+    WakeLockServiceImpl::Create(rfh, service.BindNewPipeAndPassReceiver());
+    return service;
+  }
+};
+
+TEST_F(WakeLockServiceImplTest, RequestScreenWakeLockSuccess) {
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(device::mojom::WakeLockType::kPreventDisplaySleep,
+                       device::mojom::WakeLockReason::kOther, "TestDescription",
+                       wake_lock.BindNewPipeAndPassReceiver());
+
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+  wake_lock.FlushForTesting();
+  EXPECT_TRUE(wake_lock.is_connected());
+}
+
+TEST_F(WakeLockServiceImplTest, RejectAppSuspensionWakeLock) {
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(device::mojom::WakeLockType::kPreventAppSuspension,
+                       device::mojom::WakeLockReason::kOther, "TestDescription",
+                       wake_lock.BindNewPipeAndPassReceiver());
+
+  wake_lock.FlushForTesting();
+  EXPECT_FALSE(wake_lock.is_connected());
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+}
+
+TEST_F(WakeLockServiceImplTest, RejectDisplaySleepAllowDimmingWakeLock) {
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(
+      device::mojom::WakeLockType::kPreventDisplaySleepAllowDimming,
+      device::mojom::WakeLockReason::kOther, "TestDescription",
+      wake_lock.BindNewPipeAndPassReceiver());
+
+  wake_lock.FlushForTesting();
+  EXPECT_FALSE(wake_lock.is_connected());
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+}
+
+TEST_F(WakeLockServiceImplTest, DisallowedByPermissionsPolicy) {
+  // Navigate with permissions policy disallowing screen-wake-lock.
+  network::ParsedPermissionsPolicy permissions_policy(1);
+  permissions_policy[0].feature =
+      network::mojom::PermissionsPolicyFeature::kScreenWakeLock;
+
+  auto navigation_simulator =
+      NavigationSimulator::CreateRendererInitiated(GURL(kTestUrl), main_rfh());
+  navigation_simulator->SetPermissionsPolicyHeader(permissions_policy);
+  navigation_simulator->Commit();
+
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(device::mojom::WakeLockType::kPreventDisplaySleep,
+                       device::mojom::WakeLockReason::kOther, "TestDescription",
+                       wake_lock.BindNewPipeAndPassReceiver());
+
+  wake_lock.FlushForTesting();
+  EXPECT_FALSE(wake_lock.is_connected());
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+}
+
+}  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 3680d0c..2f5ac88 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2883,6 +2883,7 @@
     "../browser/tracing/traces_internals/traces_internals_handler_unittest.cc",
     "../browser/url_loader_factory_params_helper_unittest.cc",
     "../browser/usb/web_usb_service_impl_unittest.cc",
+    "../browser/wake_lock/wake_lock_service_impl_unittest.cc",
     "../browser/web_contents/aura/gesture_nav_simple_unittest.cc",
     "../browser/web_contents/file_chooser_impl_unittest.cc",
     "../browser/web_contents/web_contents_delegate_unittest.cc",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/wake_lock/wake_lock_service_impl_unittest.cc b/content/browser/wake_lock/wake_lock_service_impl_unittest.cc
new file mode 100644
index 0000000..d74fc2e
--- /dev/null
+++ b/content/browser/wake_lock/wake_lock_service_impl_unittest.cc
@@ -0,0 +1,117 @@
+// 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/wake_lock/wake_lock_service_impl.h"
+
+#include "content/public/test/navigation_simulator.h"
+#include "content/test/test_render_frame_host.h"
+#include "content/test/test_render_view_host.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "services/device/public/mojom/wake_lock.mojom.h"
+#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/mojom/wake_lock/wake_lock.mojom.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+constexpr char kTestUrl[] = "https://example.com";
+
+}  // namespace
+
+class WakeLockServiceImplTest : public RenderViewHostImplTestHarness {
+ public:
+  WakeLockServiceImplTest() = default;
+  ~WakeLockServiceImplTest() override = default;
+
+  WakeLockServiceImplTest(const WakeLockServiceImplTest&) = delete;
+  WakeLockServiceImplTest& operator=(const WakeLockServiceImplTest&) = delete;
+
+  void SetUp() override {
+    RenderViewHostImplTestHarness::SetUp();
+    NavigateAndCommit(GURL(kTestUrl));
+  }
+
+  mojo::Remote<blink::mojom::WakeLockService> BindWakeLockService(
+      RenderFrameHost* rfh = nullptr) {
+    if (!rfh) {
+      rfh = main_rfh();
+    }
+    mojo::Remote<blink::mojom::WakeLockService> service;
+    WakeLockServiceImpl::Create(rfh, service.BindNewPipeAndPassReceiver());
+    return service;
+  }
+};
+
+TEST_F(WakeLockServiceImplTest, RequestScreenWakeLockSuccess) {
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(device::mojom::WakeLockType::kPreventDisplaySleep,
+                       device::mojom::WakeLockReason::kOther, "TestDescription",
+                       wake_lock.BindNewPipeAndPassReceiver());
+
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+  wake_lock.FlushForTesting();
+  EXPECT_TRUE(wake_lock.is_connected());
+}
+
+TEST_F(WakeLockServiceImplTest, RejectAppSuspensionWakeLock) {
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(device::mojom::WakeLockType::kPreventAppSuspension,
+                       device::mojom::WakeLockReason::kOther, "TestDescription",
+                       wake_lock.BindNewPipeAndPassReceiver());
+
+  wake_lock.FlushForTesting();
+  EXPECT_FALSE(wake_lock.is_connected());
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+}
+
+TEST_F(WakeLockServiceImplTest, RejectDisplaySleepAllowDimmingWakeLock) {
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(
+      device::mojom::WakeLockType::kPreventDisplaySleepAllowDimming,
+      device::mojom::WakeLockReason::kOther, "TestDescription",
+      wake_lock.BindNewPipeAndPassReceiver());
+
+  wake_lock.FlushForTesting();
+  EXPECT_FALSE(wake_lock.is_connected());
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+}
+
+TEST_F(WakeLockServiceImplTest, DisallowedByPermissionsPolicy) {
+  // Navigate with permissions policy disallowing screen-wake-lock.
+  network::ParsedPermissionsPolicy permissions_policy(1);
+  permissions_policy[0].feature =
+      network::mojom::PermissionsPolicyFeature::kScreenWakeLock;
+
+  auto navigation_simulator =
+      NavigationSimulator::CreateRendererInitiated(GURL(kTestUrl), main_rfh());
+  navigation_simulator->SetPermissionsPolicyHeader(permissions_policy);
+  navigation_simulator->Commit();
+
+  mojo::Remote<blink::mojom::WakeLockService> service = BindWakeLockService();
+
+  mojo::Remote<device::mojom::WakeLock> wake_lock;
+  service->GetWakeLock(device::mojom::WakeLockType::kPreventDisplaySleep,
+                       device::mojom::WakeLockReason::kOther, "TestDescription",
+                       wake_lock.BindNewPipeAndPassReceiver());
+
+  wake_lock.FlushForTesting();
+  EXPECT_FALSE(wake_lock.is_connected());
+  service.FlushForTesting();
+  EXPECT_TRUE(service.is_connected());
+}
+
+}  // namespace content
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 3680d0c..2f5ac88 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -2883,6 +2883,7 @@
     "../browser/tracing/traces_internals/traces_internals_handler_unittest.cc",
     "../browser/url_loader_factory_params_helper_unittest.cc",
     "../browser/usb/web_usb_service_impl_unittest.cc",
+    "../browser/wake_lock/wake_lock_service_impl_unittest.cc",
     "../browser/web_contents/aura/gesture_nav_simple_unittest.cc",
     "../browser/web_contents/file_chooser_impl_unittest.cc",
     "../browser/web_contents/web_contents_delegate_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.