android Linker:namespace隔离机制

为了解决碎片化、升级慢问题,android从8.0开始推出了Project Treble计划,诣在分离android framework和硬件驱动的耦合,system分区只存放原生android相关,vendor分区存放厂商相关定制。
主要通过HIDL、VNDK、SELinux来实现隔离,今天主要介绍VNDK相关的Linker namespace隔离机制。

VNDK

为了支持system分区可以升级到最新版本,而vendor分区保持不变,这意味着在不同时间编译的二进制文件必须能够相互配合使用,因此vendor分区程序只能访问system分区稳定的动态库。

共享库可分为以下三个子类别:

  • LL-NDK 库:是已知稳定的框架共享库。它们的开发者致力于保持其 API/ABI 稳定性。

    • LL-NDK 包含以下库:libEGL.so、libGLESv1_CM.so、libGLESv2.so、libGLESv3.so、libandroid_net.so、libc.so、libdl.so、liblog.so、libm.so、libnativewindow.so、libneuralnetworks.so、libsync.so、libvndksupport.so 和 libvulkan.so。
  • VNDK 库 (VNDK): 是指可以安全复制两次的框架共享库。框架模块和供应商模块可以与其各自的库副本相关联。

  • 框架专用库 (FWK-ONLY) :被视为框架内部实现细节,不稳定,不得由供应商模块访问。

Same-Process HAL (SP-HAL):是一组预先确定的 HAL,作为供应商共享库进行实现,并被加载到框架进程中。SP-HAL 由链接器命名空间进行隔离。SP-HAL 必须仅依赖于 LL-NDK 和 VNDK-SP。
VNDK-SP:是一部分预定义的符合条件的 VNDK 库。SP-HAL 和 VNDK-SP 均由 Google 定义。

Linker:namespace

Linker namespace解决了 Treble VNDK 设计中的两个难题:

  • 将 SP-HAL 共享库及其依赖项(包括 VNDK-SP 库)加载到框架进程中。这种情况下应该有一些防止
    出现符号冲突的机制。
  • dlopen() 和 android_dlopen_ext() 可能会引入一些在编译时不可见的运行时依赖项,这些依赖项使用
    静态分析很难检测到。

例如:
/system/lib/libcutils.so
/system/lib/vndk-sp/libcutils.so
这两个库可能有不同的符号。它们将加载到不同的链接器命名空间中,以便框架模块可以依赖于 /system/lib/libcutils.so,SP-HAL 共享库可以依赖于 /system/lib/vndk-sp/libcutils.so。
另一方面,比如libc.so 的依赖项libnetd_client.so被加载到libc.so 所在的命名空间中,其他命名空间将无法访问这些依赖项,可有效防止错综的依赖。

Namespace实现原理

简单来说就是用vector实现多个namespace功能的,没有namespace的linker只有一个LSPath和ALList,启用namespace后使用vector实现多个LSPath和ALList的相互隔离。

std::vector<android_namespace_t*> namespaces
std::vector<std::string> search_paths_;
soinfo_list_t soinfo_list_;

LSPath:搜索路径(Library Search Path)
ALList:已加载库列表(Alread Loaded Library list)


Namespace配置文件

ROM中配置文件位置:/system/etc/ld.config.txt、/system/etc/ld.config.vndk_lite.txt
配置说明:顾名思义,大部分配置即使不解释也能明白用途,详细解释参考谷歌官方介绍https://source.android.google.cn/devices/architecture/vndk/linker-namespace

dir.system = /system/bin
dir.vendor = /vendor/bin

[system]
additional.namespaces = sphal

namespace.default.isolated = true  //true为仅加载搜索目录下的库,flase可以自行指定path
namespace.default.search.paths = /system/${LIB}:/vendor/${LIB} //不搜索子目录
namespace.default.permitted.paths = /system/${LIB}:/vendor/${LIB} //搜索子目录

namespace.sphal.isolated = true
namespace.sphal.visible = true    //visible 为 true,该程序将能够获取链接器命名空间句柄,该句柄随后可传递到 android_dlopen_ext()。
namespace.sphal.search.paths = /vendor/${LIB}
namespace.sphal.links = default //如果无法加载到 sphal 命名空间,则动态链接器会尝试从 default 命名空间加载此共享库。
namespace.sphal.link.default.shared_libs = libc.so:libm.so //如果请求的库不是 libc.so、libm.so,则动态链接器会忽略从 sphal 到 default 命名空间的链接。

