Critical watch CVSS 9.6 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse of uninitialized variable in Mobile
DescriptionUse of uninitialized variable in Mobile
ComponentMobile
Sub-componentiOS
Bug ClassUninitialized Memory
Action tierwatch — ub-class only — no demonstrated corruption path
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.
Tracker522082472
Fix commitd86ba31be3c1 (chromium/src) +6/-0
CWECWE-457 (Use of uninitialized)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-10 (per vendor advisory)
Fix landed2026-07-02 (public git)
Disclosed2026-08-25
Patch gap54 days — fix public in source before the advisory

Background

`delegate_`
an Objective-C delegate instance pointer held by the omnibox client that supplies Lens suggestion data via [delegate_ suggestInputs], and which may be nil.
Objective-C message to `nil`
sending a message to a nil receiver is legal in Objective-C, but when the method returns a C++ struct/object by value the returned bytes are left uninitialized rather than being zeroed. (inferred: the specific ABI behavior; the diff and commit only state that “std::optional will not do the right thing” for nil delegate_.)
`std::optional<T>`
a wrapper that either holds a T or is empty (std::nullopt); its “engaged” flag and payload must be initialized correctly, or has_value() / dereference read garbage.
`GetLensOverlaySuggestInputs`
the client method that returns std::optional<lens::proto::LensOverlaySuggestInputs> by forwarding to the delegate.

Root Cause Analysis

Both ComposeboxOmniboxClient::GetLensOverlaySuggestInputs and ComposeboxCobrowseOmniboxClient::GetLensOverlaySuggestInputs unconditionally executed return [delegate_ suggestInputs];. When delegate_ is nil, the Objective-C message send does not run any method body, yet the call site still expects a fully constructed std::optional<lens::proto::LensOverlaySuggestInputs> return value; per the commit message this leaves the std::optional in a state where it “will not do the right thing,” i.e. its engaged flag and payload are not properly initialized. The violated invariant is that a returned std::optional must be either a valid std::nullopt or a valid engaged value — never uninitialized.

The fix adds if (!delegate_) { return std::nullopt; } before the message send in each function, so the nil-delegate path now returns a well-defined empty optional. Because the guard executes and returns before [delegate_ suggestInputs] is ever reached, the uninitialized value is never produced.

Key insight
The core mistake is forwarding a by-value std::optional return through an Objective-C message to a possibly-nil delegate_, which yields an uninitialized optional instead of an empty one; the fix short-circuits with an explicit return std::nullopt; when delegate_ is nil.

Attack Path

  1. Reach a state with a `nil` delegate Trigger the composebox / Lens overlay omnibox flow on iOS at a moment when the omnibox client’s delegate_ has not been set or has already been torn down, leaving it nil. (inferred: the exact UI sequence that leaves delegate_ nil is not shown in the diff.)
  2. Invoke the suggest-inputs query Cause GetLensOverlaySuggestInputs to be called; with delegate_ nil, control falls straight to return [delegate_ suggestInputs];.
  3. Return an uninitialized optional The nil message send produces an uninitialized std::optional<lens::proto::LensOverlaySuggestInputs>, whose engaged flag/payload contain garbage stack/register contents rather than std::nullopt.
  4. Consume the garbage optional Downstream code that calls has_value() or dereferences the optional acts on uninitialized memory, leading to undefined behavior. (inferred: the specific consumer and any resulting memory-safety escalation are not present in the diff.)

Impact Assessment

An attacker who can drive the omnibox/Lens suggest-inputs path while delegate_ is nil obtains use of an uninitialized std::optional in the browser process on iOS (Mobile component), with the precondition that the code reaches GetLensOverlaySuggestInputs before a valid delegate_ is assigned. This is classified by the advisory as use of an uninitialized variable; any escalation to information disclosure, memory corruption, or code execution beyond consuming the uninitialized optional is (inferred) and not demonstrated by the patch. The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6.

Changed Functions

FunctionChangeNotes
GetLensOverlaySuggestInputs
ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm
modified
if
ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm
modified
GetLensOverlaySuggestInputs
ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
modified
if
ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
modified

Files Changed

  • ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
  • ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm

Audit Directions

  • Objective-C++ methods returning C++ value types via a possibly-`nil` receiver
    audit every return [delegate_ ...] (or other nil-able receiver) whose return type is std::optional, a struct, or another non-trivial C++ value, and add an explicit nil guard.
  • Delegate lifetime vs. query surface
    review other *OmniboxClient and coordinator classes for accessor methods that forward to delegate_ without checking it, especially where the delegate can be unset or torn down independently of the client.
  • Uninitialized `std::optional` from FFI boundaries
    look for optionals populated across Objective-C, C, or other ABI boundaries where a not-taken code path can leave the engaged flag uninitialized instead of empty.
From d86ba31be3c13f0e85c4d34150fe0b0964711cf6 Mon Sep 17 00:00:00 2001
From: Stepan Khapugin <[email protected]>
Date: Thu, 02 Jul 2026 09:41:35 -0700
Subject: [PATCH] [iOS] Return std::nullopt when necessary.

When delegate_ is nil, std::optional will not do the right thing.

Fixed: 522082472
Change-Id: Ie9e975b5c593aeed6b2e2b2b094e8a25200b4174
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8034007
Commit-Queue: Radu Nitescu <[email protected]>
Auto-Submit: Stepan Khapugin <[email protected]>
Commit-Queue: Stepan Khapugin <[email protected]>
Reviewed-by: Radu Nitescu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1656148}
---
diff --git a/ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm b/ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm
index 453a3aae..e5e9391 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm
+++ b/ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm
@@ -176,6 +176,9 @@
 
 std::optional<lens::proto::LensOverlaySuggestInputs>
 ComposeboxCobrowseOmniboxClient::GetLensOverlaySuggestInputs() const {
+  if (!delegate_) {
+    return std::nullopt;
+  }
   return [delegate_ suggestInputs];
 }
 
diff --git a/ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm b/ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
index dbc93d09..e7b8c97 100644
--- a/ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
+++ b/ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
@@ -169,6 +169,9 @@
 
 std::optional<lens::proto::LensOverlaySuggestInputs>
 ComposeboxOmniboxClient::GetLensOverlaySuggestInputs() const {
+  if (!delegate_) {
+    return std::nullopt;
+  }
   return [delegate_ suggestInputs];
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-of-Uninitialized-Memory in Composebox via nil delegate message

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential use-of-uninitialized-memory vulnerability exists in the Composebox feature on iOS ARM64. Messaging a nil delegate to return a large C++ struct leaves the return buffer uninitialized due to SRet calling conventions, leading to a potential arbitrary free primitive when the resulting std::optional is destroyed.

Affected files:

  • ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
  • ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm

Estimated timestamp from git blame: 2025-11-18

Summary

A potential use-of-uninitialized-memory vulnerability exists in ComposeboxOmniboxClient and ComposeboxCobrowseOmniboxClient on iOS ARM64. The vulnerability is triggered when an Objective-C message is sent to a nil receiver (delegate_) that is expected to return a C++ struct larger than 16 bytes.

Due to the ARM64 SRet (Struct Return) calling convention and objc_msgSend behavior, messaging a nil receiver leaves the caller-allocated return buffer completely uninitialized. When the returned std::optional object is subsequently destroyed, it may attempt to free attacker-controlled pointers from the stack, resulting in an arbitrary free primitive and potential Remote Code Execution (RCE) in the browser process.

Technical Details

In ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm and ios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm, the methods GetInputState and GetLensOverlaySuggestInputs call methods on a __weak delegate (delegate_):

// ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
omnibox::InputState ComposeboxOmniboxClient::GetInputState() const {
  std::optional<contextual_search::InputState> state = [delegate_ inputState];
  return state.value_or(omnibox::InputState());
}

std::optional<lens::proto::LensOverlaySuggestInputs>
ComposeboxOmniboxClient::GetLensOverlaySuggestInputs() const {
  return [delegate_ suggestInputs];
}

The return types std::optional<contextual_search::InputState> (which aliases omnibox::InputState) and std::optional<lens::proto::LensOverlaySuggestInputs> are both C++ structs significantly larger than 16 bytes.

On the ARM64 architecture, the calling convention for functions returning a struct larger than 16 bytes requires the caller to allocate a return buffer on the stack and pass a pointer to it (the SRet pointer). If an Objective-C message is sent to a nil receiver expecting an SRet return, the runtime (objc_msgSend) returns immediately without initializing or zeroing the memory pointed to by the SRet pointer.

During UI teardown in ComposeboxInputPlateCoordinator::stop(), the _mediator (which acts as the delegate) is explicitly set to nil before the _omniboxCoordinator and its _client are stopped. If an autocomplete request or UI event synchronously queries GetInputState() or GetLensOverlaySuggestInputs() during this window, the C++ std::optional variable will contain uninitialized stack memory.

Impact

When the std::optional object goes out of scope and is destroyed, its destructor checks its boolean has_value flag. Because the memory is uninitialized, this flag may evaluate to true based on residual stack data. If so, the destructor of the underlying struct will be invoked on uninitialized memory.

Both contextual_search::InputState and lens::proto::LensOverlaySuggestInputs contain complex heap-managed members (e.g., std::vector, std::map, std::string, and Protobuf objects). Their destructors will attempt to call free() or delete on pointers read directly from the uninitialized stack buffer.

Potential Exploitation Steps

(Note: These are suggested theoretical steps as our tooling agent cannot execute dynamic proof-of-concepts)

  1. An attacker directs the user to a malicious webpage.
  2. The attacker uses JavaScript or WebAssembly to “spray” the browser process’s main thread stack with crafted data. This data includes fake heap pointers and a specific byte pattern to ensure the has_value flag of the std::optional is set to true.
  3. The user interacts with the Composebox omnibox.
  4. A UI teardown is triggered (e.g., via navigation or user interaction).
  5. ComposeboxInputPlateCoordinator::stop() is called. _mediator is set to nil, making the weak delegate_ pointer nil.
  6. A synchronous UI or autocomplete event triggers _omniboxClient->GetInputState().
  7. objc_msgSend returns immediately because delegate_ is nil, leaving the SRet buffer uninitialized and filled with the attacker’s sprayed stack data.
  8. The std::optional is destructed, reading the fake has_value flag and subsequently freeing the attacker-controlled fake pointers.
  9. This arbitrary free primitive is leveraged to corrupt heap metadata or free a targeted object, leading to full RCE and sandbox escape within the browser process.

Explicitly check if delegate_ is nil before sending the message, and return an empty std::optional or default-constructed object to prevent the Objective-C runtime from leaving the SRet buffer uninitialized:

omnibox::InputState ComposeboxOmniboxClient::GetInputState() const {
  if (!delegate_) {
    return omnibox::InputState();
  }
  std::optional<contextual_search::InputState> state = [delegate_ inputState];
  return state.value_or(omnibox::InputState());
}

std::optional<lens::proto::LensOverlaySuggestInputs>
ComposeboxOmniboxClient::GetLensOverlaySuggestInputs() const {
  if (!delegate_) {
    return std::nullopt;
  }
  return [delegate_ suggestInputs];
}

Similar checks should be applied in ComposeboxCobrowseOmniboxClient.

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker