Bionic: Android's C Library
Bionic is the standard C library (libc), math library (libm), and dynamic linker (linker) for the Android operating system. Unlike GNU C Library (glibc) used in most desktop Linux distributions, Bionic is designed specifically for the constraints and security requirements of mobile devices.
Bionic libc API Surface
Bionic provides the fundamental C POSIX APIs required by native code, including file I/O (open, read, write), string manipulation (strcpy, strlen), and basic system calls.
Key differences from glibc include:
- Size: Bionic is significantly smaller, omitting many rarely used POSIX features and legacy compatibility layers.
- Speed: It is optimized for mobile CPU architectures (ARM, ARM64).
- Security: Bionic incorporates numerous security mitigations, such as fortified string functions (
_FORTIFY_SOURCE) and strict dynamic linking rules. - Licensing: Bionic is BSD-licensed, preventing the GPL "viral" effect from impacting proprietary Android apps.
Threading: pthread Implementation in Bionic
Bionic provides the POSIX threads (pthread) implementation for Android. Every thread in an Android process (including Dalvik/ART managed threads) is fundamentally a Linux thread backed by Bionic's pthread_create.
#include <pthread.h>
#include <android/log.h>
void* thread_function(void* arg) {
__android_log_print(ANDROID_LOG_INFO, "Bionic", "Running in a new thread!");
return NULL;
}
void start_thread() {
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
pthread_join(thread, NULL);
}
Bionic's pthread implementation interacts closely with the kernel's clone syscall and manages thread-local storage (TLS), which is essential for storing per-thread data like the JNI JNIEnv pointer.
Bionic's Malloc Implementation
Memory allocation (malloc, free, calloc) is a critical function of the C library. Historically, Bionic used several different allocators:
- dlmalloc: An older, general-purpose allocator.
- jemalloc: Introduced in Android 5.0 (Lollipop) for improved multi-threading performance and reduced fragmentation.
These allocators manage the native heap, servicing requests from C/C++ code. Unlike the Java heap, memory allocated here must be explicitly freed, or the process will leak memory and eventually be killed by the kernel's Low Memory Killer (LMK).
Scudo Allocator: Hardened Malloc
Starting with Android 11, Bionic switched to Scudo as the default heap allocator. Scudo is a dynamically hardened allocator designed by the LLVM project.
Scudo prioritizes security over absolute maximum performance. It is designed to detect and mitigate common memory safety vulnerabilities:
- Heap Buffer Overflows: Detects writes past the end of an allocated chunk.
- Use-After-Free (UAF): Prevents reuse of recently freed memory blocks, reducing the window for UAF exploits.
- Double Free: Detects attempts to free the same pointer twice.
When Scudo detects a memory violation, it deliberately crashes the process, generating a tombstone. This fail-fast behavior transforms potential security exploits into simple denial-of-service crashes, significantly hardening the Android OS against native layer attacks.