CVE-2026-110162
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/renderer/searchbox/searchbox.cc |
modified |
Files Changed
chrome/renderer/searchbox/searchbox.cc
Patch
From 9f614e12873bc42267289c5aa56ea6e4eea32e19 Mon Sep 17 00:00:00 2001
From: Justin Donnelly <jdonnelly@chromium.org>
Date: Thu, 17 Sep 2026 06:18:36 -0700
Subject: [PATCH] Prevent potential UAF in the embedded search API searchbox.
Dispatching the key capture change event executes JavaScript, which can destroy the SearchBox instance. This adds a WeakPtr check after dispatching the event to ensure the object is still alive before continuing execution.
Fixed: b:540499008
Change-Id: Ia14f375ab850de4d8542ed4f6470b0e7d207c549
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8407751
Commit-Queue: Justin Donnelly <jdonnelly@chromium.org>
Reviewed-by: Tibor Goldschwendt <tiborg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1699733}
---
diff --git a/chrome/renderer/searchbox/searchbox.cc b/chrome/renderer/searchbox/searchbox.cc
index b6b9527..f5cbdd88 100644
--- a/chrome/renderer/searchbox/searchbox.cc
+++ b/chrome/renderer/searchbox/searchbox.cc
@@ -282,8 +282,15 @@
is_key_capture_enabled_ = key_capture_enabled;
DVLOG(1) << render_frame() << " KeyCaptureChange";
if (can_run_js_in_renderframe_) {
+ base::WeakPtr<SearchBox> weak_this = weak_ptr_factory_.GetWeakPtr();
SearchBoxExtension::DispatchKeyCaptureChange(
render_frame()->GetWebFrame());
+ // The onkeycapturechange event may have destroyed the SearchBox. Exit
+ // early if that is the case to avoid UAF. See
+ // https://crbug.com/540499008 for additional details.
+ if (!weak_this) {
+ return;
+ }
}
}
}
Original Bug Report
Renderer use-after-free in SearchBox::FocusChanged leading to an attacker-controlled 32-bit write
Report description
Renderer use-after-free in SearchBox::FocusChanged leading to an attacker-controlled 32-bit write
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
https://chromium.googlesource.com/chromium/src/+/main/chrome/renderer/searchbox/searchbox.cc
The problem
Please describe the technical details of the vulnerability
Renderer use-after-free in SearchBox::FocusChanged leading to an attacker-controlled 32-bit write
Summary
SearchBox::FocusChanged() dispatches the onkeycapturechange event to a
remote New Tab Page and then continues to access this. The event runs
provider-controlled JavaScript synchronously. That script can start a
cross-process navigation and enter the print nested loop, allowing the
RenderFrame and its SearchBox observer to be destroyed before the event
dispatch returns.
Control then resumes in the freed SearchBox. The first invalid operation is
a one-byte read of is_focused_. If the focus value differs, the next
instruction writes one byte to the freed object.
I reproduced the UAF twice without a debugger in Google’s official Windows
ASan build 152.0.7939.3. ASan reports:
ERROR: AddressSanitizer: heap-use-after-free
READ of size 1
SearchBox::FocusChanged
chrome/renderer/searchbox/searchbox.cc:290
MiraclePtr Status: NOT PROTECTED
This crash is still exploitable with MiraclePtr.
ASan stops at that first stale read. The attacker-controlled 32-bit write in
the title is a separate Stable x86 exploitability result measured below; it is
not inferred from the ASan READ of size 1 line.
I also reproduced the renderer access violation in Google Chrome Stable
150.0.7871.187. On the shipped x86 product, a diagnostic exploitability
harness progressed from the same stale callback to an attacker-selected
Guard-CF target and a constrained chosen-DWORD write. I do not claim an
autonomous RCE or a sandbox escape.
Affected surface
When a search provider is installed, selected as default, and supplies a
remote NTP, Chrome runs that NTP in an Instant renderer and exposes the
chrome.embeddedSearch.searchBox API. Any such provider can therefore reach
this code from its NTP document. This is the generic provider path;
PrivacyWall is only the provider used by the local PoC.
Once the NTP loads, the trigger is page-only: no click or keystroke inside the page is required. A malicious provider, or an attacker who can execute script in that provider’s NTP document, can exercise the vulnerable callback.
Root cause
The vulnerable Stable source is
chrome/renderer/searchbox/searchbox.cc:268-296:
void SearchBox::FocusChanged(OmniboxFocusState new_focus_state,
OmniboxFocusChangeReason reason) {
bool key_capture_enabled = new_focus_state == OMNIBOX_FOCUS_INVISIBLE;
if (key_capture_enabled != is_key_capture_enabled_) {
if (reason != OMNIBOX_FOCUS_CHANGE_TYPING) {
is_key_capture_enabled_ = key_capture_enabled;
if (can_run_js_in_renderframe_) {
SearchBoxExtension::DispatchKeyCaptureChange(
render_frame()->GetWebFrame());
}
}
}
bool is_focused = new_focus_state == OMNIBOX_FOCUS_VISIBLE;
if (is_focused != is_focused_) {
is_focused_ = is_focused;
if (can_run_js_in_renderframe_)
SearchBoxExtension::DispatchFocusChange(render_frame()->GetWebFrame());
}
}
DispatchKeyCaptureChange() calls provider JavaScript synchronously. During
that callback, the PoC:
- asks its Service Worker to navigate the current NTP cross-process;
- enters
window.print(), which runs a nested renderer loop; - lets the pending navigation commit inside that loop.
The commit destroys RenderFrameImpl, which destroys the observer and calls:
void SearchBox::OnDestruct() {
delete this;
}
The JavaScript dispatch then returns to the still-active
FocusChanged() frame. Line 289 reads state from the freed object; line 291
performs the conditional stale byte write.
SearchBox already contains a WeakPtrFactory, but this synchronous
JavaScript boundary does not use it.
Proof of concept
Attachments poc.html and server.py form a minimal loopback PoC. The Python
server serves the remote NTP, Service Worker, redirect, and cross-site target.
The local certificate and exact launch command are embedded in server.py.
The PoC crashes a vulnerable renderer. Use a disposable profile.
-
Start the server:
python server.py -
Launch the target Chrome with this command, replacing only the executable and disposable profile paths:
chrome.exe --user-data-dir="C:\fresh-searchbox-profile" --no-first-run --no-default-browser-check --host-resolver-rules="MAP www.privacywall.org:443 127.0.0.1:8443,MAP target.test 127.0.0.1,EXCLUDE localhost" --ignore-certificate-errors-spki-list=xkyrnR2Zb9SYwTCmi6GjkrYnkmiz76jwP4EA2WjSKW0=The host mapping and SPKI switch route the two test origins to loopback and trust only the PoC certificate. They do not disable the renderer sandbox or alter the memory allocator.
-
In that same disposable profile, select PrivacyWall as the default search provider through Chrome’s normal settings UI.
-
Open
chrome://newtab/twice. The first tab installs and pins the Service Worker. The second tab verifiesembeddedSearchadmission and triggers automatically. -
Observe the renderer ASan report.
The PoC does not use CDP, MojoJS, --no-sandbox, --single-process, a patched
browser, or synthetic page input.
Expected page/server sequence:
[+] remote NTP load 1: pin
[+] remote NTP load 2: trigger
[page] PASS: embeddedSearch admitted
[page] TRIGGER: navigation + print nested loop
[+] cross-site navigation requested
[+] releasing cross-site navigation
The attached asan_uaf.log is a complete debugger-free report from the
official ASan product. Its deallocation stack contains
SearchBox::~SearchBox and RenderFrameImpl::~RenderFrameImpl; its allocation
stack contains ChromeContentRendererClient::RenderFrameCreated.
Stable x86 exploitability result
I tested the shipped 32-bit Stable product with the normal renderer sandbox and multiprocess model retained:
OS: Windows 11
Google Chrome: 150.0.7871.187
Chromium: 30f6543ae91e6a860e73b76e3216b663b050f4e5
chrome.exe: 13d12159484e2c156f7efd94af6e7a930e6915b483a767e7f28629976219a8c2
chrome.dll: bdfa15e550ab0e6ffaddd1f62ba76aede638bec0ddd37b430c44303a40469d83
PartitionAlloc’s 32-bit freelist shadow allowed the stale pointer to resolve
into page-retained ArrayBuffer memory. The page shaped a fake object and
selected a valid entry from the product’s Guard CF table. The selected native
target copied an independently selected DWORD to an independently selected
destination.
The measured transition was:
destination: 0x6b6b746b
before: 0x11223344
value: 0x41424344
after: 0x41424344
adjacent: 0x55667788 -> 0x00000000
The target also performs mandatory collateral mutations: it replaces one source pointer with the destination, decrements one counter, and clears two fake-object fields. The primitive is therefore a constrained chosen-DWORD write, not an unconstrained arbitrary write.
Attachment stable_x86_chosen_dword.log is the raw ordered CDB receipt. CDB
resolved the randomized module base and observed fixed product instructions;
it did not perform the selected write. Attachment
stable_x86_write_receipt.json records the independent page readback, the
collateral state, the complete native return, and the live renderer probe.
Impact and limits
An installed search provider selected as default and supplying a remote NTP can trigger a renderer heap-use-after-free without any further page interaction. The allocation is not protected by MiraclePtr. The Stable x86 result shows that the stale callback can progress beyond a crash to an attacker-selected CFG-valid native target and a constrained chosen-DWORD write, with page-visible readback and a complete native return.
The x86 diagnostic used CDB to resolve the randomized module base. It does not prove an in-product ASLR disclosure, an unconstrained arbitrary write, autonomous code execution, a sandbox escape, or the same exploitation primitive on x64.
Regression
The vulnerable continuation was introduced by commit
8d4c276d84f9612b25170183e92265aaa973625d
on 2013-05-29. That change merged key-capture and focus updates into one
handler, leaving the new is_focused_ access after the synchronous
DispatchKeyCaptureChange() callback.
The same unguarded sequence remains in main revision
d7c51995d79850bbb09f5e745478674352d9187d
checked on 2026-07-29.
Attachment bisection.md contains the introducing diff and source hashes.
Suggested fix
The invariant should be: after dispatching provider JavaScript, do not access
SearchBox members until a previously captured weak pointer proves that the
object survived.
if (can_run_js_in_renderframe_) {
base::WeakPtr<SearchBox> weak_this = weak_ptr_factory_.GetWeakPtr();
SearchBoxExtension::DispatchKeyCaptureChange(
render_frame()->GetWebFrame());
if (!weak_this) {
return;
}
}
Attachment proposed_fix.patch applies this guard. A regression test should
destroy the RenderFrame synchronously from the key-capture callback and
verify that FocusChanged() returns without reading the later focus state.
Impact analysis
When a search provider is installed, selected as default, and supplies a
remote New Tab Page, Chrome exposes the privileged SearchBox API to that
provider’s page. The provider can destroy its renderer frame during the
synchronous onkeycapturechange callback without a page click or keystroke.
SearchBox::FocusChanged() then resumes and accesses the freed renderer
object. The official Windows ASan product reports a one-byte heap-use-after-free
read with MiraclePtr Status: NOT PROTECTED.
On Chrome Stable x86, a module-base-assisted diagnostic harness progressed from the stale callback to an attacker-selected Guard-CF target and a constrained chosen-DWORD write with documented collateral effects. The renderer sandbox remained enabled. I do not claim an autonomous RCE, arbitrary read, or sandbox escape.
The cause
What version of Chrome have you found the security issue in?
150.0.7871.187 Stable x86; UAF also reproduced in official Windows ASan 152.0.7939.3
Is the security issue related to a crash?
Yes, it is related to a crash.
Choose the type of vulnerability
Memory Corruption (in a sandboxed process)
How would you like to be publicly acknowledged for your report?
Findingz
- https://bughunters.google.com/about/rules/5745167867576320/chrome-vulnerability-reward-program-rules
- https://chromium.googlesource.com/chromium/src/+/30f6543ae91e6a860e73b76e3216b663b050f4e5/chrome/renderer/searchbox/searchbox.cc#268
- https://chromium.googlesource.com/chromium/src/+/8d4c276d84f9612b25170183e92265aaa973625d
- https://chromium.googlesource.com/chromium/src/+/d7c51995d79850bbb09f5e745478674352d9187d/chrome/renderer/searchbox/searchbox.cc#268
- https://chromium.googlesource.com/chromium/src/+/main/chrome/renderer/searchbox/searchbox.cc