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.mk
中 PRODUCT_TREBLE_LINKER_NAMESPACES
、BOARD_VNDK_VERSION
和 BOARD_VNDK_RUNTIME_DISABLE
的值选择不同的配置:
ld.config.vndk_lite.txt
会隔离 SP-HAL 和 VNDK-SP 共享库,以使其符号不会与其他框架共享库发生冲突。链接器命名空间之间的关系如下所示:
ld.config.txt
除了隔离 SP-HAL 和 VNDK-SP 共享库外, 还会提供全面的动态链接器隔离。它可确保系统分区中的模块不依赖于供应商分区中的共享库,反之亦然。链接器命名空间之间的关系如下图所示:
配置文件格式
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}
配置文件包含以下内容:
- 多个“目录-区段”映射属性(位于动态链接器的开头),用于选择有效的区段。
-
多个链接器命名空间配置区段:
- 每个区段都包含多个命名空间(圆形顶点)以及各命名空间之间的多个回退链接(图形弧)。
- 每个命名空间都具有自己的隔离、搜索路径、允许的路径以及可见性设置。
下表详细介绍了各属性的含义。
动态链接库共享
命名空间共享
链接器命名空间将本地库的使用隔离开来,但是许多情况下当前命名空间可能需要使用其他命名空间的库。这时可以通过设置配置文件中的属性,将一个命名空间的某些共享库导出到另一个命名空间。涉及的属性如下,
- 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 开始,供应商的本地库需要遵循以下额外限制,需要进行相应设置:
-
必须给供应商的本地库赋予适当的权限,以便应用可以访问。如过有任何应用(包括第三方应用)要求访问一个供应商的本地库,则该库必须在供应商特定的
file_contexts
中被标记为same_process_hal_file
。例如,设置libnative.so
权限以供应用使用,/vendor/lib(64)?/libnative.so u:object_r:same_process_hal_file:s0
- 库不得依赖(无论是直接依赖,还是通过其依赖项间接依赖)VNDK-SP 库和 LLNDK 库之外的任何系统库。您可以在
development/vndk/tools/definition/tool/datasets/eligible-list--release.csv
中找到 VNDK-SP 库和 LLNDK 库的列表。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。