CVE-2026-113233
Overview
Files Changed
content/public/android/java/src/org/chromium/content_public/app/ZygotePreload.javadocs/security/android-sandbox.mdbase/android/linker/linker_jni.hbase/android/java/src/org/chromium/base/library_loader/Linker.javadocs/android_native_libraries.md
Patch
From 527c7a7b44a58f04ba00a70ec72561546c03418d Mon Sep 17 00:00:00 2001 From: Egor Pasko <[email protected]> Date: Wed, 23 Sep 2026 04:02:15 -0700 Subject: [PATCH] doc: Clarify security properties of zygote preload Hopefully this covers the trickiest nuances of the current security model around Android App Zygote, Zygote Preload and RELRO sharing. I'm writing it mainly to avoid false positive 'potential vulnerabilities' reported by LLMs. Fixed: 562111801 Change-Id: Ied2a3a37939e94eca0e3498d563450d354d8ee34 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8445916 Commit-Queue: Egor Pasko <[email protected]> Reviewed-by: Yaron Friedman <[email protected]> Cr-Commit-Position: refs/heads/main@{#1703315} --- diff --git a/base/android/java/src/org/chromium/base/library_loader/Linker.java b/base/android/java/src/org/chromium/base/library_loader/Linker.java index c0d4526..d307450 100644 --- a/base/android/java/src/org/chromium/base/library_loader/Linker.java +++ b/base/android/java/src/org/chromium/base/library_loader/Linker.java @@ -49,10 +49,16 @@ * * Security considerations: * - * - The shared RELRO memory region is always forced read-only after creation, which means it is + * - The shared RELRO memory region is always forced readonly after creation, which means it is * impossible for a compromised process to map it read-write (e.g. by calling mmap() or * mprotect()) and modify its content, altering values seen in other processes. * + * - The App Zygote process is guaranteed (by Android) to be uncompromised. See + * //docs/security/android-sandbox.md#zygote. + * + * - When the RELRO region is taken from an isolated process, it should be done before any untrusted + * data or code reaches that process. + * * - The common library load addresses are randomized for each instance of the program on the * device. * diff --git a/base/android/linker/linker_jni.h b/base/android/linker/linker_jni.h index e19486e5..e52931a 100644 --- a/base/android/linker/linker_jni.h +++ b/base/android/linker/linker_jni.h @@ -301,10 +301,10 @@ // // The external NativeLibInfo can arrive from a different process. // - // Note on security: The RELRO region is treated as *trusted*, no untrusted - // user/website/network input can be processed in an isolated process before - // it sends the RELRO FD. This is because there is no way to check whether the - // process has a writable mapping of the region remaining. + // Note on security: The RELRO region is treated as *trusted* because the App + // Zygote does not host untrusted code/data before it sends the RELRO FD. The + // Linker does not check write protection of the region for simplicity. See + // //docs/security/android-sandbox.md#zygote. bool CompareRelroAndReplaceItBy(const NativeLibInfo& other_lib_info); void set_relro_info_for_testing(uintptr_t start, size_t size) { diff --git a/content/public/android/java/src/org/chromium/content_public/app/ZygotePreload.java b/content/public/android/java/src/org/chromium/content_public/app/ZygotePreload.java index ef11eea..e47e0fae 100644 --- a/content/public/android/java/src/org/chromium/content_public/app/ZygotePreload.java +++ b/content/public/android/java/src/org/chromium/content_public/app/ZygotePreload.java @@ -19,11 +19,15 @@ import org.chromium.build.annotations.NullMarked; /** - * Class used in android:zygotePreloadName attribute of manifest. - * Code in this class runs in the zygote. It runs in a limited environment - * (eg no application) and cannot communicate with any other app process, - * so care must be taken when writing code in this class. Eg it should not - * create any thread. + * Class used in android:zygotePreloadName attribute of manifest. Code in this class runs in the App + * Zygote. It runs in a limited environment (e.g. no Application context) and cannot communicate + * with any other app process, so care must be taken when writing code in this class: + * + * <ul> + * <li>It must not create any threads. + * <li>It must not retain writable file descriptors or mutable shared memory across forking (see + * //docs/security/android-sandbox.md#zygote). + * </ul> */ @RequiresApi(Build.VERSION_CODES.Q) @NullMarked diff --git a/docs/android_native_libraries.md b/docs/android_native_libraries.md index e56bb32..45ec5ebf 100644 --- a/docs/android_native_libraries.md +++ b/docs/android_native_libraries.md @@ -167,6 +167,17 @@ * For non-renderer processes, the above Android N+ logic applies. * For renderer processes, the OS starts all Monochrome renderer processes by `fork()`ing the WebView zygote rather than the normal application zygote. +**Security properties:** + * When the Browser process consumes a shared RELRO FD produced by the App + Zygote, it relies on the App Zygote not hosting untrusted code or data. The + initial forked child process transferring the `IRelroLibInfo` parcel during + `ChildProcessService.setupConnection()` must be done before any untrusted web + or network input is processed. + * After populating the shared memory region, the producer unmaps its temporary + writable mapping and marks the FD read-only. + * For the broader App Zygote threat model and cross-process isolation + invariants, see [Chrome Android Sandbox Design](security/android-sandbox.md#zygote). + ## Library Prefetching diff --git a/docs/security/android-sandbox.md b/docs/security/android-sandbox.md index ea950b33..3f86715 100644 --- a/docs/security/android-sandbox.md +++ b/docs/security/android-sandbox.md @@ -104,6 +104,37 @@ processes. This extends the benefits of lower initialization time and greater memory sharing from the system zygote to individual applications. +With greater memory sharing comes greater responsibility. + +When about to fork, the Zygote checks that all its open File Descriptors (FDs) +are in its allowlist. The allowlist includes useful files like /dev/urandom, IPC +sockets, config files, system libraries and APKs. Automatically excluding other +files protects apps against accidental exposure of Android Framework internals +(like ART). + +One particular risk of exposing open FDs in child processes is that their offset +position can be changed in isolated processes. A malicious call to `lseek(2)` +from an unprivileged context could potentially confuse privileged code reading +or writing from/to these shared FDs. + +However, FDs open during Zygote Preload are not checked against the allowlist +above. Chrome is therefore fully responsible for controlling the added shared +state created during Preload. Keeping an open file descriptor or a memory +mapping (or a native library loaded) past Zygote Preload makes them shared +across all isolated processes. There is no automatic protection against +accidental cross-process leaks like this. + +[RELRO Sharing](../android_native_libraries.md#relro-sharing) is one example of +a readonly memory region (and its FD) created during Preload and shared across +all process types in Chrome. In this case Preload must make sure to unmap all +writable mappings and convert the region to readonly (PROT_READ for ashmem, +F_SEAL_WRITE or F_SEAL_FUTURE_WRITE for memfd). + +Chrome trusts the App Zygote (`app_zygote` SELinux domain) and its child process +bootstrap to never host untrusted code or data. Chrome could potentially +mitigate some kinds of zygote contamination, but these checks would not be +effective at protecting against leaking web credentials. + ## Sandboxing Similar to the [Linux sandbox design](../linux/sandboxing.md), which uses a @@ -131,6 +162,10 @@ - [**isolated_app**](https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/isolated_app.te;drc=941ba723baceac19151560e8a1d2830b9be6493c) is a restrictive sandbox that can be applied via the `<service android:isolatedProcess="true">` tag in an application's manifest +- [**app_zygote**](https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/app_zygote.te;drc=9128735f1f02603c6c35de40a5c2eeb6e8cdc2e6) + is similarly restrictive to `isolated_app`; it is additionally allowed to + access/open executable and static application files for preload, allowed to + fork into `isolated_app` In Chrome, the browser process runs under the **untrusted_app** SELinux domain, which enforces separation between distinct apps on the system.
Original Bug Report
Possible vuln in commit 'Android: Enable chromium linker for non-trichrome APKs'
Flapjack has identified 1 potential security vulnerability finding(s) for commit 569ddcb27df9 (Redirect: crrev.com/569ddcb27df986b3c70f050ac605193a0a3d6d1d).
Missing RELRO Shared Memory sealing check allows Sandbox Escape / Browser Process RCE
Overview: The Chromium Linker on Android S+ allows a less-privileged App Zygote or child process to provide the RELRO segment to the highly-privileged Browser process. However, the Browser process maps this shared memory region without verifying whether the incoming file descriptor is sealed against writes, allowing a compromised child process to overwrite C++ vtables and execute arbitrary code in the Browser context.
Affected files:
base/android/linker/linker_jni.ccbase/android/java/src/org/chromium/base/library_loader/LibraryLoader.javabase/android/java/src/org/chromium/base/library_loader/Linker.javacontent/public/android/java/src/org/chromium/content/browser/ChildProcessLauncherHelperImpl.java
Description
On Android S (API 31) and above, Chromium enables the Chromium Linker (used for shared RELROs) for all non-trichrome APKs (such as the standard Chrome Public APK). In this architecture, the less-privileged App Zygote acts as the RELRO producer and the highly-privileged Browser process acts as the RELRO consumer. This is controlled in Java by LibraryLoader.mainProcessIntendsToProvideRelroFd(), which returns false on Android S+.
When the Browser process launches a child process, it retrieves the IRelroLibInfo parcel via ChildProcessLauncherHelperImpl.distributeZygoteInfo and Linker.takeSharedRelrosFromAidl. This eventually calls NativeLibInfo::CompareRelroAndReplaceItBy in base/android/linker/linker_jni.cc to map and replace the Browser’s own RELRO section (.data.rel.ro).
Vulnerability Details
During this replacement process:
NativeLibInfo::RelroIsIdenticalverifies that the incoming RELRO shared memory contents are identical to its own viamemcmp.- Crucially, the Browser process never verifies whether the received file descriptor (
relro_fd_) has been sealed read-only (such as checkingF_SEAL_FUTURE_WRITEorF_SEAL_WRITEseals on memfd or equivalent protection flags on ashmem). - Once the
memcmpcheck succeeds,NativeLibInfo::ReplaceRelroWithSharedOnemaps the incoming file descriptor on top of the Browser’s RELRO section usingMAP_FIXED | MAP_SHAREDandPROT_READprotection.
Impact & Exploitation Scenario
If an attacker compromises the App Zygote or a child process that retains a writable handle/file descriptor to the shared memory region, they can perform a Time-of-Check to Time-of-Use (TOCTOU) style attack:
- The compromised process sends an unsealed, writable
relro_fd_to the Browser process while keeping a writable mapping to the region active. - The initial
memcmpverification succeeds since the contents are initially identical. - The Browser process maps the file descriptor as
MAP_SHAREDover its own.data.rel.rosection. - Because the region is unsealed and the attacker still has a writable mapping to it, the attacker can overwrite C++ virtual method tables (vtables) or function pointers in the shared memory region at any time.
- When the Browser process invokes a virtual method on an object whose vtable resides in the RELRO section, it dereferences the hijacked pointer and transfers control flow to the attacker’s payload.
This leads to Arbitrary Code Execution (RCE) in the unsandboxed Browser process, achieving a full Sandbox Escape.
Remediation
The shared memory verification logic must ensure that the received file descriptor is sealed against writes (e.g., checking that F_GET_SEALS contains F_SEAL_FUTURE_WRITE and F_SEAL_WRITE) before mapping it as a shared RELRO segment.
The descriptions of these vulnerabilities are LLM-generated and can contain mistakes. Your feedback is appreciated and will help us make improvements over time. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.