第三方app加载系统/system/lib下的库--is not accessible for the namespace

荆学民
2023-12-01

第三方app想load /system/lib 下的so库。报错:is not accessible for the namespace

缘由:android限制了app加载so库,从 7.1.2 源码来看,在加载so库的时候会检查 加载者的 权限,对于部分常用的库比如:libssl.so libsqlite.so libutils.so libstagefright.so libmedia.so  libbinder.so libandroid_runtime.so 等,不限制。

问题: 1.0 在androidstudio 上写了个简单的app,直接点run 安装到连接的设备上。(作为一个第三方app,在 android_studio上 minSdkVersion 14; tartgetSdkVersion 28;) 

2.0 在源码上写jni库, android.mk编译出 .so  push到设备system/lib64/   (具体环境 android 7.1.2 对应api 25)

#Android.mk for make test .so, 
ROOT_DIR := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_PATH := $(ROOT_DIR)


LOCAL_MODULE := libwangtest
LOCAL_SHARED_LIBRARIES += libc

#option 表示该模块在所有模式下 user release ,都编译
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := test_jni.c

LOCAL_LDLIBS += -llog 

include $(BUILD_SHARED_LIBRARY)
#define MY_LOG_TAG    "from-jni" // 这个是自定义的LOG的标识
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,MY_LOG_TAG,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,MY_LOG_TAG,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,MY_LOG_TAG,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,MY_LOG_TAG,__VA_ARGS__) // 定义LOGF类型


#define TRACK(...)  __android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,"[%d %s]",__LINE__,__FUNCTION__);__android_log_print(ANDROID_LOG_INFO,MY_LOG_TAG,__VA_ARGS__)
JNIEXPORT jint JNICALL Java_com_example_wangxiancan_as_1test_TestA_NativeHello
  (JNIEnv *env, jobject cls)
 {
	  TRACK("Natieve Hello!");
	  return 0;
 }

3.0 app上加载了对应库,

public class TestA {
    static {
        Log.i("TestA","start loadLibrary!");
        try {
            System.loadLibrary("wangtest");
        } catch (UnsatisfiedLinkError ule) {
            Log.e("TestA", "Can't load wangtest library: " + ule);
            System.exit(1);
        } catch (SecurityException se) {
            Log.e("TestA", "Encountered a security issue when loading wangtest lib    rary: " + se);
            System.exit(1);
        }
        Log.i("TestA","loadLibrary ok !");
    }
....略
}

4.0 把编译的libwangtest.so push 到设备上/system/lib64/  运行app,报错

2019-09-11 09:42:39.353 21260-21260/com.example.wangxiancan.as_test I/TestA: start loadLibrary!

2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/linker: library "/system/lib64/libwangtest.so" ("/system/lib64/libwangtest.so") needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="", permitted_paths="/data:/mnt/expand:/data/data/com.example.wangxiancan.as_test"]

2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/libwangtest.so" needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"

解决办法:

  1. 把需要的库拷贝到 app的打包目录一起打包到app。具体哪一个目录,找个demo看看,还需注意 abi,不同的cpu架构都有不同的目录名称,arm64位,arm32位,还是x86, 或者mips. 如果确定只一种设备用,那么只放一个就行。
  2. app安装好后手动把需要的库push到app私有目录的lib下面。(方法1 ,在安装app时会自动解压,这里算是安装完之后手动拷贝)
  3. (20220704 追加)  修改设备上公共库的清单文件。  /system/etc/public.libraries.txt
    添加上自己的库名,然后将编译得到的库都push到 /system/lib64/xx 和/system/lib 重启
    。 (实际操作中发现修改了清单文件,只push上去了64位库,导致系统起不来,同步时把32bit和64bit的库都push上去 OK)

具体的操作,还是结合实际的错误提示来分析,贴个自己的调试过程:

现在在第三方app加载刚编译的so库,该so库已经push到 /system/lib64/目录下。
运行app:
错误 1
2019-09-11 09:42:39.353 21260-21260/com.example.wangxiancan.as_test I/TestA: start loadLibrary!
2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/linker: library "/system/lib64/libwangtest.so" ("/system/lib64/libwangtest.so") needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="", permitted_paths="/data:/mnt/expand:/data/data/com.example.wangxiancan.as_test"]
2019-09-11 09:42:39.355 21260-21260/com.example.wangxiancan.as_test E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dlopen failed: library "/system/lib64/libwangtest.so" needed or dlopened by "/system/lib64/libnativeloader.so" is not accessible for the namespace "classloader-namespace"
	
"/system/lib64/libnativeloader.so" is not accessible for the namespace: 无法访问。。。据说是android7 限制了普通app加载系统私有库(一些共用的库还是可以)
解决方法:既然无法加载system目录下的库,就把这个库拷贝到app目录下吧,(拷贝后为实验的准确性还是把system/lib目录下的那个库删掉确保只有一个吧。不然可能还是去system/lib下找到了这个库,结果加载不成功,不会再去app目录找了,关于这个查找的顺序,应该是先app私有目录再system目录的)既然有错误提示,不妨利用一下,直接把/system/lib64 下刚刚报错的库删除,这样在运行就会报错提示找不到这个库。
错误2
019-09-11 10:29:14.621 6205-6205/? I/TestA: start loadLibrary!
2019-09-11 10:29:14.622 6205-6205/? E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.example.wangxiancan.as_test-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.wangxiancan.as_test-1/lib/arm64, /system/lib64, /vendor/lib64]]] couldn't find "libwangtest.so"
提示查找的路径是 app私有目录下的 lib/arm64/
哪我们就把测试用的so拷贝到这个目录。(没有arm64目录就自己mkdir 一个)

