diff options
-rw-r--r-- | libnativebridge/Android.bp | 11 | ||||
-rw-r--r-- | libnativebridge/include/nativebridge/native_bridge.h | 5 | ||||
-rw-r--r-- | libnativebridge/native_bridge.cc | 34 | ||||
-rw-r--r-- | libnativeloader/Android.bp | 35 | ||||
-rw-r--r-- | libnativeloader/library_namespaces.cpp | 10 | ||||
-rw-r--r-- | libnativeloader/native_loader.cpp | 6 | ||||
-rw-r--r-- | libnativeloader/native_loader_test.cpp | 110 | ||||
-rwxr-xr-x | test/etc/run-test-jar | 1 |
8 files changed, 152 insertions, 60 deletions
diff --git a/libnativebridge/Android.bp b/libnativebridge/Android.bp index cb07d35363..8e87997b00 100644 --- a/libnativebridge/Android.bp +++ b/libnativebridge/Android.bp @@ -10,9 +10,6 @@ package { cc_defaults { name: "libnativebridge-defaults", defaults: ["art_defaults"], - cppflags: [ - "-fvisibility=protected", - ], header_libs: [ "jni_headers", "libnativebridge-headers", @@ -64,10 +61,10 @@ art_cc_library { target: { android: { - version_script: "libnativebridge.map.txt", - }, - linux: { - version_script: "libnativebridge.map.txt", + header_libs: [ + "libnativeloader-headers", // For dlext_namespaces.h + ], + shared_libs: ["libdl_android"], }, }, diff --git a/libnativebridge/include/nativebridge/native_bridge.h b/libnativebridge/include/nativebridge/native_bridge.h index e20b6270a1..2199bab552 100644 --- a/libnativebridge/include/nativebridge/native_bridge.h +++ b/libnativebridge/include/nativebridge/native_bridge.h @@ -29,6 +29,11 @@ namespace android { extern "C" { #endif // __cplusplus +// Loads a shared library from the system linker namespace, suitable for +// platform libraries in /system/lib(64). If linker namespaces don't exist (i.e. +// on host), this simply calls dlopen(). +void* OpenSystemLibrary(const char* path, int flags); + struct NativeBridgeRuntimeCallbacks; struct NativeBridgeRuntimeValues; diff --git a/libnativebridge/native_bridge.cc b/libnativebridge/native_bridge.cc index 46a05a23bf..fb13d62be0 100644 --- a/libnativebridge/native_bridge.cc +++ b/libnativebridge/native_bridge.cc @@ -31,6 +31,10 @@ #include <android-base/macros.h> #include <log/log.h> +#ifdef ART_TARGET_ANDROID +#include "nativeloader/dlext_namespaces.h" +#endif + namespace android { #ifdef __APPLE__ @@ -40,6 +44,28 @@ void UNUSED(const T&) {} extern "C" { +void* OpenSystemLibrary(const char* path, int flags) { +#ifdef ART_TARGET_ANDROID + // The system namespace is called "default" for binaries in /system and + // "system" for those in the Runtime APEX. Try "system" first since + // "default" always exists. + // TODO(b/185587109): Get rid of this error prone logic. + android_namespace_t* system_ns = android_get_exported_namespace("system"); + if (system_ns == nullptr) { + system_ns = android_get_exported_namespace("default"); + LOG_ALWAYS_FATAL_IF(system_ns == nullptr, + "Failed to get system namespace for loading %s", path); + } + const android_dlextinfo dlextinfo = { + .flags = ANDROID_DLEXT_USE_NAMESPACE, + .library_namespace = system_ns, + }; + return android_dlopen_ext(path, flags, &dlextinfo); +#else + return dlopen(path, flags); +#endif +} + // Environment values required by the apps running with native bridge. struct NativeBridgeRuntimeValues { const char* os_arch; @@ -223,8 +249,12 @@ bool LoadNativeBridge(const char* nb_library_filename, if (!NativeBridgeNameAcceptable(nb_library_filename)) { CloseNativeBridge(true); } else { - // Try to open the library. - void* handle = dlopen(nb_library_filename, RTLD_LAZY); + // Try to open the library. We assume this library is provided by the + // platform rather than the ART APEX itself, so use the system namespace + // to avoid requiring a static linker config link to it from the + // com_android_art namespace. + void* handle = OpenSystemLibrary(nb_library_filename, RTLD_LAZY); + if (handle != nullptr) { callbacks = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle, kNativeBridgeInterfaceSymbol)); diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp index 316c087453..2cc6b80787 100644 --- a/libnativeloader/Android.bp +++ b/libnativeloader/Android.bp @@ -12,9 +12,6 @@ package { cc_defaults { name: "libnativeloader-defaults", defaults: ["art_defaults"], - cppflags: [ - "-fvisibility=hidden", - ], header_libs: ["libnativeloader-headers"], export_header_lib_headers: ["libnativeloader-headers"], } @@ -121,30 +118,32 @@ cc_library_headers { art_cc_test { name: "libnativeloader_test", + defaults: ["art_test_defaults"], + host_supported: false, + srcs: [ "native_loader_test.cpp", - "native_loader.cpp", - "library_namespaces.cpp", - "native_loader_namespace.cpp", - "public_libraries.cpp", ], cflags: ["-DANDROID"], - static_libs: [ - "libbase", - "liblog", - "libgmock", - "PlatformProperties", + + // The test mocks libdl_android and libnativebridge symbols, so export them + // to override the ones loaded from their libs. + ldflags: [ + "-Wl,--export-dynamic-symbol=android_*", + "-Wl,--export-dynamic-symbol=NativeBridge*", ], + header_libs: [ "libnativebridge-headers", "libnativehelper_header_only", - "libnativeloader-headers", ], - // native_loader_test.cpp mocks libdl APIs so system_shared_libs - // are used to include C libraries without libdl. - system_shared_libs: [ - "libc", - "libm", + static_libs: [ + "libgmock", + ], + shared_libs: [ + "libbase", + "libnativeloader", ], + test_suites: ["device-tests"], } diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp index 79fee060cf..59369eee43 100644 --- a/libnativeloader/library_namespaces.cpp +++ b/libnativeloader/library_namespaces.cpp @@ -122,16 +122,18 @@ void LibraryNamespaces::Initialize() { return; } - // android_init_namespaces() expects all the public libraries - // to be loaded so that they can be found by soname alone. + // Load the preloadable public libraries. Since libnativeloader is in the + // com_android_art namespace, use OpenSystemLibrary rather than dlopen to + // ensure the libraries are loaded in the system namespace. // // TODO(dimitry): this is a bit misleading since we do not know // if the vendor public library is going to be opened from /vendor/lib // we might as well end up loading them from /system/lib or /product/lib // For now we rely on CTS test to catch things like this but // it should probably be addressed in the future. - for (const auto& soname : android::base::Split(preloadable_public_libraries(), ":")) { - LOG_ALWAYS_FATAL_IF(dlopen(soname.c_str(), RTLD_NOW | RTLD_NODELETE) == nullptr, + for (const std::string& soname : android::base::Split(preloadable_public_libraries(), ":")) { + void* handle = OpenSystemLibrary(soname.c_str(), RTLD_NOW | RTLD_NODELETE); + LOG_ALWAYS_FATAL_IF(handle == nullptr, "Error preloading public library %s: %s", soname.c_str(), dlerror()); } } diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp index b34692ae16..30c7b5a377 100644 --- a/libnativeloader/native_loader.cpp +++ b/libnativeloader/native_loader.cpp @@ -254,7 +254,11 @@ void* OpenNativeLibrary(JNIEnv* env, int32_t target_sdk_version, const char* pat } } - void* handle = dlopen(path, RTLD_NOW); + // Fall back to the system namespace. This happens for preloaded JNI + // libraries in the zygote. + // TODO(b/185833744): Investigate if this should fall back to the app main + // namespace (aka anonymous namespace) instead. + void* handle = OpenSystemLibrary(path, RTLD_NOW); if (handle == nullptr) { *error_msg = strdup(dlerror()); } diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp index 43c3c151c9..e754414f46 100644 --- a/libnativeloader/native_loader_test.cpp +++ b/libnativeloader/native_loader_test.cpp @@ -36,12 +36,13 @@ namespace android { namespace nativeloader { using ::testing::Eq; +using ::testing::NotNull; using ::testing::Return; using ::testing::StrEq; using ::testing::_; using internal::ConfigEntry; -using internal::ParseConfig; using internal::ParseApexLibrariesConfig; +using internal::ParseConfig; #if defined(__LP64__) #define LIB_DIR "lib64" @@ -49,19 +50,15 @@ using internal::ParseApexLibrariesConfig; #define LIB_DIR "lib" #endif -// gmock interface that represents interested platform APIs on libdl and libnativebridge +// gmock interface that represents interested platform APIs on libdl_android and libnativebridge class Platform { public: virtual ~Platform() {} - // libdl APIs - virtual void* dlopen(const char* filename, int flags) = 0; - virtual int dlclose(void* handle) = 0; - virtual char* dlerror(void) = 0; - - // These mock_* are the APIs semantically the same across libdl and libnativebridge. + // These mock_* are the APIs semantically the same across libdl_android and libnativebridge. // Instead of having two set of mock APIs for the two, define only one set with an additional - // argument 'bool bridged' to identify the context (i.e., called for libdl or libnativebridge). + // argument 'bool bridged' to identify the context (i.e., called for libdl_android or + // libnativebridge). typedef char* mock_namespace_handle; virtual bool mock_init_anonymous_namespace(bool bridged, const char* sonames, const char* search_paths) = 0; @@ -74,7 +71,7 @@ class Platform { virtual void* mock_dlopen_ext(bool bridged, const char* filename, int flags, mock_namespace_handle ns) = 0; - // libnativebridge APIs for which libdl has no corresponding APIs + // libnativebridge APIs for which libdl_android has no corresponding APIs virtual bool NativeBridgeInitialized() = 0; virtual const char* NativeBridgeGetError() = 0; virtual bool NativeBridgeIsPathSupported(const char*) = 0; @@ -125,11 +122,6 @@ class MockPlatform : public Platform { })); } - // Mocking libdl APIs - MOCK_METHOD2(dlopen, void*(const char*, int)); - MOCK_METHOD1(dlclose, int(void*)); - MOCK_METHOD0(dlerror, char*()); - // Mocking the common APIs MOCK_METHOD3(mock_init_anonymous_namespace, bool(bool, const char*, const char*)); MOCK_METHOD7(mock_create_namespace, @@ -155,19 +147,11 @@ class MockPlatform : public Platform { static std::unique_ptr<MockPlatform> mock; -// Provide C wrappers for the mock object. +// Provide C wrappers for the mock object. These symbols must be exported by ld +// to be able to override the real symbols in the shared libs. extern "C" { -void* dlopen(const char* file, int flag) { - return mock->dlopen(file, flag); -} -int dlclose(void* handle) { - return mock->dlclose(handle); -} - -char* dlerror(void) { - return mock->dlerror(); -} +// libdl_android APIs bool android_init_anonymous_namespace(const char* sonames, const char* search_path) { return mock->mock_init_anonymous_namespace(false, sonames, search_path); @@ -197,6 +181,7 @@ void* android_dlopen_ext(const char* filename, int flags, const android_dlextinf } // libnativebridge APIs + bool NativeBridgeIsSupported(const char* libpath) { return mock->NativeBridgeIsSupported(libpath); } @@ -313,7 +298,8 @@ class NativeLoaderTest : public ::testing::TestWithParam<bool> { std::vector<std::string> default_public_libs = android::base::Split(preloadable_public_libraries(), ":"); for (auto l : default_public_libs) { - EXPECT_CALL(*mock, dlopen(StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE)) + EXPECT_CALL(*mock, + mock_dlopen_ext(false, StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE, NotNull())) .WillOnce(Return(any_nonnull)); } } @@ -336,6 +322,76 @@ TEST_P(NativeLoaderTest, InitializeLoadsDefaultPublicLibraries) { RunTest(); } +TEST_P(NativeLoaderTest, OpenNativeLibraryWithoutClassloaderInApex) { + const char* test_lib_path = "libfoo.so"; + void* fake_handle = &fake_handle; // Arbitrary non-null value + EXPECT_CALL(*mock, + mock_dlopen_ext(false, StrEq(test_lib_path), RTLD_NOW, NsEq("com_android_art"))) + .WillOnce(Return(fake_handle)); + + bool needs_native_bridge = false; + char* errmsg = nullptr; + EXPECT_EQ(fake_handle, + OpenNativeLibrary(env.get(), + /*target_sdk_version=*/17, + test_lib_path, + /*class_loader=*/nullptr, + /*caller_location=*/"/apex/com.android.art/javalib/myloadinglib.jar", + /*library_path=*/nullptr, + &needs_native_bridge, + &errmsg)); + // OpenNativeLibrary never uses nativebridge when there's no classloader. That + // should maybe change. + EXPECT_EQ(needs_native_bridge, false); + EXPECT_EQ(errmsg, nullptr); +} + +TEST_P(NativeLoaderTest, OpenNativeLibraryWithoutClassloaderInFramework) { + const char* test_lib_path = "libfoo.so"; + void* fake_handle = &fake_handle; // Arbitrary non-null value + EXPECT_CALL(*mock, mock_dlopen_ext(false, StrEq(test_lib_path), RTLD_NOW, NsEq("system"))) + .WillOnce(Return(fake_handle)); + + bool needs_native_bridge = false; + char* errmsg = nullptr; + EXPECT_EQ(fake_handle, + OpenNativeLibrary(env.get(), + /*target_sdk_version=*/17, + test_lib_path, + /*class_loader=*/nullptr, + /*caller_location=*/"/system/framework/framework.jar!classes1.dex", + /*library_path=*/nullptr, + &needs_native_bridge, + &errmsg)); + // OpenNativeLibrary never uses nativebridge when there's no classloader. That + // should maybe change. + EXPECT_EQ(needs_native_bridge, false); + EXPECT_EQ(errmsg, nullptr); +} + +TEST_P(NativeLoaderTest, OpenNativeLibraryWithoutClassloaderAndCallerLocation) { + const char* test_lib_path = "libfoo.so"; + void* fake_handle = &fake_handle; // Arbitrary non-null value + EXPECT_CALL(*mock, mock_dlopen_ext(false, StrEq(test_lib_path), RTLD_NOW, NsEq("system"))) + .WillOnce(Return(fake_handle)); + + bool needs_native_bridge = false; + char* errmsg = nullptr; + EXPECT_EQ(fake_handle, + OpenNativeLibrary(env.get(), + /*target_sdk_version=*/17, + test_lib_path, + /*class_loader=*/nullptr, + /*caller_location=*/nullptr, + /*library_path=*/nullptr, + &needs_native_bridge, + &errmsg)); + // OpenNativeLibrary never uses nativebridge when there's no classloader. That + // should maybe change. + EXPECT_EQ(needs_native_bridge, false); + EXPECT_EQ(errmsg, nullptr); +} + INSTANTIATE_TEST_SUITE_P(NativeLoaderTests, NativeLoaderTest, testing::Bool()); ///////////////////////////////////////////////////////////////// diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar index 86eb722e6b..ba3c4eb60f 100755 --- a/test/etc/run-test-jar +++ b/test/etc/run-test-jar @@ -1234,7 +1234,6 @@ if [ "$HOST" = "n" ]; then cmdline="cd $DEX_LOCATION && \ export ASAN_OPTIONS=$RUN_TEST_ASAN_OPTIONS && \ export ANDROID_DATA=$DEX_LOCATION && \ - export ANDROID_ADDITIONAL_PUBLIC_LIBRARIES=$PUBLIC_LIBS && \ export DEX_LOCATION=$DEX_LOCATION && \ export ANDROID_ROOT=$ANDROID_ROOT && \ export ANDROID_I18N_ROOT=$ANDROID_I18N_ROOT && \ |