Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Desktop
DescriptionUse after free in Desktop
ComponentDesktop
Sub-componentUI>Browser>New Tab Page (use subcomponent)>Desktop
Bug ClassUAF
Tracker540499008
Fix commit9f614e12873b (chromium/src) +7/-0
CISA KEVNot listed
CreditedFindingz
Disclosed2026-10-13

Changed Functions

FunctionChangeNotes
if
chrome/renderer/searchbox/searchbox.cc
modified

Files Changed

  • chrome/renderer/searchbox/searchbox.cc
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;
+        }
       }
     }
   }
Loading diff…

Original Bug Report

reported by al...@gmail.com

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:

  1. asks its Service Worker to navigate the current NTP cross-process;
  2. enters window.print(), which runs a nested renderer loop;
  3. 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.

  1. Start the server:

    python server.py
    
  2. 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.

  3. In that same disposable profile, select PrivacyWall as the default search provider through Chrome’s normal settings UI.

  4. Open chrome://newtab/ twice. The first tab installs and pins the Service Worker. The second tab verifies embeddedSearch admission and triggers automatically.

  5. 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

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

View on issue tracker