再运行:
错误3
019-09-11 10:30:41.310 7529-7529/com.example.wangxiancan.as_test I/TestA: start loadLibrary!
2019-09-11 10:30:41.312 7529-7529/com.example.wangxiancan.as_test E/TestA: Can't load wangtest library: java.lang.UnsatisfiedLinkError: dlopen failed: library "libc++.so" not found
还需要libc++? 可以在设备上看到 /system/lib64/下面是存在libc++.so的,既然又报错找不到,也一并把这个/system/lib64/libc++.so 拷贝一份到 app私有目录 */lib/arm64/

再运行:成功加载并调用
2019-09-11 10:32:29.630 8109-8109/? I/TestA: start loadLibrary!
2019-09-11 10:32:29.634 8109-8109/? I/TestA: loadLibrary ok !
2019-09-11 10:32:29.635 8109-8109/? I/from-jni: [19 Java_com_example_wangxiancan_as_1test_TestA_NativeHello]
2019-09-11 10:32:29.635 8109-8109/? I/from-jni: Natieve Hello!

继续分析为何限制?如何限制?看android源码。源码工程中查找 is not accessible for the namespace,在/bionic/linker.cpp 中有加载库的函数。加载的时候先判断对应库是否存在,然后检查“限制条件”

if (!ns->is_accessible(realpath)) {//判断是否可以访问,文件是否存在
    // TODO(dimitry): workaround for http://b/26394120 - the grey-list
    const soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr;
    if (is_greylisted(name, needed_by)) {//这里有一个灰色表,表里面列举了部分库文件,如果
//要加载的库是在这个表里面或者加载库的是"系统"用户,就通过检查,否则拒绝加载,被限制。
      // print warning only if needed by non-system library
      if (needed_by == nullptr || !is_system_library(needed_by->get_realpath())) {
        const soinfo* needed_or_dlopened_by = task->get_needed_by();
        const char* sopath = needed_or_dlopened_by == nullptr ? "(unknown)" :
                                                      needed_or_dlopened_by->get_realpath();
        DL_WARN("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the namespace \"%s\""
                " - the access is temporarily granted as a workaround for http://b/26394120, note that the access"
                " will be removed in future releases of Android.",
                name, realpath.c_str(), sopath, ns->get_name());
        add_dlwarning(sopath, "unauthorized access to",  name);
      }
    } else {
      // do not load libraries if they are not accessible for the specified namespace.
      const char* needed_or_dlopened_by = task->get_needed_by() == nullptr ?
                                          "(unknown)" :
                                          task->get_needed_by()->get_realpath();

      DL_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"",
             name, needed_or_dlopened_by, ns->get_name());

      PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the"
            " namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\","
            " permitted_paths=\"%s\"]",
            name, realpath.c_str(),
            needed_or_dlopened_by,
            ns->get_name(),
            android::base::Join(ns->get_ld_library_paths(), ':').c_str(),
            android::base::Join(ns->get_default_library_paths(), ':').c_str(),
            android::base::Join(ns->get_permitted_paths(), ':').c_str());
      return false;
    }
  }

所以这个函数 is_greylisted() 是关键的条件,其中就

列举出了可以被第三方app加载的 /system/ *.so  

// TODO(dimitry): The grey-list is a workaround for http://b/26394120 ---
// gradually remove libraries from this list until it is gone.
static bool is_greylisted(const char* name, const soinfo* needed_by) {
  static const char* const kLibraryGreyList[] = {
    "libandroid_runtime.so",
    "libbinder.so",
    "libcrypto.so",
    "libcutils.so",
    "libexpat.so",
    "libgui.so",
    "libmedia.so",
    "libnativehelper.so",
    "libskia.so",
    "libssl.so",
    "libstagefright.so",
    "libsqlite.so",
    "libui.so",
    "libutils.so",
    "libvorbisidec.so",
    nullptr
  };

  // limit greylisting to apps targeting sdk version 23 and below
  if (get_application_target_sdk_version() > 23) {
    return false;
  }

  // if the library needed by a system library - implicitly assume it
  // is greylisted

  if (needed_by != nullptr && is_system_library(needed_by->get_realpath())) {
    return true;
  }

  // if this is an absolute path - make sure it points to /system/lib(64)
  if (name[0] == '/' && dirname(name) == kSystemLibDir) {
    // and reduce the path to basename
    name = basename(name);
  }

  for (size_t i = 0; kLibraryGreyList[i] != nullptr; ++i) {
    if (strcmp(name, kLibraryGreyList[i]) == 0) {
      return true;
    }
  }

  return false;
}

 类似资料: