Low watch CVSS 7.5 chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in WebView
DescriptionIncorrect authorization in WebView
ComponentWebView
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.
Tracker496212975
Fix commita43e3a87de7e (chromium/src) +21/-11
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-25 (per vendor advisory)
Fix landed2026-06-19 (public git)
Disclosed2026-09-29
Patch gap102 days — fix public in source before the advisory

Background

`WebMessageListener` / `sourceOrigin`
An Android WebView API where the app registers a JavaScript-to-app message channel and receives, with each message, a sourceOrigin the app uses to decide whether to trust the message.
`JsToBrowserMessaging`
The browser-process endpoint (in components/js_injection/browser) that receives PostMessage mojo calls from the renderer and forwards them to the app’s WebMessageHost, carrying the frame’s origin and main-frame status.
`DCHECK_EQ` vs `mojo::ReportBadMessage`
DCHECK_EQ is compiled out of release builds (only active when DCHECK_IS_ON()), whereas mojo::ReportBadMessage always runs and kills a misbehaving renderer; the two guard renderer-supplied data very differently in production.
Cached origin/frame members
top_level_origin_string_, origin_string_, and is_main_frame_, snapshotted on the first PostMessage and meant to be re-checked against later calls.

Root Cause Analysis

In the PostMessage handler of JsToBrowserMessaging (name not shown in the @@ -254 hunk but established by the class and the mojo method), the origin/frame values are captured on the first call (when host_ is created) and are supposed to remain invariant for the life of the connection.

Pre-patch, both the cached members (top_level_origin_string_, origin_string_, is_main_frame_) and the consistency checks were wrapped in #if DCHECK_IS_ON(), so in production builds the members did not exist and the DCHECK_EQ comparisons were compiled out entirely. A compromised or malicious renderer could therefore send a first PostMessage establishing a legitimate origin and then send subsequent PostMessage calls carrying a different source_origin/top_level_origin, which the browser forwarded to the app’s WebMessageListener as the sourceOrigin without any release-build check — spoofing the origin an app relies on for authorization.

The fix makes the three members unconditional and replaces each DCHECK_EQ with an always-enforced if (mismatch) { mojo::ReportBadMessage(...); return; }, so a later call whose origin or frame status differs from the cached values is rejected (and the renderer killed) in production; it also reorders the if (!host_) return; check to run before the members are populated. As the added comment notes, the checks are effective from the second call onward, pinning the origin to the value captured when the host was first created.

Key insight
The single mistake was guarding renderer-supplied origin/frame consistency with debug-only DCHECK_EQ (and storing the cached values only under DCHECK_IS_ON()), so the anti-spoofing check simply did not exist in shipping builds; the fix promotes the checks to always-on mojo::ReportBadMessage and always populates the cached values.

Attack Path

  1. Obtain renderer control of the message pipe A compromised or malicious renderer holds the JsToBrowserMessaging mojo endpoint and controls the source_origin, top_level_origin, and frame arguments of each PostMessage.
  2. Establish a trusted host The renderer sends a first PostMessage with a legitimate origin, causing CreateHost(...) to succeed and (post-patch) the cached origin/frame members to be recorded.
  3. Send a spoofed follow-up message On a subsequent PostMessage, the renderer supplies a different source_origin/top_level_origin (or flips is_main_frame); pre-patch, the debug-only DCHECK_EQ was compiled out, so the mismatch was not detected in release builds.
  4. App receives a spoofed `sourceOrigin` The forged origin is delivered to the app’s WebMessageListener as the message’s sourceOrigin, so app logic that authorizes messages by origin (e.g. an allowlist) can be tricked into trusting a message from an origin that did not send it.

Impact Assessment

A compromised/malicious renderer could spoof the sourceOrigin reported to a WebView app’s WebMessageListener, defeating origin-based authorization decisions the app makes in the browser process. The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 7.5. Any concrete consequence within a specific embedding app (data access, privileged action) is (inferred) and depends on how that app trusts sourceOrigin. The precondition is renderer control over the PostMessage arguments after a host has been established.

Changed Functions

FunctionChangeNotes
if
components/js_injection/browser/js_to_browser_messaging.cc
modified

Files Changed

  • components/js_injection/browser/js_to_browser_messaging.cc
  • components/js_injection/browser/js_to_browser_messaging.h

