2

Android 动态链接库隔离

Treble 架构将 Android 系统分为 Framework 和 Vendor 两部分,在本地库上也分为两套:Framework 和 Vendor。使用本地库时,动态链接器会在运行时从存储器中找到库,将它们加载到内存中并链接它们的符号。动态链接器使用库搜索路径(Library Search Path,Linux上由 LD_LIBRARY_PATH 来配置)来查找库,当接收到库加载请求时,动态链接器遍历库搜索路径进行搜索。动态链接器用 已加载库列表(Alread Loaded Library list)来跟踪加载的库,当要求加载的库已在列表中时,库数据会被直接使用。当进程正在运行时,已加载库列表 代表本地代码资源的状态。

尽管 Treble 架构已经将本地库在物理上进行分隔,并且使用 sepolicy 限制其访问权限。但是依然会存在以下问题,

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

为了解决这些难题,Treble 引入了链接器命名空间机制。它和 Java 类加载器 隔离资源的方法类似,通过这种设计,每个库都加载到一个特定的 命名空间 中,除非它们通过 命名空间链接namespace link)共享,否则不能访问其他 命名空间 中的库。

链接器命名空间

链接器命名空间机制由动态链接器提供,可以隔离不同链接器命名空间中的共享库,以确保具有相同库名称和不同符号的库不会发生冲突。同时,链接器命名空间机制可提供相应的灵活性,从而将由一个链接器命名空间导出的某些共享库用于另一个链接器命名空间。这些导出的共享库可能会成为对其他程序公开的应用编程接口,同时在其链接器命名空间中隐藏实现细节。

工作原理

进程的空间中可以存在多个链接器命名空间,链接器命名空间都有自己的库搜索路径已加载库列表。当动态链接器加载 共享库(DT_NEEDED 条目中指定或 dlopen()android_dlopen_ext() 的参数指定)时,动态链接器会找到库所在的链接器命名空间,并尝试将相关依赖项加载到同一个链接器命名空间中。如果动态链接器无法将共享库加载到指定的链接器命名空间中,它会向关联的链接器命名空间索取导出的共享库。

链接器的配置文件为ld.config,命名空间的配置也在其中指定。${android-src}/system/core/rootdir/etc 中有 3 个配置文件。系统会根据 BoardConfig.mkPRODUCT_TREBLE_LINKER_NAMESPACESBOARD_VNDK_VERSIONBOARD_VNDK_RUNTIME_DISABLE 的值选择不同的配置:

ld_config_4.png

ld.config.vndk_lite.txt 会隔离 SP-HAL 和 VNDK-SP 共享库,以使其符号不会与其他框架共享库发生冲突。链接器命名空间之间的关系如下所示:

treble_vndk_linker_namespace1.png

ld.config.txt 除了隔离 SP-HAL 和 VNDK-SP 共享库外, 还会提供全面的动态链接器隔离。它可确保系统分区中的模块不依赖于供应商分区中的共享库,反之亦然。链接器命名空间之间的关系如下图所示:

treble_vndk_linker_namespace0.png

配置文件格式

ld.config使用 INI 文件格式,典型的配置文件如下所示:

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

[system]
additional.namespaces = sphal,vndk

namespace.default.isolated = true
namespace.default.search.paths = /system/${LIB}
namespace.default.permitted.paths = /system/${LIB}/hw
namespace.default.asan.search.paths = /data/asan/system/${LIB}:/system/${LIB}
namespace.default.asan.permitted.paths = /data/asan/system/${LIB}/hw:/system/${LIB}/hw

namespace.sphal.isolated = true
namespace.sphal.visible = true
namespace.sphal.search.paths = /odm/${LIB}:/vendor/${LIB}
namespace.sphal.permitted.paths = /odm/${LIB}:/vendor/${LIB}
namespace.sphal.asan.search.paths  = /data/asan/odm/${LIB}:/odm/${LIB}
namespace.sphal.asan.search.paths += /data/asan/vendor/${LIB}:/vendor/${LIB}
namespace.sphal.asan.permitted.paths  = /data/asan/odm/${LIB}:/odm/${LIB}
namespace.sphal.asan.permitted.paths += /data/asan/vendor/${LIB}:/vendor/${LIB}
namespace.sphal.links = default,vndk
namespace.sphal.link.default.shared_libs = libc.so:libm.so
namespace.sphal.link.vndk.shared_libs = libbase.so:libcutils.so

