CVE-2026-78935
Overview
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_cobrowse_omnibox_client.mmios/chrome/browser/composebox/coordinator/composebox_omnibox_client.mm
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.