Audit Directions

  • DCHECK-guarded validation of IPC data
    Search for DCHECK/DCHECK_EQ used to “validate” renderer- or other-process-supplied values across a trust boundary; these vanish in release and should be mojo::ReportBadMessage/ReceivedBadMessage.
  • State cached only under `DCHECK_IS_ON()`
    Look for member variables compiled only in debug builds that a security check depends on, since the check silently becomes a no-op in production.
  • Immutability assumptions on per-connection identity
    Audit mojo endpoints that snapshot an origin/frame/identity once and assume it cannot change, verifying the invariant is enforced on every subsequent call rather than only the first.
From a43e3a87de7e4b908bb10b24237239431cd1f2dd Mon Sep 17 00:00:00 2001
From: Abhijith Nair <abhijithnair@chromium.org>
Date: Fri, 19 Jun 2026 03:22:27 -0700
Subject: [PATCH] Make it harder to spoof WebMessageListener sourceOrigin in WV

We now compare cached origin against current in production builds. The
member variables storing origin and frame information are now always
populated. The consistency checks for these values are upgraded from
DCHECK_EQ to mojo::ReportBadMessage, ensuring they are always enforced.

Bug: 496212975
Change-Id: Ia87e7803d1dd414373700f4faa41ee2031a2a135
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7943265
Reviewed-by: Peter Pakkenberg <pbirk@chromium.org>
Commit-Queue: Abhijith Nair <abhijithnair@chromium.org>
Reviewed-by: Richard Coles <torne@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1649582}
---
diff --git a/components/js_injection/browser/js_to_browser_messaging.cc b/components/js_injection/browser/js_to_browser_messaging.cc
index b785940..b7b06ea 100644
--- a/components/js_injection/browser/js_to_browser_messaging.cc
+++ b/components/js_injection/browser/js_to_browser_messaging.cc
@@ -20,6 +20,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/weak_document_ptr.h"
 #include "content/public/browser/web_contents.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/common/messaging/message_port_descriptor.h"
 #include "third_party/blink/public/common/messaging/string_message_codec.h"
@@ -254,21 +255,32 @@
     host_ =
         connection_factory_->CreateHost(top_level_origin_string, origin_string,
                                         is_main_frame, reply_proxy_.get());
-#if DCHECK_IS_ON()
+    if (!host_) {
+      return;
+    }
+
     top_level_origin_string_ = top_level_origin_string;
     origin_string_ = origin_string;
     is_main_frame_ = is_main_frame;
-#endif
-    if (!host_)
-      return;
   }
+
+  // The following checks will only be effective on the second and following
+  // calls to `PostMessage`.
   // The origin and whether this is the main frame should not change once
   // PostMessage() has been received.
-#if DCHECK_IS_ON()
-  DCHECK_EQ(GetOriginString(top_level_origin), top_level_origin_string_);
-  DCHECK_EQ(GetOriginString(source_origin), origin_string_);
-  DCHECK_EQ(is_main_frame_, !render_frame_host_->GetParentOrOuterDocument());
-#endif
+  if (top_level_origin_string_ != GetOriginString(top_level_origin)) {
+    mojo::ReportBadMessage("top_level_origin changed after host reset.");
+    return;
+  }
+  if (origin_string_ != GetOriginString(source_origin)) {
+    mojo::ReportBadMessage("source_origin changed after host reset.");
+    return;
+  }
+  if (is_main_frame_ != !render_frame_host_->GetParentOrOuterDocument()) {
+    mojo::ReportBadMessage("is_main_frame changed after host reset.");
+    return;
+  }
+
   std::unique_ptr<WebMessage> web_message = std::make_unique<WebMessage>();
   web_message->message = std::move(message);
   web_message->ports = std::move(ports);
diff --git a/components/js_injection/browser/js_to_browser_messaging.h b/components/js_injection/browser/js_to_browser_messaging.h
index 14217a4..0b3a716b 100644
--- a/components/js_injection/browser/js_to_browser_messaging.h
+++ b/components/js_injection/browser/js_to_browser_messaging.h
@@ -72,11 +72,9 @@
   std::unique_ptr<WebMessageHost> host_;
   mojo::SharedAssociatedRemote<mojom::BrowserToJsMessagingFactory>
       browser_to_js_factory_;
-#if DCHECK_IS_ON()
   std::string top_level_origin_string_;
   std::string origin_string_;
   bool is_main_frame_;
-#endif
 };
 
 }  // namespace js_injection
Loading diff…