blob: 3f86715be278fa096e408c57c42447b51708a787 [file] [view]
# Chrome Android Sandbox Design
This document discusses the sandbox and process architecture of Chrome on
Android. Like Chrome's desktop platforms, Android uses a sandbox to isolate and
de-privilege untrustworthy computing principals. However, the overall
architecture is materially different than on desktop platforms, while still
arriving close to the same end result.
## Overall Architecture
The Chrome Android browser process is the main trusted principal in the system,
it manages and communicates with a network of processes over inter-process
communication (IPC). The major features of the security architecture are:
- The browser process is protected by the [Android app
sandbox](https://source.android.com/docs/security/app-sandbox), which isolates
each installed application on the system.
- Chrome launches its helper processes as Android
[Services](https://developer.android.com/reference/android/app/Service).
- For sandboxed helper processes, the sandbox follows a similar bilayer design
as the desktop Linux sandbox. The layer-one sandbox is provided by the
[`android:isolatedProcess`](https://developer.android.com/guide/topics/manifest/service-element#isolated)
attribute on the Service's manifest definition.
- Chrome [applies](https://source.chromium.org/chromium/chromium/src/+/main:sandbox/linux/seccomp-bpf-helpers/seccomp_starter_android.h)
a layer-two sandbox in the form of a Seccomp-BPF system call filter.
![Diagram of the Android OS components and the parent/child and IPC relationships](android-sandbox-diagram.png)
<!-- Source: https://docs.google.com/drawings/d/11oVsYx_TCrPMglMMW009IFFCStPrLPFbAIVNPp647Lw/edit -->
## Processes
The below sections discuss three IPC transport mechanisms: Binder, UNIX sockets,
and Mojo. Binder is Android's primary IPC mechanism, implemented with a kernel
driver and a user `ioctl` client. UNIX socket IPC is used between certain
Android OS components, and it underpins Chrome's Mojo IPC mechanism. [Mojo
IPC](../../mojo/README.md) is the main IPC system used in Chrome to communicate
between components.
### Browser Process
The Chrome browser process on Android is unique compared to the security
architecture of Chrome on other platforms. On desktop systems, all applications
typically run as the same user principal (UID), and each application often has
the same view of the global filesystem and equal access to the files within.
On Android, each installed application is given a distinct user ID (UID) and a
restricted, scoped storage location. Applications only interact with each other
using the high-level
[Intents](https://developer.android.com/guide/components/intents-filters) IPC
system.
The implications of this are that Chrome on Android should generally be
less trusting of data coming from other apps. Similarly, Chrome should either
trust or take care to sanitize data it sends to other apps. For more details see
[Android IPC Security Considerations](android-ipc.md).
### Helper Processes
Chrome uses helper processes to segment and isolate the work it performs on
behalf of websites and the user. On desktop platforms, Chrome's browser process
has a parent-child relationship to the helper processes. But on Android, all
process creation goes through the Activity Manager and a zygote. The entrypoint
in Chrome for these processes is the
[`ContentChildProcessService`](https://source.chromium.org/chromium/chromium/src/+/main:content/public/android/java/src/org/chromium/content/app/ContentChildProcessService.java;drc=4e1b7bc33d42b401d7d9ad1dcba72883add3e2af),
which has both privileged (un-sandboxed) and sandboxed subclass variants.
The zygote architecture is similar to the [one used on
Linux](../linux/zygote.md), but the Android OS uses it for all processes on the
system. This design exists because starting the Android JVM from scratch in each
new process would be expensive, and the zygote enables cloning the running JVM
to improve performance and reduce memory usage by sharing non-dirty memory
pages.
A consequence of this design is that process creation on Android is more
involved than on other operating systems. Rather than a system call (or two,
e.g. `fork()` + `execve()`), process creation in Android involves several IPCs
through the system:
1. Browser sends a Binder IPC to Activity Manager for [`Context.bindService()`](https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.Context.BindServiceFlags,%20java.util.concurrent.Executor,%20android.content.ServiceConnection))
2. Activity Manager sends the zygote an IPC over a privileged UNIX socket to fork a process
3. Zygote responds to Activity Manager with the forked process ID
4. New process sends a Binder IPC to Activity Manager to connect with it
5. Activity Manager sends a Binder IPC to the new process with information about the APK to load
6. Activity Manager sends a Binder IPC channel connecting the new process to the browser process
process for [`ServiceConnection.onServiceConnected`](https://developer.android.com/reference/android/content/ServiceConnection#onServiceConnected(android.content.ComponentName,%20android.os.IBinder))
In Chrome, after the process is created, the browser process sends a file
descriptor to the new process over Binder to establish Chrome's Mojo IPC. From
then on, Chrome primarily uses Mojo IPC between the browser and the helper
process. The Binder IPC channel is retained in order to control the lifetime of
the helper process.
### Zygote
Starting with Android API v29, the OS offers applications the ability to create
per-app zygotes. An `isolatedProcess <service>` can be declared with
[`android:useAppZygote`](https://developer.android.com/reference/android/R.styleable#AndroidManifestService_useAppZygote)
and provide a [`ZygotePreload`](https://developer.android.com/reference/android/app/ZygotePreload)
implementation. This instructs the OS to spawn a new zygote, from the system
zygote, that will be used to launch all instances of that Service. By giving the
application the ability to participate via `ZygotePreload`, the application can
acquire resources and perform initialization that is common to all future
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
bilayer sandbox, Chrome on Android also uses two technologies to secure
low-privilege processes.
### SELinux
The Android OS applies an [SELinux policy](https://source.android.com/docs/security/features/selinux)
to every process on the system. A full explanation of SELinux is outside the
scope of this document, but at a high-level it is a [mandatory access control
mechanism](https://csrc.nist.gov/glossary/term/mandatory_access_control) that
enforces a capability sandbox. Every object represented in the kernel has an
associated security label, and the system policy specifies what subjects can do
to objects. For Chrome, SELinux acts as the layer-one sandbox.
System services have per-service policies, but applications and their components
are usually assigned to one of three domains:
- [**untrusted_app**](https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/untrusted_app.te;drc=4aad91d920ad42a7374e7bbd4cb9a50de4e85efb)
is the default policy for most user-installed apps
- [**priv_app**](https://source.android.com/docs/core/permissions/perms-allowlist)
is the used for system applications that require powerful capabilities (e.g.
backup/restore)
- [**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.
Chrome's sandboxed helper processes (e.g. renderers, utility processes,
sandboxed Mojo services) run under **isolated_app**. When the Android OS starts
an **isolated_app** process, it also assigns the process to an ephemeral UID, to
separate it from other POSIX security principals. The **isolated_app** SELinux
domain prevents the process from accessing virtually all system services,
devices, the network, and most of the filesystem.
Chrome's un-sandboxed helper processes (e.g. GPU, un-sandboxed Mojo services)
run under **untrusted_app** and the same UID as the browser process. This means
there is no privilege separation between un-sandboxed helper processes and the
browser process.
A unique difference with Android compared to desktop platforms is that the
operating system controls the sandbox policy (as a result of it being a
mandatory access control mechanism). This means that Chrome cannot modify the
policy, nor create gradations of sandboxes as can be done on other operating
systems. A process on Android runs at either the same security principal as the
browser, or in the tightly sandboxed **isolated_app** domain.
### Seccomp-BPF
[Seccomp-BPF](https://www.kernel.org/doc/html/v4.19/userspace-api/seccomp_filter.html)
is the second layer of the sandbox, which performs kernel attack surface
reduction. By removing system calls, or filtering specific arguments of system
calls, some of the complexity provided by the Linux kernel can be walled off
from a low-privilege process.
The [policy applied to
Android](https://source.chromium.org/chromium/chromium/src/+/main:sandbox/linux/seccomp-bpf-helpers/baseline_policy_android.h;l=25;drc=f18d3489a59d34c92a8d96ef6a7e7279198a8ec6)
is based on the desktop Linux policy. But, additional system calls are permitted
compared to desktop Linux. The major differences are to allow the JVM to
function properly and to account for differences in Bionic (Android Libc) vs
Glibc (standard Linux).
Additionally, the Android OS applies a [seccomp-bpf filter to all
applications](https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/seccomp/seccomp_policy.cpp;l=305;drc=704772bda034448165d071f68b6aeca716f4220e).
This policy is necessarily looser (since it applies to all applications) than
the one applied by Chrome. But, seccomp-bpf policies stack and can only be made
more restrictive.