Namespace Formula

namespace = groups and rules used by linker while linking

  1. groups: symbols are being resolved according to their belonging to a named group.
  2. rules: search paths, permitted paths, relations between groups of the namespace.
  3. linker: /system/bin/linker[64].
  4. 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 about the linker namespace in Android is that it is neither security countermeasure nor the protection mechanism for system API. It's all about to resolve symbols safely.

real-world-of-linking

Two main goals are declared at the top of the main article:

  1. to prevent symbol conflicts,
  2. to detect hidden runtime dependencies.

You could see about the Linker namespace isolation: pay attention that in those case 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 a security point of view.

Namespace Interface

bionic-namespace-api

  1. /system/bin/ld.config.txt: the linker loads a "namespace config" initializing the namespace according to one. 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}
  1. The linker assigns a default namespace to each library while resolving dependencies according to DT_NEEDED entry.
  2. libdl.so exports a few symbols related to the linker namespace (implementation resides in the linker):
    3.1. android_create_namespace().
    3.2. android_init_anonymous_namespace().
    3.3. android_get_exported_namespace().
    3.4. 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.5. dlopen(): loads dynamic library according to rules of caller's namespace; assigns the caller's namespace.
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.

  1. Executable starts, e.g. /system/bin/ls.
  2. The linker is loaded into virtual memory of newly created task.
  3. The linker loads /system/bin/ld.config.txt.
  4. The linker initializes a list of namespaces: default, sphal, rs, vndk.
  5. libhardware.so calls libvndksupport.so loading libraries into the sphal namespace.

namespace-scenario-native

Java Application

📌 Namespaces: default, sphal, rs, vndk, classloader-namespace, anonymous.

  1. Native process zygote (/system/bin/app_process) forks.
  2. All native namespaces are inherited.
  3. libnativeloader.so creates a classloader-namespace (request is starting from ZygoteInit.java). The purpose is to restrict System.loadLibrary() to load library (JNI) from path "/data/<app>/lib/".
  4. 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 a single process.

namespace-threat-model

In the current design the consumers of the linker namespace (shared libraries, main code, all executable code) and the namespace controller (linker) reside in a single virtual memory space of process, that makes possible to trick with the namespace through libdl.so, and to trick with execution flow.

But it's not just about a shared virtual memory space: there is unclear concept of access control in the namespace API.

2. Lack of Access Control

Current design of the Android Linker Namespace API has a lack of control, let's see:

  1. Only dlopen() respects the namespace configuration.
  2. android_create_namespace() allows to create any namespace.
  3. 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. Caller Can Be Forged

Caller is an executable in a memory of process detected by a return address (/bionic/libdl/libdl.c:101):

void* dlopen(const char* filename, int flag) {
  const void* caller_addr = __builtin_return_address(0);
  ...
}

ldload-namespace

  1. Option 1: Forge a return address from inside of process.
  2. Option 2: Forge a return address from a tracer attached to a process.

Conclusion

In speaking of security functions, the linker namespace could be considered to carry out the following ones:

Security Function Status
Detection +
Prevention +
Protection -

Undesirable linking can be prevented. Inappropriate usage of system and vendor libraries can be prevented. Runtime errors during symbol resolution can be detected as well as unintentional dependencies on the system framework (libraries in /system/lib).

With being that said, the linker namespace does not provide the reasonable level of protection against the deliberate exposure to namespace mechanism. It follows from analysis of the namespace interface, implementation, and design.