Android Linker Namespace: Security Flaws
Linker namespaces are the feature of Android's dynamic linker "bionic". I'm going to show the linker namespace engine, security issues, security flaws, in detail from a security perspective. (Updated in 2021).
Part 2 ("how to"): Bypassing the Android Linker Namespace.
Namespace Formula
namespace
= groups
and rules
used by linker
while linking
- groups: symbols are being resolved according to their belonging to a named group.
- rules: search paths, permitted paths, relations between groups of the namespace.
- linker:
/system/bin/linker[64]
. - linking:
dlopen()
,android_dlopen_ext()
,DT_NEEDED
.
Overview
The linker namespace mechanism is introduced as a part of Project Treble starting from Android O (8.0). The linker namespace is implemented as a part of the Android linker (bionic linker) and as a set of patches in Android system framework.
Despite the linker namespace is described in detail in the official documentation, the explanation seems to be quite confusing.
Strictly speaking, the most confusing part is that the linker namespace is neither security countermeasure nor the protection mechanism for system API. It's all about to resolve symbols safely.
Two main goals are declared at the top of the main article:
- to prevent symbol conflicts,
- to detect hidden runtime dependencies.
You could see about the Linker namespace isolation: notice, that isolation is not equal to protection. Yet prevention and detection.
Namespace Function | |
---|---|
Prevent symbol conflicts | + |
Prevent unpredictable resolutions | + |
Restrict System.loadLibrary in Java | + |
Protect system native API | - |
Securely control JNI libraries | - |
Securely isolate groups of libraries | - |
I'm going to show the linker namespace mechanism in details from security perspective.
Namespace Interface
- /system/bin/ld.config.txt: the linker loads a "namespace config" initializing the namespace according to it. The linker loads a "namespace config" (ld.config.txt) when the process starts. There are a couple of rules for /system and /vendor paths:
dir.system = /system/bin/ dir.vendor = /vendor/bin/ [system] additional.namespaces = sphal,vndk,rs namespace.default.permitted.paths = /system/${LIB}:/vendor/${LIB} namespace.sphal.permitted.paths = /vendor/${LIB} namespace.rs.permitted.paths = /vendor/${LIB}:/data namespace.vndk.permitted.paths = /vendor/${LIB}/hw:/vendor/${LIB}/egl [vendor] namespace.default.search.paths = /vendor/${LIB}:/vendor/${LIB}/vndk-sp:/system/${LIB}/vndk-sp:/system/${LIB}
- The linker assigns a default namespace to each library while resolving dependencies according to
DT_NEEDED
entry. libdl.so
in Android 8, andlibc.so
in Android 11, exports a few symbols (implementation resides in the linker):
3.1.android_dlopen_ext()
: assigns a custom namespace to newly loaded shared library; file must comply to the rules of the new namespace to be loaded.
3.2.dlopen()
: loads dynamic library according to rules of caller's namespace; assigns the caller's namespace.
3.3.android_create_namespace()
(removed in Android 11).
3.4.android_init_anonymous_namespace()
(removed in Android 11).
3.5.android_get_exported_namespace()
(private function inlibc.so
in Android 11).
Interface / Event | Effect | Notes |
---|---|---|
load ld.config.txt | create ns | |
resolve DT_NEEDED | assign ns | |
android_create_namespace | create ns | |
android_get_exported_namespace | get ns | "sphal" is only case: "namespace.sphal.visible = true" |
android_init_anonymous_namespace | create ns | for JIT executable code |
android_dlopen_ext | assign ns | from any place |
dlopen | check ns | load library according to the namespace rules |
Namespace Scenarios
Native Application
📌 Namespaces: default, sphal, rs, vndk.
- Executable starts, e.g. /system/bin/ls.
- The linker is loaded into virtual memory of newly created task.
- The linker loads /system/bin/ld.config.txt.
- The linker initializes a list of namespaces: default, sphal, rs, vndk.
libhardware.so
callslibvndksupport.so
loading libraries into the sphal namespace.
Java Application
📌 Namespaces: default, sphal, rs, vndk, classloader-namespace, anonymous.
- Native process zygote (/system/bin/app_process) forks.
- All native namespaces are inherited.
libnativeloader.so
creates a classloader-namespace (request is starting fromZygoteInit.java
). The purpose is to restrictSystem.loadLibrary()
to load library (JNI) from path "/data/<app>/lib/".- Anonymous namespace is created by the means of
android_init_anonymous_namespace
for JIT compiled code.
Security
1. Missing Threat Model
I would draw a trust boundary only between processes, not inside of a single process.
In the current design, consumers of the linker namespace (shared libraries, main code, all executable code) and the namespace controller (linker) reside in a single virtual memory of a process, which makes possible to trick with the namespaces and with execution flow.
2. Lack of Access Control
Current design of the Android Linker Namespace API has a lack of control, let's see:
- Only
dlopen()
respects the namespace configuration. android_create_namespace()
allows to create any namespace (in Android 8).android_dlopen_ext()
can specify any namespace to a desired library.
libnativeloader.so creates "classpath-namespace" from zygote process, libvndksupport.so loads libraries to a specific namespace ("sphal"), anonymous namespace is being created on-the-fly in Java application. Perhaps developers faced with a high complexity to properly isolate dependencies in preconfigured namespace, e.g. to restrict a process to just a /system path, or /vendor path.
3. A Caller Can Be Forged
A caller is an executable in a memory of a process identified by a return address (/bionic/libdl/libdl.c:101):
void* dlopen(const char* filename, int flag) {
const void* caller_addr = __builtin_return_address(0);
...
}
- Option 1: Forging a return address from inside of a process.
- Option 2: Forging a return address from a tracer attached to a process.
Conclusion
Speaking about security functions, the linker namespace carries out the following ones:
Security Function | Status |
---|---|
Detection | + |
Prevention | + |
Protection | - |
Undesirable linking is prevented. Inappropriate usage of system and vendor libraries is prevented. Runtime errors during symbol resolution can be detected as well as unintentional dependencies on the system framework (libraries in /system/lib).
That being said, the linker namespace does not provide the reasonable level of protection against the deliberate exposure to namespace mechanism.