Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Core
DescriptionUse after free in Core
ComponentCore
Bug ClassUAF
Tracker497612174
Fix commitdcbf07463a2f (chromium/src) +10/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
content/browser/child_process_launcher_helper_ios.mm
modified

Files Changed

  • content/browser/child_process_launcher_helper_ios.mm
From dcbf07463a2f9e0566e398f8c46b38dd452c28ba Mon Sep 17 00:00:00 2001
From: Dave Tapuska <dtapuska@chromium.org>
Date: Fri, 01 May 2026 09:51:51 -0700
Subject: [PATCH] [ios blink] Fix launch helper

There could be a potential race in determining whether the process
was dead because of multiple threads updating the state object.
Use a std::atomic so it is clear the process terminated.

BUG=497612174

Change-Id: Idd54f2252e94f529a87898b47155af5e4129408b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7810074
Reviewed-by: Justin Cohen <justincohen@google.com>
Commit-Queue: Dave Tapuska <dtapuska@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1623876}
---

diff --git a/content/browser/child_process_launcher_helper_ios.mm b/content/browser/child_process_launcher_helper_ios.mm
index cc35e95..822a8b0 100644
--- a/content/browser/child_process_launcher_helper_ios.mm
+++ b/content/browser/child_process_launcher_helper_ios.mm
@@ -11,6 +11,7 @@
 
 #import <BrowserEngineKit/BrowserEngineKit.h>
 
+#include <atomic>
 #include <list>
 
 #include "base/apple/mach_port_rendezvous_ios.h"
@@ -158,12 +159,18 @@
 
   ~ProcessStorage() override { [grant_ invalidate]; }
 
-  void ReleaseProcess() override { process_ = nullptr; }
+  void ReleaseProcess() override {
+    terminated_ = true;
+    process_ = nullptr;
+  }
 
   NSObject* Process() { return process_; }
 
+  bool IsTerminated() const { return terminated_; }
+
  private:
   NSObject* process_;
+  std::atomic<bool> terminated_ = false;
   [[maybe_unused]] xpc_connection_t ipc_channel_;
   id<BEProcessCapabilityGrant> grant_;
 };
@@ -464,8 +471,8 @@
   ChildProcessTerminationInfo info;
   if (!process_storage_) {
     info.status = base::TERMINATION_STATUS_LAUNCH_FAILED;
-  } else if (static_cast<ProcessStorage*>(process_storage_.get())->Process() ==
-             nullptr) {
+  } else if (static_cast<ProcessStorage*>(process_storage_.get())
+                 ->IsTerminated()) {
     if (exit_code_.has_value()) {
       if (exit_code_.value() == RESULT_CODE_NORMAL_EXIT) {
         info.status = base::TERMINATION_STATUS_NORMAL_TERMINATION;
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential Use-After-Free in Browser Process via Data Race in ChildProcessLauncherHelper

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A data race in ChildProcessLauncherHelper on iOS allows a potential use-after-free (UAF) of an ARC-managed process object. The UI thread reads the process object without synchronization while a background thread clears it, which under ARC can lead to retaining a deallocated object.

Affected files:

  • content/browser/child_process_launcher_helper_ios.mm
  • content/browser/child_process_launcher_helper.h

Estimated timestamp from git blame: 2024-04-12

Summary

A potential Use-After-Free (UAF) vulnerability exists in the browser process on iOS. It is caused by an unsynchronized cross-thread access to an ARC-managed strong pointer (process_) within the ProcessStorage class. Objective-C ARC pointer reads and writes are not atomic. A race condition between the UI thread reading the pointer and background termination handlers clearing it can cause ARC to retain a deallocated object, leading to a UAF.

Technical Details

In content/browser/child_process_launcher_helper_ios.mm, the ProcessStorage class manages the lifetime of iOS child process objects (BEWebContentProcess, BENetworkingProcess, or BERenderingProcess):

class ProcessStorage : public ProcessStorageBase {
 public:
  // ...
  void ReleaseProcess() override { process_ = nullptr; }
  NSObject* Process() { return process_; }
 private:
  NSObject* process_; // ARC-managed __strong pointer
  // ...
};

There are two conflicting paths accessing process_:

  1. Writer Path (Background Thread): When a child process terminates, an XPC error handler or interruption handler runs on an arbitrary background libdispatch queue. This handler calls OnChildProcessTerminatedOnAnyThread, which acquires g_process_table_lock_ and eventually calls ReleaseProcess(). This sets process_ = nullptr, which ARC translates to an objc_storeStrong call that decrements the object’s reference count and deallocates it if the count reaches zero.

  2. Reader Path (UI Thread): When the UI thread handles a Mojo channel error (e.g., in RenderProcessHostImpl::OnChannelError), it calls ChildProcessLauncherHelper::GetTerminationInfo. This method checks static_cast<ProcessStorage*>(process_storage_.get())->Process() == nullptr to determine the process status. Crucially, this read does not acquire g_process_table_lock_ or any other synchronization primitive.

The Race and UAF

When the UI thread calls Process(), ARC generates code to load the pointer and typically calls objc_retain (or objc_retainAutoreleasedReturnValue) to ensure the returned object remains valid. If the UI thread loads the pointer value, and then the background thread clears process_ and releases the object before the UI thread can call objc_retain, the object may be deallocated. The UI thread will then call objc_retain on a freed object, dereferencing its isa pointer and causing a Use-After-Free.

This occurs in the highly privileged browser process. Additionally, because the pointer is an Objective-C ARC pointer, it is not protected by MiraclePtr (BackupRefPtr).

Potential Exploitation Steps

Note: These are theoretical steps, as our tooling agent cannot execute code to provide a working PoC.

  1. An attacker exploits a renderer process to gain arbitrary code execution within the sandbox.
  2. The attacker performs heap grooming in the browser process via IPC, preparing malicious payloads to replace the BEWebContentProcess object in memory.
  3. The attacker deliberately crashes the renderer process or drops the Mojo connection, triggering concurrent termination handling in the browser process: the Mojo channel error on the UI thread and the XPC error on a background thread.
  4. If the UI thread loads the process_ pointer and is immediately preempted, the background thread may execute ReleaseProcess(), dropping the reference count to zero and freeing the object.
  5. The attacker’s groomed data is immediately reallocated into the freed memory space, forging the isa pointer.
  6. The UI thread resumes and calls objc_retain on the attacker-controlled memory, hijacking the Objective-C method dispatch to redirect control flow to an attacker-controlled ROP chain, achieving arbitrary code execution and a full sandbox escape.

Suggested Fix

Synchronize access to the process_ pointer and exit_code_ within ChildProcessLauncherHelper. One solution is to require g_process_table_lock_ to be held when accessing process_storage_ in GetTerminationInfo, or by using explicit thread-safe primitives (like std::atomic or locks) to manage the Objective-C pointer access.

Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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