KernelGP: Racing Against the Android Kernel

Chariton Karamitas (Vulnerability Researcher · Census Labs)

OffensiveCon 2025 · Day 2 · Main · Briefings

Overview

Chariton Karamitas of Census Labs presents four novel techniques for forcing the Android kernel to delay execution, enabling attackers to win race conditions without relying on userfaultfd — which is either disabled or unreliable on Android. Three techniques exploit Android's scoped storage and proxy file descriptor mechanisms (both built on FUSE) from the untrusted app SELinux domain; a fourth exploits the Incremental File System from the system app domain. Because these techniques are grounded in core Android architectural design decisions, they are difficult to mitigate without breaking platform functionality. ---

Watch on YouTube

Visual summary for KernelGP: Racing Against the Android Kernel by Chariton Karamitas
Visual summary for KernelGP: Racing Against the Android Kernel by Chariton Karamitas

Key moments

  1. 2:06 userfaultfd unreliable on Android: disabled by vendors or restricted by UCTCLNOV on hardened builds
  2. 4:10 FUSE inaccessible directly on Android, but proxy file descriptors and scoped storage use FUSE internally
  3. 5:05 Techniques exploit architectural design choices, making them very difficult to mitigate without regressions
  4. 9:28 Proxy file descriptor translates any syscall into an async Java callback, stalling the kernel thread
  5. 13:20 openProxyFileDescriptor via StorageManager creates controllable blocking fd from untrusted app context
  6. 18:21 Scoped storage MediaProvider transcoding (HEVC to H.264) introduces hundreds of milliseconds of blocking I/O delay
  7. 23:55 EXIF redaction read path creates controllable I/O latency exploitable as race primitive from untrusted app
  8. 39:59 IncFS demand-load dataloader is functional userfaultfd replacement for systemapp SELinux domain

KernelGP: Racing Against the Android Kernel

Speaker: Chariton Karamitas / "Hugo" (Census Labs)

Conference: OffensiveCon 2025 — May 16–17, 2025, Berlin

YouTube: https://www.youtube.com/watch?v=DJBGu2fSSZg

Reading time: ~10 minutes

TL;DR

Chariton Karamitas of Census Labs presents four novel techniques for forcing the Android kernel to delay execution, enabling attackers to win race conditions without relying on userfaultfd — which is either disabled or unreliable on Android. Three techniques exploit Android's scoped storage and proxy file descriptor mechanisms (both built on FUSE) from the untrusted app SELinux domain; a fourth exploits the Incremental File System from the system app domain. Because these techniques are grounded in core Android architectural design decisions, they are difficult to mitigate without breaking platform functionality.

Introduction

Race condition exploitation in the Linux kernel has long depended on a small toolkit of execution-stalling primitives, with userfaultfd being the most widely used: it allows an unprivileged process to register a handler for page faults in a specific memory region, causing the kernel to block indefinitely while the handler executes. Researchers including Jann Horn and Vitaliy Nikolenko have used userfaultfd to precisely control the timing window in use-after-free, time-of-check/time-of-use (TOCTOU), and heap spray scenarios.

On Android, userfaultfd is a moving target. Vendors historically disabled it in kernel configurations because no framework component needed it. Android 13 re-enabled it to support the ART runtime's new garbage collector — but Karamitas's central observation is that Android internals change rapidly, and there is no guarantee userfaultfd will remain available across future versions. A more fundamental concern is UCTCL_NOV, the kernel flag that prevents unprivileged processes from using userfaultfd even when the syscall is compiled in; this restriction applies on most hardened Android configurations regardless of ART's needs.

The talk addresses this gap directly: rather than relying on userfaultfd, Karamitas demonstrates that Android's own framework mechanisms — mechanisms present on every shipping Android device — can be weaponized to produce equivalent kernel execution delays. The techniques are technique-focused rather than vulnerability-specific, and the talk explicitly positions them as primitives that researchers can apply to their own existing race-condition bugs.

▶ Watch: userfaultfd availability problem on Android (2:01)

The userfaultfd Problem on Android and the FUSE Misconception

userfaultfd stalls the kernel by causing a page fault on a user-controlled memory region, transferring execution to a userland handler thread. The equivalent mechanism on desktop Linux is a custom FUSE daemon: by mounting a FUSE filesystem and mapping a file from it, an attacker can control exactly when the kernel's read from that mapping completes. On desktop Linux, /dev/fuse has permissive ownership and fusermount is a setuid-root binary, making FUSE accessible to unprivileged processes.

