CVE-2026-106372
Overview
Files Changed
chrome/browser/chrome_content_browser_client.cc
Patch
From 378f4aa2ccd6d1a54dec41b6fd882d9ca4db3d47 Mon Sep 17 00:00:00 2001 From: Dan Harrington <[email protected]> Date: Fri, 11 Sep 2026 08:42:29 -0700 Subject: [PATCH] Only enable mojo bindings in the glic guest for main frame Previously, mojo bindings were enabled on subframes, but this is not needed. Note that the glic mojo interface itself will not be bound unless the origin is allowed. This further restricts access. Bug: 557288890 Change-Id: I393a309ac99c0e90e86242d84976d08e6a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8373184 Reviewed-by: Bryant Chandler <[email protected]> Auto-Submit: Dan H <[email protected]> Commit-Queue: Dan H <[email protected]> Cr-Commit-Position: refs/heads/main@{#1696294} --- diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 5d156f8a..6ec5dbb0 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -2273,7 +2273,8 @@ content::RenderFrameHost& render_frame_host) { content::WebContents* web_contents = content::WebContents::FromRenderFrameHost(&render_frame_host); - if (web_contents && glic::IsGlicGuest(web_contents)) { + if (web_contents && glic::IsGlicGuest(web_contents) && + !render_frame_host.GetParentOrOuterDocument()) { return true; } // TODO(crbug.com/539909218): Prototype shortcut. Enabling MojoJS for any PWC
Original Bug Report
MojoJS bindings are granted per-WebContents rather than per-frame in the Glic guest, so any subframe it hosts receives the full Mojo interface surface
VULNERABILITY DETAILS
Summary
GlicGuestObserver enables MojoJS — which exposes the entire Mojo interface surface to
JavaScript — based on a property of the WebContents, not of the frame or its committed origin.
The grant runs from RenderFrameCreated, which fires for every RenderFrameHost in that WebContents,
subframes included.
The sibling overload of the same function, ten lines away, does check both the frame and the origin. So does the narrow, intended API. Only the broad grant checks neither.
The defect
chrome/browser/glic/host/glic_guest_observer.cc:87-92
void GlicGuestObserver::MaybeEnableMojoJsBindings(
content::RenderFrameHost* render_frame_host) {
if (IsGlicGuest(web_contents())) {
render_frame_host->EnableMojoJsBindings(/*features=*/nullptr);
}
}
Called from :57-60:
void GlicGuestObserver::RenderFrameCreated(
content::RenderFrameHost* render_frame_host) {
MaybeEnableMojoJsBindings(render_frame_host);
MaybeSetBackgroundColor(render_frame_host);
}
WebContentsObserver::RenderFrameCreated is documented at
content/public/browser/web_contents_observer.h:134-137 as “Called when a RenderFrame for
|render_frame_host| is created in the renderer process” — with no main-frame qualifier. It fires
for subframes.
The sole condition, IsGlicGuest(web_contents())
(chrome/browser/glic/host/guest_util.cc:256-259), is
GlicGuestObserver::FromWebContents(web_contents) != nullptr — a property of the WebContents.
The asymmetry, all within the same feature
| Site | Frame checked | Origin checked |
|---|---|---|
glic_guest_observer.cc:94-107 — the NavigationHandle overload |
IsInMainFrame() at :96 |
IsOriginAllowedGlicApi() at :103 |
glic_guest_observer.cc:110-112 — MaybeSetBackgroundColor, purely cosmetic |
GetParentOrOuterDocument() at :112 |
— |
guest_util.cc:424-431 — IsFrameAllowedGlicApi, gating the narrow intended API |
— | IsOriginAllowedGlicApi(GetLastCommittedOrigin()) |
glic_guest_observer.cc:87-92 — the MojoJS grant |
no | no |
The function that sets a background colour checks for a main frame. The function that grants the entire Mojo surface does not.
The origin check in the sibling overload does not save it
A cross-document navigation creates a new RenderFrameHost. RenderFrameCreated fires for that
new host first and grants unconditionally, before ReadyToCommitNavigation reaches the overload
that would have checked the origin.
The CHECK does not catch it
content/browser/renderer_host/render_frame_host_impl.cc:19230-19241
void RenderFrameHostImpl::EnableMojoJsBindings(
content::mojom::ExtraMojoJsFeaturesPtr features) {
CHECK(
WebUIControllerFactoryRegistry::GetInstance()->GetWebUIType(...) != WebUI::kNoWebUI ||
GetContentClient()->browser()->ShouldAllowMojoJsBindingsForFrame(*this));
GetFrameBindingsControl()->EnableMojoJsBindings(std::move(features));
}
ChromeContentBrowserClient::ShouldAllowMojoJsBindingsForFrame
(chrome/browser/chrome_content_browser_client.cc:2276-2280) returns true for any frame whose
WebContents is a Glic guest — the same container-granularity test. The CHECK therefore passes for a
subframe, and the grant is real rather than a crash.
The renderer-side flag is never reset
enable_mojo_js_bindings_ has exactly three references outside tests:
content/renderer/render_frame_impl.h:1456 (declaration),
content/renderer/render_frame_impl.cc:2599 (set to true),
content/renderer/render_frame_impl.cc:4952 (read). There is no reset on commit.
Note also that EnableMojoJsBindings does not set enabled_bindings_, so the commit-time gate
CheckWebUIRendererDoesNotDisplayNormalURL — which keys off GetEnabledBindings() — never applies
to embedder-path grants such as this one.
Related second instance, reported for completeness
chrome/browser/chrome_content_browser_client.cc:2282-2290 grants MojoJS to any outermost main
frame of a PrivilegedWebContents, and its own TODO states the problem:
// TODO(crbug.com/539909218): Prototype shortcut. Enabling MojoJS for any PWC
// exposes the entire Mojo interface surface rather than only GeicApi, and the
// committed origin is not checked against the capability allowlist here.
That path is separately significant because Glic’s own policy delegate deliberately makes the
navigation allowlist a superset of the capability allowlist
(chrome/browser/glic/host/glic_no_webview_contents_manager.cc:62-69):
bool IsNavigationAllowed(const url::Origin& o) const override { return IsGuestOriginAllowed(o); }
bool IsCapabilityOrigin(const url::Origin& o) const override { return IsOriginAllowedGlicApi(o); }
IsGuestOriginAllowed (guest_util.cc:360-384) additionally admits accounts.google.com,
login.corp.google.com, accounts.googlers.com, gaiastaging.corp.google.com and
*.corp.google.com. Those origins are, by the design’s own statement, meant to be able to navigate
but not to hold capabilities — yet they receive the entire Mojo surface, which is strictly more
than the capability they were denied.
I am aware the TODO indicates this is known and that a scoped capability mechanism is being
designed. The RenderFrameCreated grant above is a distinct defect: the TODO concerns the
outermost main frame of a PWC, whereas that grant reaches subframes and applies no origin check
at all.
What I have established, and what I have not
Established by reading the code, each point quoted above: the grant is keyed on the WebContents; it fires for subframes; it performs no origin check; the sibling overload’s origin check is bypassed by RenderFrameHost creation ordering; the CHECK does not catch it; the renderer-side flag is never reset.
Now measured (poc_browsertest.cc, browser_tests, no flags):
[CONTROL] guest main frame: typeof Mojo = function
[RESULT] cross-origin subframe: typeof Mojo = function,
typeof Mojo.bindInterface = function
The control is non-zero, so the grant mechanism demonstrably ran; the subframe at a different
origin holds a working Mojo.bindInterface. That settles the question of whether the grant reaches
subframes at all.
Still not established: that an attacker-controlled document can be placed inside the Glic guest WebContents. That depends on whether the deployed guest page embeds third-party frames, which is not visible from the Chromium source. If it does not, this is a defence-in-depth failure rather than a directly reachable escape. I would rather state that plainly than overstate the finding.
VERSION Chrome Version: 155.0.8043.0 dev Dev/Canary only. The grant landed in bbbe503702 on 2026-09-01, after the M155 branch cut, so it is NOT in 154 (beta) or 152 (stable). Operating System: macOS 26 (Tahoe), arm64
REPRODUCTION CASE
Attached: poc_browsertest.cc — a browser_tests case built on the existing Glic harness
(chrome/browser/glic/test_support/glic_browser_test.h). It opens Glic, waits for the guest to
reach the ready state, appends a cross-origin iframe to the guest’s main frame, and evaluates
typeof Mojo in that subframe. The test asserts the SECURE behaviour, so it fails on an affected
build.
It logs the guest main frame’s own typeof Mojo first as a positive control: a zero result in the
subframe means nothing unless the mechanism is shown to have run at all.
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION
Type of crash: N/A - this is not a crash. Crash State: N/A - no ASan report. The ASan build was used as the test configuration, not as the detector. Client ID (if relevant): N/A
CREDIT INFORMATION Reporter credit: Quyền Sơn (@zer0qs)