為了解決碎片化硬萍、升級慢問題,android從8.0開始推出了Project Treble計劃辙纬,詣在分離android framework和硬件驅(qū)動的耦合生兆,system分區(qū)只存放原生android相關(guān),vendor分區(qū)存放廠商相關(guān)定制拴还。
主要通過HIDL跨晴、VNDK、SELinux來實現(xiàn)隔離片林,今天主要介紹VNDK相關(guān)的Linker namespace隔離機制端盆。
VNDK
為了支持system分區(qū)可以升級到最新版本,而vendor分區(qū)保持不變费封,這意味著在不同時間編譯的二進制文件必須能夠相互配合使用焕妙,因此vendor分區(qū)程序只能訪問system分區(qū)穩(wěn)定的動態(tài)庫。
共享庫可分為以下三個子類別:
-
LL-NDK 庫:是已知穩(wěn)定的框架共享庫弓摘。它們的開發(fā)者致力于保持其 API/ABI 穩(wěn)定性焚鹊。
- 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): 是指可以安全復(fù)制兩次的框架共享庫迈窟∷街玻框架模塊和供應(yīng)商模塊可以與其各自的庫副本相關(guān)聯(lián)。
框架專用庫 (FWK-ONLY) :被視為框架內(nèi)部實現(xiàn)細節(jié)车酣,不穩(wěn)定曲稼,不得由供應(yīng)商模塊訪問索绪。
Same-Process HAL (SP-HAL):是一組預(yù)先確定的 HAL,作為供應(yīng)商共享庫進行實現(xiàn)贫悄,并被加載到框架進程中瑞驱。SP-HAL 由鏈接器命名空間進行隔離。SP-HAL 必須僅依賴于 LL-NDK 和 VNDK-SP窄坦。
VNDK-SP:是一部分預(yù)定義的符合條件的 VNDK 庫唤反。SP-HAL 和 VNDK-SP 均由 Google 定義。
Linker:namespace
Linker namespace解決了 Treble VNDK 設(shè)計中的兩個難題:
- 將 SP-HAL 共享庫及其依賴項(包括 VNDK-SP 庫)加載到框架進程中鸭津。這種情況下應(yīng)該有一些防止
出現(xiàn)符號沖突的機制彤侍。 - dlopen() 和 android_dlopen_ext() 可能會引入一些在編譯時不可見的運行時依賴項,這些依賴項使用
靜態(tài)分析很難檢測到逆趋。
例如:
/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實現(xiàn)原理
簡單來說就是用vector實現(xiàn)多個namespace功能的杆融,沒有namespace的linker只有一個LSPath和ALList楞卡,啟用namespace后使用vector實現(xiàn)多個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池摧,該程序?qū)⒛軌颢@取鏈接器命名空間句柄,該句柄隨后可傳遞到 android_dlopen_ext()激况。
namespace.sphal.search.paths = /vendor/${LIB}
namespace.sphal.links = default //如果無法加載到 sphal 命名空間作彤,則動態(tài)鏈接器會嘗試從 default 命名空間加載此共享庫。
namespace.sphal.link.default.shared_libs = libc.so:libm.so //如果請求的庫不是 libc.so、libm.so,則動態(tài)鏈接器會忽略從 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會根據(jù)配置文件初始化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加載時調(diào)用的System.loadLibrary()、dlopen()、android_dlopen_ext()最終都走到do_dlopen():
- void* caller_addr = __builtin_return_address(0);//得到當(dāng)前函數(shù)返回地址
- soinfo* const caller = find_containing_library(caller_addr);//查找地址所在的動態(tài)庫
- android_namespace_t* ns = get_caller_namespace(caller);//ns為調(diào)用庫所在命名空間
也就是說新加載so的namespace與加載者保持一致艺沼。關(guān)鍵代碼如下:
void* dlopen(const char* filename, int flag) {
const void* caller_addr = __builtin_return_address(0);//得到當(dāng)前函數(shù)返回地址
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);//查找地址所在的動態(tài)庫
android_namespace_t* ns = get_caller_namespace(caller);//ns為調(diào)用庫所在命名空間
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);//根據(jù)ns來find
}
//根據(jù)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() 將創(chuàng)建一個 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導(dǎo)出的庫缚窿,否則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;
}