On Android, a common assumption holds that FUSE is equally inaccessible to untrusted applications: /dev/fuse is owned by root, there are no setuid mount binaries, and SELinux policy explicitly denies access to /dev/fuse for all but a small set of privileged daemons. Karamitas identifies this as a misconception. FUSE is actively used by the Android framework for two purposes: the scoped storage subsystem (part of Android's external storage model) and the proxy file descriptor mechanism. Both go through vold, a privileged daemon that can mount FUSE filesystems on behalf of applications via a controlled API. The untrusted application never touches /dev/fuse directly — but it can indirectly cause FUSE filesystem operations to block in the kernel by interacting with these framework APIs.

▶ Watch: FUSE on Android and the scoped storage mechanism (4:00)

Technique 1: Proxy File Descriptor Blocking

Proxy file descriptors are an Android framework mechanism for revocable file access. When a privileged component (system_server) needs to share a file with an application while retaining the ability to revoke access at any time, it wraps the underlying file descriptor in a proxy: a FUSE-backed file descriptor whose I/O callbacks are dispatched as asynchronous Java callbacks to the file's owner. The Java class RevocableFileDescriptor implements this pattern with mOuter (the FUSE-backed proxy) and mInner (the underlying sensitive file descriptor), plus a boolean revoked flag checked in the onRead callback before each read.

From an exploitation perspective, the key property is that any system call operating on a proxy file descriptor will block the calling kernel thread until the Java callback returns. An attacker implements openProxyFileDescriptor with an onRead callback that sleeps for an arbitrary duration — injecting a controlled delay into any kernel code path that reads from the mapped region.

The infrastructure for this is non-trivial. The application calls StorageManager.openProxyFileDescriptor(), which causes system_server (running as UID 1000) to contact vold, which mounts a FUSE filesystem at /mnt/user/<uid>/ using the kernel FUSE device. Because system_server does not trust the application with the raw FUSE file descriptor, it creates a socket pair: one end goes to the application's process, the other acts as a translation bridge between the socket and the /dev/fuse fd. Vold then creates a file under the FUSE mount point and returns the file descriptor to the application. When the kernel attempts to read a page from a mapping backed by this file descriptor, a FUSE read request is generated, dispatched through the socket bridge to system_server, forwarded to the application's onRead callback, and execution stalls until the callback returns.

As an added exploitation convenience, proxy file descriptors are fully transferable: an attacker can pass the blocking fd to a victim process via Binder, causing the kernel thread servicing that victim's read to stall — enabling inter-process timing control.

▶ Watch: Proxy file descriptor mechanism and FUSE bridge architecture (10:00)

Techniques 2 and 3: Scoped Storage Redaction and Transcoding Delays

Android's scoped storage model restricts application access to shared storage, routing file reads through MediaProvider — a privileged content provider that applies permission checks, metadata redaction, and on-the-fly media transcoding. These operations introduce variable latency between a read system call and the actual data transfer: redaction of sensitive EXIF fields requires per-read metadata inspection, and transcoding video from HEVC to H.264 (for compatibility with older API targets) can require hundreds of milliseconds per request.

Both of these behaviors are exercisable from the untrusted app context through standard Android content provider APIs (ContentResolver.openFileDescriptor() with a URI pointing to a scoped storage path). By constructing content provider requests that trigger maximum redaction load or on-the-fly transcoding, an attacker can introduce substantial and controllable delays into kernel read paths, without any direct access to FUSE infrastructure.

These techniques fall into the blocking I/O category of race-condition primitives rather than the userfaultfd/FUSE page-fault category: the kernel thread blocks on an I/O completion rather than a page fault handler, but the effect on race window width is equivalent. The practical advantage is that they require only standard Android permissions — no privileged APIs and no special SELinux domains beyond the default untrusted app context.

Technique 4: Incremental File System (IncFS) Delays

The Incremental File System (IncFS) is a kernel filesystem introduced in Android 12 to support streaming app installation: an app can be launched from the Play Store before its entire APK has been downloaded, with blocks delivered on demand as the app requests them. IncFS accomplishes this via a kernel driver that intercepts page faults on unmapped IncFS file ranges and issues a request to a userland server (the dataloader process) to fill the missing block.

This page-fault-driven demand loading is functionally equivalent to the userfaultfd primitive. An attacker in the system app or system_server SELinux domain can create an IncFS mount, register as the dataloader, and then artificially delay responses to block-read requests. Any kernel code path that reads from an IncFS-backed file will stall until the dataloader provides the data.

The limitation relative to the proxy file descriptor technique is the SELinux domain requirement: IncFS mount operations require the system_app or system_server domain, making this technique unavailable to unprivileged third-party applications. However, for exploits targeting elevation from system app privileges — or for research contexts where the initial position is already at system app level — it provides a clean userfaultfd replacement with no dependency on kernel configuration.

▶ Watch: Incremental file system technique (16:00)

Mitigability and the Architectural Design Problem

Karamitas explicitly selected these techniques because they are grounded in architectural design choices that Android cannot easily walk back. Proxy file descriptors and scoped storage are core to Android's privacy and security model. Removing or significantly limiting the FUSE-backed behavior of proxy file descriptors would break revocable file sharing across the framework. Eliminating MediaProvider's redaction and transcoding pipeline would regress years of privacy work. IncFS is foundational to streaming installation.

Mitigating these primitives at the kernel level — for example, by preventing FUSE operations initiated through these APIs from stalling in page-fault context — would require changes to how FUSE callbacks interact with kernel threading, potentially introducing performance regressions or deadlock risks. This is in sharp contrast to userfaultfd, which was mitigated by adding UCTCL_NOV with relatively contained impact.

The talk positions these techniques not as exploits to be patched, but as inherent capabilities of the Android framework that security researchers and exploit developers can rely on as stable primitives for the foreseeable future.

Notable Quotes

"There's a common misconception that on Android, FUSE cannot be used for exploitation purposes. Now, we need a set of techniques for achieving the same principles like those that userfaultfd achieves — ideally, techniques that are hard to mitigate because they're based on architectural design choices."

— Chariton Karamitas, ▶ 4:00

"Proxy file descriptors are an abstraction over FUSE. The end result is that any file-related system call performed on the proxy file descriptor is actually translated to an asynchronous callback in Java code."

— Chariton Karamitas, ▶ 8:00

"As an added bonus, of course, a proxy file descriptor is a file descriptor, so we can send it to any other application in any other form or race condition. We can make any other application block — or even block ourselves."

— Chariton Karamitas, ▶ 16:00

Key Takeaways

  • userfaultfd is not a reliable primitive on Android: Vendor kernel configurations have historically disabled it, and even when present (as on Android 13+ for ART), its future availability is uncertain; exploit chains that depend on it are fragile across Android versions.
  • FUSE is accessible from untrusted app context via Android APIs: Contrary to common assumptions, proxy file descriptors and scoped storage both route through FUSE under the hood, and an attacker can control FUSE callback timing without ever touching /dev/fuse directly.
  • Proxy file descriptors provide cross-process timing control: Because proxy fds are transferable via Binder, an attacker can stall the kernel thread of a victim process — not just their own — creating cross-process timing windows without any kernel bug.
  • Scoped storage transcoding and redaction introduce exploitable I/O delays: MediaProvider's on-the-fly HEVC-to-H.264 transcoding and EXIF redaction create controllable latency in standard content provider read paths, usable as blocking I/O primitives from the untrusted app context.
  • IncFS provides a direct userfaultfd replacement at system app privilege: The Incremental File System's demand-load architecture is functionally equivalent to userfaultfd for exploits operating from the system app or system server SELinux domains, and is an integral Android feature that cannot be easily removed.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

KernelGP fills a real gap: Android race-condition exploitation has been missing a stable userfaultfd replacement since vendors started restricting it, and Karamitas delivers not one but four techniques grounded in Android's own framework architecture. The proxy file descriptor cross-process blocking primitive and the IncFS demand-load equivalence to userfaultfd are both immediately usable by anyone with an Android race-condition bug in hand. Architectural unmitigability is the selling point and it is well-argued.

Heather Calloway (CISO) — PASS

Four techniques replacing userfaultfd for race condition exploitation on Android, using Android's own FUSE filesystem, IncFS delay mechanisms, and scoped storage proxy file descriptors. Deep kernel race exploitation research.

→ Top-rated talks at OffensiveCon 2025

All talks from OffensiveCon 2025