From 7331fe18d7ffd550996e07b534bc7a6cf625afa5 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov Date: Mon, 14 Dec 2015 14:11:17 -0800 Subject: [PATCH] linker: implement shared namespaces Shared namespaces clone the list of loaded native libraries from the caller namespace. This allows classloaders for bundled apps to share already loaded libraries with default namespace. Bug: http://b/22548808 Bug: http://b/26165097 Change-Id: I8949d45937fdb38e1f586ff0679003adac0d9dad (cherry picked from commit e78deef364d952dd1141a2f3067a12060aaf11e6) --- libc/include/android/dlext.h | 41 ++++++++-- libdl/libdl.c | 2 +- linker/dlfcn.cpp | 8 +- linker/linker.cpp | 32 ++++++-- linker/linker.h | 6 +- linker/linker_debug.h | 1 + tests/dlext_test.cpp | 147 +++++++++++++++++++++++++++++++++-- 7 files changed, 208 insertions(+), 29 deletions(-) diff --git a/libc/include/android/dlext.h b/libc/include/android/dlext.h index 7979c43d4..d5ec38690 100644 --- a/libc/include/android/dlext.h +++ b/libc/include/android/dlext.h @@ -142,6 +142,33 @@ extern void* android_dlopen_ext(const char* filename, int flag, const android_dl extern bool android_init_namespaces(const char* public_ns_sonames, const char* anon_ns_library_path); + +enum { + /* A regular namespace is the namespace with a custom search path that does + * not impose any restrictions on the location of native libraries. + */ + ANDROID_NAMESPACE_TYPE_REGULAR = 0, + + /* An isolated namespace requires all the libraries to be on the search path + * or under permitted_when_isolated_path. The search path is the union of + * ld_library_path and default_library_path. + */ + ANDROID_NAMESPACE_TYPE_ISOLATED = 1, + + /* The shared namespace clones the list of libraries of the caller namespace upon creation + * which means that they are shared between namespaces - the caller namespace and the new one + * will use the same copy of a library if it was loaded prior to android_create_namespace call. + * + * Note that libraries loaded after the namespace is created will not be shared. + * + * Shared namespaces can be isolated or regular. Note that they do not inherit the search path nor + * permitted_path from the caller's namespace. + */ + ANDROID_NAMESPACE_TYPE_SHARED = 2, + ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED = ANDROID_NAMESPACE_TYPE_SHARED | + ANDROID_NAMESPACE_TYPE_ISOLATED, +}; + /* * Creates new linker namespace. * ld_library_path and default_library_path represent the search path @@ -152,19 +179,19 @@ extern bool android_init_namespaces(const char* public_ns_sonames, * 2. In directories specified by DT_RUNPATH of the "needed by" binary. * 3. deault_library_path (This of this as namespace-local default library path) * - * When is_isolated is true the resulting namespace requires all of the libraries - * to be on the search path or under the permitted_when_isolated_path; the search_path is - * ld_library_path:default_library_path. Note that the permitted_when_isolated_path path - * is not part of the search_path and does not affect the search order. It is a way - * to allow loading libraries from specific locations when using absolute path. - * + * When type is ANDROID_NAMESPACE_TYPE_ISOLATED the resulting namespace requires all of + * the libraries to be on the search path or under the permitted_when_isolated_path; + * the search_path is ld_library_path:default_library_path. Note that the + * permitted_when_isolated_path path is not part of the search_path and + * does not affect the search order. It is a way to allow loading libraries from specific + * locations when using absolute path. * If a library or any of its dependencies are outside of the permitted_when_isolated_path * and search_path, and it is not part of the public namespace dlopen will fail. */ extern struct android_namespace_t* android_create_namespace(const char* name, const char* ld_library_path, const char* default_library_path, - bool is_isolated, + uint64_t type, const char* permitted_when_isolated_path); __END_DECLS diff --git a/libdl/libdl.c b/libdl/libdl.c index 0604d3eee..fa5237f6f 100644 --- a/libdl/libdl.c +++ b/libdl/libdl.c @@ -65,7 +65,7 @@ bool android_init_namespaces(const char* public_ns_sonames __unused, struct android_namespace_t* android_create_namespace(const char* name __unused, const char* ld_library_path __unused, const char* default_library_path __unused, - bool isolated __unused, + uint64_t type __unused, const char* permitted_when_isolated_path __unused) { return 0; } diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp index ba54d3973..a7c3fb0cb 100644 --- a/linker/dlfcn.cpp +++ b/linker/dlfcn.cpp @@ -148,12 +148,14 @@ bool android_init_namespaces(const char* public_ns_sonames, } android_namespace_t* android_create_namespace(const char* name, const char* ld_library_path, - const char* default_library_path, bool is_isolated, + const char* default_library_path, uint64_t type, const char* permitted_when_isolated_path) { + void* caller_addr = __builtin_return_address(0); ScopedPthreadMutexLocker locker(&g_dl_mutex); - android_namespace_t* result = create_namespace(name, ld_library_path, default_library_path, - is_isolated, permitted_when_isolated_path); + android_namespace_t* result = create_namespace(caller_addr, name, ld_library_path, + default_library_path, type, + permitted_when_isolated_path); if (result == nullptr) { __bionic_format_dlerror("android_create_namespace failed", linker_get_error_buffer()); diff --git a/linker/linker.cpp b/linker/linker.cpp index e38e2523c..eb938c750 100644 --- a/linker/linker.cpp +++ b/linker/linker.cpp @@ -1747,7 +1747,6 @@ static bool load_library(android_namespace_t* ns, }); return true; - } static bool load_library(android_namespace_t* ns, @@ -2377,8 +2376,12 @@ bool init_namespaces(const char* public_ns_sonames, const char* anon_ns_library_ g_public_namespace_initialized = true; // create anonymous namespace + // When the caller is nullptr - create_namespace will take global group + // from the anonymous namespace, which is fine because anonymous namespace + // is still pointing to the default one. android_namespace_t* anon_ns = - create_namespace("(anonymous)", nullptr, anon_ns_library_path, false, nullptr); + create_namespace(nullptr, "(anonymous)", nullptr, anon_ns_library_path, + ANDROID_NAMESPACE_TYPE_REGULAR, nullptr); if (anon_ns == nullptr) { g_public_namespace_initialized = false; @@ -2389,16 +2392,23 @@ bool init_namespaces(const char* public_ns_sonames, const char* anon_ns_library_ return true; } -android_namespace_t* create_namespace(const char* name, +android_namespace_t* create_namespace(const void* caller_addr, + const char* name, const char* ld_library_path, const char* default_library_path, - bool is_isolated, + uint64_t type, const char* permitted_when_isolated_path) { if (!g_public_namespace_initialized) { DL_ERR("cannot create namespace: public namespace is not initialized."); return nullptr; } + soinfo* caller_soinfo = find_containing_library(caller_addr); + + android_namespace_t* caller_ns = caller_soinfo != nullptr ? + caller_soinfo->get_namespace() : + g_anonymous_namespace; + ProtectedDataGuard guard; std::vector ld_library_paths; std::vector default_library_paths; @@ -2410,14 +2420,20 @@ android_namespace_t* create_namespace(const char* name, android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t(); ns->set_name(name); - ns->set_isolated(is_isolated); + ns->set_isolated((type & ANDROID_NAMESPACE_TYPE_ISOLATED) != 0); ns->set_ld_library_paths(std::move(ld_library_paths)); ns->set_default_library_paths(std::move(default_library_paths)); ns->set_permitted_paths(std::move(permitted_paths)); - // TODO(dimtiry): Should this be global group of caller's namespace? - auto global_group = make_global_group(&g_default_namespace); - std::copy(global_group.begin(), global_group.end(), std::back_inserter(ns->soinfo_list())); + if ((type & ANDROID_NAMESPACE_TYPE_SHARED) != 0) { + // If shared - clone the caller namespace + auto& soinfo_list = caller_ns->soinfo_list(); + std::copy(soinfo_list.begin(), soinfo_list.end(), std::back_inserter(ns->soinfo_list())); + } else { + // If not shared - copy only the global group + auto global_group = make_global_group(caller_ns); + std::copy(global_group.begin(), global_group.end(), std::back_inserter(ns->soinfo_list())); + } return ns; } diff --git a/linker/linker.h b/linker/linker.h index 5ec259ede..5a06853db 100644 --- a/linker/linker.h +++ b/linker/linker.h @@ -444,8 +444,8 @@ void set_application_target_sdk_version(uint32_t target); uint32_t get_application_target_sdk_version(); bool init_namespaces(const char* public_ns_sonames, const char* anon_ns_library_path); -android_namespace_t* create_namespace(const char* name, const char* ld_library_path, - const char* default_library_path, bool is_isolated, - const char* permitted_when_isolated_path); +android_namespace_t* create_namespace(const void* caller_addr, const char* name, + const char* ld_library_path, const char* default_library_path, + uint64_t type, const char* permitted_when_isolated_path); #endif diff --git a/linker/linker_debug.h b/linker/linker_debug.h index 17c6986f0..5af992916 100644 --- a/linker/linker_debug.h +++ b/linker/linker_debug.h @@ -55,6 +55,7 @@ *********************************************************************/ #include "private/libc_logging.h" +#include __LIBC_HIDDEN__ extern int g_ld_debug_verbosity; diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp index 5327e36b2..261aa5549 100644 --- a/tests/dlext_test.cpp +++ b/tests/dlext_test.cpp @@ -625,13 +625,21 @@ TEST(dlext, ns_smoke) { // Check that libraries added to public namespace are NODELETE dlclose(handle_public); - handle_public = dlopen((lib_path + "/public_namespace_libs/" + g_public_lib).c_str(), RTLD_NOW | RTLD_NOLOAD); + handle_public = dlopen((lib_path + "/public_namespace_libs/" + g_public_lib).c_str(), + RTLD_NOW | RTLD_NOLOAD); + ASSERT_TRUE(handle_public != nullptr) << dlerror(); - android_namespace_t* ns1 = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false, nullptr); + android_namespace_t* ns1 = + android_create_namespace("private", nullptr, + (lib_path + "/private_namespace_libs").c_str(), + ANDROID_NAMESPACE_TYPE_REGULAR, nullptr); ASSERT_TRUE(ns1 != nullptr) << dlerror(); - android_namespace_t* ns2 = android_create_namespace("private_isolated", nullptr, (lib_path + "/private_namespace_libs").c_str(), true, nullptr); + android_namespace_t* ns2 = + android_create_namespace("private_isolated", nullptr, + (lib_path + "/private_namespace_libs").c_str(), + ANDROID_NAMESPACE_TYPE_ISOLATED, nullptr); ASSERT_TRUE(ns2 != nullptr) << dlerror(); // This should not have affect search path for default namespace: @@ -732,13 +740,22 @@ TEST(dlext, ns_isolated) { ASSERT_TRUE(android_init_namespaces(path.c_str(), nullptr)) << dlerror(); - android_namespace_t* ns_not_isolated = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false, nullptr); + android_namespace_t* ns_not_isolated = + android_create_namespace("private", nullptr, + (lib_path + "/private_namespace_libs").c_str(), + ANDROID_NAMESPACE_TYPE_REGULAR, nullptr); ASSERT_TRUE(ns_not_isolated != nullptr) << dlerror(); - android_namespace_t* ns_isolated = android_create_namespace("private_isolated1", nullptr, (lib_path + "/private_namespace_libs").c_str(), true, nullptr); + android_namespace_t* ns_isolated = + android_create_namespace("private_isolated1", nullptr, + (lib_path + "/private_namespace_libs").c_str(), + ANDROID_NAMESPACE_TYPE_ISOLATED, nullptr); ASSERT_TRUE(ns_isolated != nullptr) << dlerror(); - android_namespace_t* ns_isolated2 = android_create_namespace("private_isolated2", (lib_path + "/private_namespace_libs").c_str(), nullptr, true, lib_path.c_str()); + android_namespace_t* ns_isolated2 = + android_create_namespace("private_isolated2", + (lib_path + "/private_namespace_libs").c_str(), + nullptr, ANDROID_NAMESPACE_TYPE_ISOLATED, lib_path.c_str()); ASSERT_TRUE(ns_isolated2 != nullptr) << dlerror(); ASSERT_TRUE(dlopen(root_lib, RTLD_NOW) == nullptr); @@ -808,6 +825,122 @@ TEST(dlext, ns_isolated) { dlclose(handle1); } +TEST(dlext, ns_shared) { + static const char* root_lib = "libnstest_root_not_isolated.so"; + static const char* root_lib_isolated = "libnstest_root.so"; + std::string path = std::string("libc.so:libc++.so:libdl.so:libm.so:") + g_public_lib; + + const std::string lib_path = std::string(getenv("ANDROID_DATA")) + NATIVE_TESTS_PATH; + const std::string lib_public_path = lib_path + "/public_namespace_libs/" + g_public_lib; + void* handle_public = dlopen(lib_public_path.c_str(), RTLD_NOW); + ASSERT_TRUE(handle_public != nullptr) << dlerror(); + + android_set_application_target_sdk_version(42U); // something > 23 + + ASSERT_TRUE(android_init_namespaces(path.c_str(), nullptr)) << dlerror(); + + // preload this library to the default namespace to check if it + // is shared later on. + void* handle_dlopened = + dlopen((lib_path + "/private_namespace_libs/libnstest_dlopened.so").c_str(), RTLD_NOW); + ASSERT_TRUE(handle_dlopened != nullptr) << dlerror(); + + android_namespace_t* ns_not_isolated = + android_create_namespace("private", nullptr, + (lib_path + "/private_namespace_libs").c_str(), + ANDROID_NAMESPACE_TYPE_REGULAR, nullptr); + ASSERT_TRUE(ns_not_isolated != nullptr) << dlerror(); + + android_namespace_t* ns_isolated_shared = + android_create_namespace("private_isolated_shared", nullptr, + (lib_path + "/private_namespace_libs").c_str(), + ANDROID_NAMESPACE_TYPE_ISOLATED | ANDROID_NAMESPACE_TYPE_SHARED, + nullptr); + ASSERT_TRUE(ns_isolated_shared != nullptr) << dlerror(); + + ASSERT_TRUE(dlopen(root_lib, RTLD_NOW) == nullptr); + ASSERT_STREQ("dlopen failed: library \"libnstest_root_not_isolated.so\" not found", dlerror()); + + std::string lib_private_external_path = + lib_path + "/private_namespace_libs_external/libnstest_private_external.so"; + + // Load lib_private_external_path to default namespace + // (it should remain invisible for the isolated namespaces after this) + void* handle = dlopen(lib_private_external_path.c_str(), RTLD_NOW); + ASSERT_TRUE(handle != nullptr) << dlerror(); + + android_dlextinfo extinfo; + extinfo.flags = ANDROID_DLEXT_USE_NAMESPACE; + extinfo.library_namespace = ns_not_isolated; + + void* handle1 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo); + ASSERT_TRUE(handle1 != nullptr) << dlerror(); + + extinfo.library_namespace = ns_isolated_shared; + + void* handle2 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo); + ASSERT_TRUE(handle2 == nullptr); + ASSERT_STREQ("dlopen failed: library \"libnstest_private_external.so\" not found", dlerror()); + + // Check dlopen by absolute path + handle2 = android_dlopen_ext(lib_private_external_path.c_str(), RTLD_NOW, &extinfo); + ASSERT_TRUE(handle2 == nullptr); + ASSERT_EQ("dlopen failed: library \"" + lib_private_external_path + "\" is not accessible for the namespace \"private_isolated_shared\"", dlerror()); + + // load libnstest_root.so to shared namespace in order to check that everything is different + // except shared libnstest_dlopened.so + + handle2 = android_dlopen_ext(root_lib_isolated, RTLD_NOW, &extinfo); + + typedef const char* (*fn_t)(); + fn_t ns_get_local_string = reinterpret_cast(dlsym(handle1, "ns_get_local_string")); + ASSERT_TRUE(ns_get_local_string != nullptr) << dlerror(); + fn_t ns_get_local_string_shared = reinterpret_cast(dlsym(handle2, "ns_get_local_string")); + ASSERT_TRUE(ns_get_local_string_shared != nullptr) << dlerror(); + + ASSERT_STREQ("This string is local to root library", ns_get_local_string()); + ASSERT_STREQ("This string is local to root library", ns_get_local_string_shared()); + ASSERT_TRUE(ns_get_local_string() != ns_get_local_string_shared()); + + fn_t ns_get_private_extern_string = + reinterpret_cast(dlsym(handle1, "ns_get_private_extern_string")); + ASSERT_TRUE(ns_get_private_extern_string != nullptr) << dlerror(); + fn_t ns_get_private_extern_string_shared = + reinterpret_cast(dlsym(handle2, "ns_get_private_extern_string")); + ASSERT_TRUE(ns_get_private_extern_string_shared() != nullptr) << dlerror(); + + ASSERT_STREQ("This string is from private namespace", ns_get_private_extern_string()); + ASSERT_STREQ("This string is from private namespace", ns_get_private_extern_string_shared()); + ASSERT_TRUE(ns_get_private_extern_string() != ns_get_private_extern_string_shared()); + + fn_t ns_get_public_extern_string = + reinterpret_cast(dlsym(handle1, "ns_get_public_extern_string")); + ASSERT_TRUE(ns_get_public_extern_string != nullptr) << dlerror(); + fn_t ns_get_public_extern_string_shared = + reinterpret_cast(dlsym(handle2, "ns_get_public_extern_string")); + ASSERT_TRUE(ns_get_public_extern_string_shared != nullptr) << dlerror(); + + ASSERT_STREQ("This string is from public namespace", ns_get_public_extern_string()); + ASSERT_STREQ("This string is from public namespace", ns_get_public_extern_string_shared()); + ASSERT_TRUE(ns_get_public_extern_string() == ns_get_public_extern_string_shared()); + + fn_t ns_get_dlopened_string = reinterpret_cast(dlsym(handle1, "ns_get_dlopened_string")); + ASSERT_TRUE(ns_get_dlopened_string != nullptr) << dlerror(); + fn_t ns_get_dlopened_string_shared = reinterpret_cast(dlsym(handle2, "ns_get_dlopened_string")); + ASSERT_TRUE(ns_get_dlopened_string_shared != nullptr) << dlerror(); + const char** ns_dlopened_string = static_cast(dlsym(handle_dlopened, "g_private_dlopened_string")); + ASSERT_TRUE(ns_dlopened_string != nullptr) << dlerror(); + + ASSERT_STREQ("This string is from private namespace (dlopened library)", ns_get_dlopened_string()); + ASSERT_STREQ("This string is from private namespace (dlopened library)", *ns_dlopened_string); + ASSERT_STREQ("This string is from private namespace (dlopened library)", ns_get_dlopened_string_shared()); + ASSERT_TRUE(ns_get_dlopened_string() != ns_get_dlopened_string_shared()); + ASSERT_TRUE(*ns_dlopened_string == ns_get_dlopened_string_shared()); + + dlclose(handle1); + dlclose(handle2); +} + TEST(dlext, ns_anonymous) { static const char* root_lib = "libnstest_root.so"; std::string path = std::string("libc.so:libc++.so:libdl.so:libm.so:") + g_public_lib; @@ -825,7 +958,7 @@ TEST(dlext, ns_anonymous) { android_namespace_t* ns = android_create_namespace( "private", nullptr, (lib_path + "/private_namespace_libs").c_str(), - false, nullptr); + ANDROID_NAMESPACE_TYPE_REGULAR, nullptr); ASSERT_TRUE(ns != nullptr) << dlerror();