namespace.vndk.isolated = true
namespace.vndk.search.paths = /system/${LIB}/vndk-sp-29
namespace.vndk.permitted.paths = /system/${LIB}/vndk-sp-29
namespace.vndk.links = default
namespace.vndk.link.default.shared_libs = libc.so:libm.so

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

配置文件包含以下内容:

  • 多个“目录-区段”映射属性(位于动态链接器的开头),用于选择有效的区段。
  • 多个链接器命名空间配置区段:

    • 每个区段都包含多个命名空间(圆形顶点)以及各命名空间之间的多个回退链接(图形弧)。
    • 每个命名空间都具有自己的隔离、搜索路径、允许的路径以及可见性设置。

下表详细介绍了各属性的含义。

ld_config_1.png

ld_config_2.png

ld_config_3.png

动态链接库共享

命名空间共享

链接器命名空间将本地库的使用隔离开来,但是许多情况下当前命名空间可能需要使用其他命名空间的库。这时可以通过设置配置文件中的属性,将一个命名空间的某些共享库导出到另一个命名空间。涉及的属性如下,

  • links:后备命名空间。如果在当前命名空间中找不到共享库,则动态链接器会尝试从后备命名空间加载共享库。
  • link.other.shared_libs:共享库列表。
  • link.other.allow_all_shared_libs:布尔值,用于指示是否可以在 other 命名空间中搜索所有共享库。

例如,如果在配置文件system目录的vndk区段的设置如下,

namespace.vndk.links = default,sphal
namespace.vndk.link.default.shared_libs  = libEGL.so:libRS.so:libc.so
namespace.vndk.link.sphal.allow_all_shared_libs = true

表示system目录下,vndk命名空间可以使用default命名空间的libEGL.so、libRS.so、libc.so,可以使用sphal命名空间的全部库。需要注意的是,link.other.shared_libs属性无法与 link.other.allow_all_shared_libs 一起使用。

系统库共享

Treble架构将系统和应用分离,应用理论上只可以访问NDK提供的本地库和应用自带的本地库。但实际上应用也经常需要使用系统库,每个应用都将系统库打包到自己中未免太浪费了。所以Android还提供了应用访问系统库的方法,通过public.libraries.txt设置白名单。

将本地系统库的名字加入到public.libraries.txt,可以开放给应用。本地系统库放置的位置为,

  • /vendor/lib(芯片供应商的 32 位库)和 /vendor/lib64(芯片供应商的 64 位库)
  • /system/lib(设备制造商的 32 位库)和 /system/lib64(设备制造商的 64 位库)

public.libraries.txt存在多个文件,分别代表不同的意义:

  • /vendor/etc/public.libraries.txt(芯片供应商的库)。
  • /system/etc/public.libraries-COMPANYNAME.txt(设备制造商的库),其中 COMPANYNAME 指的是制造商的名称(例如 awesome.company)。如果某些库来自外部解决方案提供商,则可以在设备中包含多个此类 .txt 文件。
  • /system/etc/public.libraries.txt(标准公共本地库),原生代码system/core/rootdir/etc/中的文件,理论上不应该修改。

注意:由设备制造商公开的本地库必须命名为 lib*COMPANYNAME.so,例如 libFoo.awesome.company.so。换句话说,没有公司名称后缀的 libFoo.so 不得公开。库文件名中的 COMPANYNAME 必须与列出库名称的 txt 文件的名称中的 COMPANYNAME 匹配。

从 Android 8.0 开始,供应商的本地库需要遵循以下额外限制,需要进行相应设置:

  1. 必须给供应商的本地库赋予适当的权限,以便应用可以访问。如过有任何应用(包括第三方应用)要求访问一个供应商的本地库,则该库必须在供应商特定的 file_contexts中被标记为same_process_hal_file。例如,设置libnative.so权限以供应用使用,

    /vendor/lib(64)?/libnative.so u:object_r:same_process_hal_file:s0
  2. 库不得依赖(无论是直接依赖,还是通过其依赖项间接依赖)VNDK-SP 库和 LLNDK 库之外的任何系统库。您可以在 development/vndk/tools/definition/tool/datasets/eligible-list--release.csv 中找到 VNDK-SP 库和 LLNDK 库的列表。
参考文档:

Linker Namespace
Namespaces for Native Libraries


戈壁老王
143 声望64 粉丝

做为一个不称职的老年码农,一直疏忽整理笔记,开博记录一下,用来丰富老年生活,