Quick Guide

JNI -> android_create_namespace -> android_dlopen_ext

Full solution: https://github.com/fadeevab/android-custom-jni-namespace/

Goal

Recently I investigated Android Linker Namespace: Security Flaws, and here I would like to demonstrate a lack of access control. I want to underline that it is not about a vulnerability, it is about a nuance of the linker namespace design.

Starting from Android 8.0 (Project Treble) while trying to access the private system API from a Java application, you may see the following error:

java.lang.UnsatisfiedLinkError: dlopen failed: library "libwifi-system.so" ("/system/lib64/libwifi-system.so") needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"

It is a consequence of doing things like:

// From Java code
System.loadLibrary("wifi-system");

or

// From JNI
dlopen("libwifi-system.so", RTLD_NOW);

The Android's linker (bionic) disallows to load the most of the shared libraries from /system when the request is carried out from the executable code belonging to "classloader-namespace".

Namespace Inheritance
Zygote process assigns the "classloader-namespace" to a Java application with the help of the libnativeloader.so. System.loadLibrary loads a shared library into the same "classloader-namespace", preserving the namespace of parent process.

An ordinary application from the market should not use the private system libraries (not included into NDK) in order to be portable among many devices. And the linker namespace mechanism is a nice thing to control undesirable dependencies. But if you are really sure about to use a private system API, see the method below.

Steps

The current example shows how to load libwifi-system.so: any uncommon system library to which access is most likely denied. The nuance of the linker namespace API is going to be used.

Full example is here: https://github.com/fadeevab/android-custom-jni-namespace/.

  1. The first step is to get access to native code from Java application. Create a Java Native Interface (JNI) and declare a native method (e.g. linkSystemLib):

    public final class RoadToSystem {
        public native int linkSystemLib();
    
        static {
            System.loadLibrary("roadtosystem");
        }
    }
    
  2. Build a shared library, implementing linkSystemLib method:

    #include "dlext_namespaces.h"
    
    JNIEXPORT jint JNICALL Java_com_example_android_jniapi_RoadToSystem_linkSystemLib(
            JNIEnv *env, jobject obj) {
        ...
    }
    

    "dlext_namespaces.h" is copied from Android 8.0 sources, the header provides prototypes of the linker namespace functions, which are being the exported symbols of libdl.so linked into the process.

  3. JNI shared library resides in "classloader-namespace" after loading by System.loadLibrary call. dlopen() returns NULL for the most of the system libraries. Need to create a namespace with "no rules" (permitted paths will contain /system/lib[64]) and load a system shared library into the one:

    struct android_namespace_t *ns =
        android_create_namespace(
          "trustme",
          "/system/lib64/",
          "/system/lib64/",
          ANDROID_NAMESPACE_TYPE_SHARED |
          ANDROID_NAMESPACE_TYPE_ISOLATED,
          "/system/:/data/:/vendor/",
          NULL);
    
    const android_dlextinfo dlextinfo = {
      .flags = ANDROID_DLEXT_USE_NAMESPACE,
      .library_namespace = ns,
    };
    
    // Access granted
    void *so = android_dlopen_ext("libwifi-system.so", RTLD_LOCAL | RTLD_NOW, &dlextinfo);
    

    android_dlopen_call returns a valid non-zero pointer in a case of success, while dlopen returns NULL trying to load a system library from JNI.