[vendor]
namespace.default.isolated = false
namespace.default.search.paths = /vendor/${LIB}:/system/${LIB}
namespace.default.permitted.paths = /system/${LIB}/vndk-28

Namespace:system|vendor/bin

bin程序启动时linker会根据配置文件初始化namespace,得到默认namespace:

static const char* const kLdConfigFilePath = "/system/etc/ld.config.txt";
static const char* const kLdConfigVndkLiteFilePath = "/system/etc/ld.config.vndk_lite.txt";
//初始化namespace,从/proc/self/exe确定路径
std::vector<android_namespace_t*> init_default_namespaces(const char* executable_path);

so加载时调用的System.loadLibrary()、dlopen()、android_dlopen_ext()最终都走到do_dlopen():

  • void* caller_addr = __builtin_return_address(0);//得到当前函数返回地址
  • soinfo* const caller = find_containing_library(caller_addr);//查找地址所在的动态库
  • android_namespace_t* ns = get_caller_namespace(caller);//ns为调用库所在命名空间

也就是说新加载so的namespace与加载者保持一致。关键代码如下:

void* dlopen(const char* filename, int flag) {
  const void* caller_addr = __builtin_return_address(0);//得到当前函数返回地址
  return __loader_dlopen(filename, flag, caller_addr);
}

void* do_dlopen(const char* name, int flags,
                const android_dlextinfo* extinfo,
                const void* caller_addr) {
  soinfo* const caller = find_containing_library(caller_addr);//查找地址所在的动态库
  android_namespace_t* ns = get_caller_namespace(caller);//ns为调用库所在命名空间

  soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);//根据ns来find
}
//根据ns来load
if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags, search_linked_namespaces)) {
  return true;
}

struct soinfo {
  soinfo(android_namespace_t* ns, const char* name, const struct stat* file_stat,
         off64_t file_offset, int rtld_flags);
  android_namespace_t* primary_namespace_;
  android_namespace_list_t secondary_namespaces_;
}

Namespace:APK

System.loadLibrary()--> nativeLoad() -->Runtime.c::Runtime_nativeLoad()-->JVM_NativeLoad()-->Openjdkjvm.cc::JVM_NativeLoad()-->java_vm_ext.cc::LoadNativeLibrary()-->native_loader.cpp::OpenNativeLibrary()-->android_dlopen_ext()--> libdl.cpp::android_dlopen_ext()

System.loadLibrary()走到OpenNativeLibrary() 将创建一个 classloader- namespace命名空间,并将它们链接到 default 命名空间。

static constexpr const char* kClassloaderNamespaceName = "classloader-namespace";
const char* namespace_name = kClassloaderNamespaceName;

android_namespace_t* ns = android_create_namespace(namespace_name,
                                                 nullptr,
                                                 library_path.c_str(),
                                                 namespace_type,
                                                 permitted_path.c_str(),
                                                 parent_ns.get_android_ns());

if (!android_link_namespaces(ns, nullptr, system_exposed_libraries.c_str())) { //nullptr将取default namespace
*error_msg = dlerror();
return false;
}

//system_exposed_libraries
static constexpr const char kPublicNativeLibrariesSystemConfigPathFromRoot[] =
    "/etc/public.libraries.txt";
static constexpr const char kPublicNativeLibrariesVendorConfig[] =
    "/vendor/etc/public.libraries.txt";

apk namespace 补充

system APP可以访问/system/lib,但是update后不能访问。
必须访问public.txt导出的库,否则update后加载so将crash。

frameworks/base/core/java/android/app/LoadedApk.java:

boolean isBundledApp = mApplicationInfo.isSystemApp()
        && !mApplicationInfo.isUpdatedSystemApp();

if (isBundledApp) {
    libraryPermittedPath += File.pathSeparator
            + Paths.get(getAppDir()).getParent().toString();
    // This is necessary to grant bundled apps access to
    // libraries located in subdirectories of /system/lib
    libraryPermittedPath += File.pathSeparator + defaultSearchPaths;
}

VNDK升级架构概览

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容