CVE-2026-78935
Overview
Background
- `delegate_`
- an Objective-C delegate instance pointer held by the omnibox client that supplies Lens suggestion data via
[delegate_ suggestInputs], and which may benil. - Objective-C message to `nil`
- sending a message to a
nilreceiver 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::optionalwill not do the right thing” fornildelegate_.) - `std::optional<T>`
- a wrapper that either holds a
Tor is empty (std::nullopt); its “engaged” flag and payload must be initialized correctly, orhas_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.
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
- 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 itnil. (inferred: the exact UI sequence that leavesdelegate_nilis not shown in the diff.) - Invoke the suggest-inputs query
Cause
GetLensOverlaySuggestInputsto be called; withdelegate_nil, control falls straight toreturn [delegate_ suggestInputs];. - Return an uninitialized optional
The
nilmessage send produces an uninitializedstd::optional<lens::proto::LensOverlaySuggestInputs>, whose engaged flag/payload contain garbage stack/register contents rather thanstd::nullopt. - 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
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
| Function | Change | Notes |
|---|---|---|
GetLensOverlaySuggestInputsios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm |
modified | |
ifios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm |
modified | |
GetLensOverlaySuggestInputsios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm |
modified | |
ifios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm |
modified |
Files Changed
ios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mmios/chrome/browser/composebox/coordinator/composebox_cobrowse_omnibox_client.mm
Audit Directions
- Objective-C++ methods returning C++ value types via a possibly-`nil` receiveraudit every
return [delegate_ ...](or othernil-able receiver) whose return type isstd::optional, a struct, or another non-trivial C++ value, and add an explicitnilguard. - Delegate lifetime vs. query surfacereview other
*OmniboxClientand coordinator classes for accessor methods that forward todelegate_without checking it, especially where the delegate can be unset or torn down independently of the client. - Uninitialized `std::optional` from FFI boundarieslook 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.
Patch
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]; }
Original Bug Report
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.mmios/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)
- An attacker directs the user to a malicious webpage.
- 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_valueflag of thestd::optionalis set to true. - The user interacts with the Composebox omnibox.
- A UI teardown is triggered (e.g., via navigation or user interaction).
ComposeboxInputPlateCoordinator::stop()is called._mediatoris set tonil, making the weakdelegate_pointernil.- A synchronous UI or autocomplete event triggers
_omniboxClient->GetInputState(). objc_msgSendreturns immediately becausedelegate_isnil, leaving the SRet buffer uninitialized and filled with the attacker’s sprayed stack data.- The
std::optionalis destructed, reading the fakehas_valueflag and subsequently freeing the attacker-controlled fake pointers. - 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.
Recommended Fix
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.