| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Tests accessibility of platform native libraries |
| */ |
| |
| #include <dirent.h> |
| #include <dlfcn.h> |
| #include <fcntl.h> |
| #include <jni.h> |
| #include <libgen.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <queue> |
| #include <regex> |
| #include <string> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/properties.h> |
| #include <android-base/strings.h> |
| #include <nativehelper/JNIHelp.h> |
| #include <nativehelper/ScopedLocalRef.h> |
| #include <nativehelper/ScopedUtfChars.h> |
| |
| #if defined(__LP64__) |
| #define LIB_DIR "lib64" |
| #else |
| #define LIB_DIR "lib" |
| #endif |
| |
| static const std::string kSystemLibraryPath = "/system/" LIB_DIR; |
| static const std::string kArtApexLibraryPath = "/apex/com.android.art/" LIB_DIR; |
| static const std::string kVendorLibraryPath = "/vendor/" LIB_DIR; |
| static const std::string kProductLibraryPath = "/product/" LIB_DIR; |
| |
| static const std::vector<std::regex> kSystemPathRegexes = { |
| std::regex("/system/lib(64)?"), |
| std::regex("/apex/com\\.android\\.[^/]*/lib(64)?"), |
| std::regex("/system/lib/arm(64)?"), // when CTS runs in ARM ABI on non-ARM CPU. http://b/149852946 |
| }; |
| |
| static const std::string kWebViewPlatSupportLib = "libwebviewchromium_plat_support.so"; |
| |
| static bool is_directory(const char* path) { |
| struct stat sb; |
| if (stat(path, &sb) != -1) { |
| return S_ISDIR(sb.st_mode); |
| } |
| |
| return false; |
| } |
| |
| static bool not_accessible(const std::string& err) { |
| return err.find("dlopen failed: library \"") == 0 && |
| err.find("is not accessible for the namespace \"classloader-namespace\"") != std::string::npos; |
| } |
| |
| static bool not_found(const std::string& err) { |
| return err.find("dlopen failed: library \"") == 0 && |
| err.find("\" not found") != std::string::npos; |
| } |
| |
| static bool wrong_arch(const std::string& library, const std::string& err) { |
| // https://issuetracker.google.com/37428428 |
| // It's okay to not be able to load a library because it's for another |
| // architecture (typically on an x86 device, when we come across an arm library). |
| return err.find("dlopen failed: \"" + library + "\" has unexpected e_machine: ") == 0; |
| } |
| |
| static bool is_library_on_path(const std::unordered_set<std::string>& library_search_paths, |
| const std::string& baselib, |
| const std::string& path) { |
| std::string tail = '/' + baselib; |
| if (!android::base::EndsWith(path, tail)) return false; |
| return library_search_paths.count(path.substr(0, path.size() - tail.size())) > 0; |
| } |
| |
| static std::string try_dlopen(const std::string& path) { |
| // try to load the lib using dlopen(). |
| void *handle = dlopen(path.c_str(), RTLD_NOW); |
| std::string error; |
| |
| bool loaded_in_native = handle != nullptr; |
| if (loaded_in_native) { |
| dlclose(handle); |
| } else { |
| error = dlerror(); |
| } |
| return error; |
| } |
| |
| // Tests if a file can be loaded or not. Returns empty string on success. On any failure |
| // returns the error message from dlerror(). |
| static std::string load_library(JNIEnv* env, jclass clazz, const std::string& path, |
| bool test_system_load_library) { |
| std::string error = try_dlopen(path); |
| bool loaded_in_native = error.empty(); |
| |
| if (android::base::EndsWith(path, '/' + kWebViewPlatSupportLib)) { |
| // Don't try to load this library from Java. Otherwise, the lib is initialized via |
| // JNI_OnLoad and it fails since WebView is not loaded in this test process. |
| return error; |
| } |
| |
| // try to load the same lib using System.load() in Java to see if it gives consistent |
| // result with dlopen. |
| static jmethodID java_load = |
| env->GetStaticMethodID(clazz, "loadWithSystemLoad", "(Ljava/lang/String;)Ljava/lang/String;"); |
| ScopedLocalRef<jstring> jpath(env, env->NewStringUTF(path.c_str())); |
| jstring java_load_errmsg = jstring(env->CallStaticObjectMethod(clazz, java_load, jpath.get())); |
| bool java_load_ok = env->GetStringLength(java_load_errmsg) == 0; |
| |
| jstring java_load_lib_errmsg; |
| bool java_load_lib_ok = java_load_ok; |
| if (test_system_load_library && java_load_ok) { |
| // If System.load() works then test System.loadLibrary() too. Cannot test |
| // the other way around since System.loadLibrary() might very well find the |
| // library somewhere else and hence work when System.load() fails. |
| std::string baselib = basename(path.c_str()); |
| ScopedLocalRef<jstring> jname(env, env->NewStringUTF(baselib.c_str())); |
| static jmethodID java_load_lib = env->GetStaticMethodID( |
| clazz, "loadWithSystemLoadLibrary", "(Ljava/lang/String;)Ljava/lang/String;"); |
| java_load_lib_errmsg = jstring(env->CallStaticObjectMethod(clazz, java_load_lib, jname.get())); |
| java_load_lib_ok = env->GetStringLength(java_load_lib_errmsg) == 0; |
| } |
| |
| if (loaded_in_native != java_load_ok || java_load_ok != java_load_lib_ok) { |
| const std::string java_load_error(ScopedUtfChars(env, java_load_errmsg).c_str()); |
| error = "Inconsistent result for library \"" + path + "\": dlopen() " + |
| (loaded_in_native ? "succeeded" : "failed (" + error + ")") + |
| ", System.load() " + |
| (java_load_ok ? "succeeded" : "failed (" + java_load_error + ")"); |
| if (test_system_load_library) { |
| const std::string java_load_lib_error(ScopedUtfChars(env, java_load_lib_errmsg).c_str()); |
| error += ", System.loadLibrary() " + |
| (java_load_lib_ok ? "succeeded" : "failed (" + java_load_lib_error + ")"); |
| } |
| } |
| |
| if (loaded_in_native && java_load_ok) { |
| // Unload the shared lib loaded in Java. Since we don't have a method in Java for unloading a |
| // lib other than destroying the classloader, here comes a trick; we open the same library |
| // again with dlopen to get the handle for the lib and then calls dlclose twice (since we have |
| // opened the lib twice; once in Java, once in here). This works because dlopen returns the |
| // the same handle for the same shared lib object. |
| void* handle = dlopen(path.c_str(), RTLD_NOW); |
| dlclose(handle); |
| dlclose(handle); // don't delete this line. it's not a mistake (see comment above). |
| } |
| |
| return error; |
| } |
| |
| static bool check_lib(JNIEnv* env, |
| jclass clazz, |
| const std::string& path, |
| const std::unordered_set<std::string>& library_search_paths, |
| const std::unordered_set<std::string>& public_library_basenames, |
| bool test_system_load_library, |
| bool check_absence, |
| /*out*/ std::vector<std::string>* errors) { |
| std::string err = load_library(env, clazz, path, test_system_load_library); |
| bool loaded = err.empty(); |
| |
| // The current restrictions on public libraries: |
| // - It must exist only in the top level directory of "library_search_paths". |
| // - No library with the same name can be found in a sub directory. |
| // - Each public library does not contain any directory components. |
| |
| std::string baselib = basename(path.c_str()); |
| bool is_public = public_library_basenames.find(baselib) != public_library_basenames.end(); |
| |
| // Special casing for symlinks in APEXes. For bundled APEXes, some files in |
| // the APEXes could be symlinks pointing to libraries in /system/lib to save |
| // storage. In that case, use the realpath so that `is_in_search_path` is |
| // correctly determined |
| bool is_in_search_path; |
| std::string realpath; |
| if (android::base::StartsWith(path, "/apex/") && android::base::Realpath(path, &realpath)) { |
| is_in_search_path = is_library_on_path(library_search_paths, baselib, realpath); |
| } else { |
| is_in_search_path = is_library_on_path(library_search_paths, baselib, path); |
| } |
| |
| if (is_public) { |
| if (is_in_search_path) { |
| if (!loaded) { |
| errors->push_back("The library \"" + path + |
| "\" is a public library but it cannot be loaded: " + err); |
| return false; |
| } |
| } else { // !is_in_search_path |
| if (loaded) { |
| errors->push_back("The library \"" + path + |
| "\" is a public library that was loaded from a subdirectory."); |
| return false; |
| } |
| } |
| } else { // !is_public |
| // If the library loaded successfully but is in a subdirectory then it is |
| // still not public. That is the case e.g. for |
| // /apex/com.android.runtime/lib{,64}/bionic/lib*.so. |
| if (loaded && is_in_search_path && check_absence) { |
| errors->push_back("The library \"" + path + "\" is not a public library but it loaded."); |
| return false; |
| } |
| } |
| |
| if (!loaded && !not_accessible(err) && !not_found(err) && !wrong_arch(path, err)) { |
| errors->push_back("unexpected dlerror: " + err); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool check_path(JNIEnv* env, |
| jclass clazz, |
| const std::string& library_path, |
| const std::unordered_set<std::string>& library_search_paths, |
| const std::unordered_set<std::string>& public_library_basenames, |
| bool test_system_load_library, |
| bool check_absence, |
| /*out*/ std::vector<std::string>* errors) { |
| bool success = true; |
| std::queue<std::string> dirs; |
| dirs.push(library_path); |
| while (!dirs.empty()) { |
| std::string dir = dirs.front(); |
| dirs.pop(); |
| |
| std::unique_ptr<DIR, decltype(&closedir)> dirp(opendir(dir.c_str()), closedir); |
| if (dirp == nullptr) { |
| errors->push_back("Failed to open " + dir + ": " + strerror(errno)); |
| success = false; |
| continue; |
| } |
| |
| dirent* dp; |
| while ((dp = readdir(dirp.get())) != nullptr) { |
| // skip "." and ".." |
| if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) { |
| continue; |
| } |
| |
| std::string path = dir + "/" + dp->d_name; |
| if (is_directory(path.c_str())) { |
| dirs.push(path); |
| } else if (!check_lib(env, clazz, path, library_search_paths, public_library_basenames, |
| test_system_load_library, check_absence, errors)) { |
| success = false; |
| } |
| } |
| } |
| |
| return success; |
| } |
| |
| static bool jobject_array_to_set(JNIEnv* env, |
| jobjectArray java_libraries_array, |
| std::unordered_set<std::string>* libraries, |
| std::string* error_msg) { |
| error_msg->clear(); |
| size_t size = env->GetArrayLength(java_libraries_array); |
| bool success = true; |
| for (size_t i = 0; i<size; ++i) { |
| ScopedLocalRef<jstring> java_soname( |
| env, (jstring) env->GetObjectArrayElement(java_libraries_array, i)); |
| std::string soname(ScopedUtfChars(env, java_soname.get()).c_str()); |
| |
| // Verify that the name doesn't contain any directory components. |
| if (soname.rfind('/') != std::string::npos) { |
| *error_msg += "\n---Illegal value, no directories allowed: " + soname; |
| continue; |
| } |
| |
| // Check to see if the string ends in " 32" or " 64" to indicate the |
| // library is only public for one bitness. |
| size_t space_pos = soname.rfind(' '); |
| if (space_pos != std::string::npos) { |
| std::string type = soname.substr(space_pos + 1); |
| if (type != "32" && type != "64") { |
| *error_msg += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname; |
| success = false; |
| continue; |
| } |
| #if defined(__LP64__) |
| if (type == "32") { |
| // Skip this, it's a 32 bit only public library. |
| continue; |
| } |
| #else |
| if (type == "64") { |
| // Skip this, it's a 64 bit only public library. |
| continue; |
| } |
| #endif |
| soname.resize(space_pos); |
| } |
| |
| libraries->insert(soname); |
| } |
| |
| return success; |
| } |
| |
| // This is not public function but only known way to get search path of the default namespace. |
| extern "C" void android_get_LD_LIBRARY_PATH(char*, size_t) __attribute__((__weak__)); |
| |
| extern "C" JNIEXPORT jstring JNICALL |
| Java_android_jni_cts_LinkerNamespacesHelper_runAccessibilityTestImpl( |
| JNIEnv* env, |
| jclass clazz, |
| jobjectArray java_system_public_libraries, |
| jobjectArray java_runtime_public_libraries, |
| jobjectArray java_vendor_public_libraries, |
| jobjectArray java_product_public_libraries) { |
| bool success = true; |
| std::vector<std::string> errors; |
| std::string error_msg; |
| std::unordered_set<std::string> vendor_public_libraries; |
| if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries, |
| &error_msg)) { |
| success = false; |
| errors.push_back("Errors in vendor public library file:" + error_msg); |
| } |
| |
| std::unordered_set<std::string> system_public_libraries; |
| if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries, |
| &error_msg)) { |
| success = false; |
| errors.push_back("Errors in system public library file:" + error_msg); |
| } |
| |
| std::unordered_set<std::string> product_public_libraries; |
| if (!jobject_array_to_set(env, java_product_public_libraries, &product_public_libraries, |
| &error_msg)) { |
| success = false; |
| errors.push_back("Errors in product public library file:" + error_msg); |
| } |
| |
| std::unordered_set<std::string> runtime_public_libraries; |
| if (!jobject_array_to_set(env, java_runtime_public_libraries, &runtime_public_libraries, |
| &error_msg)) { |
| success = false; |
| errors.push_back("Errors in runtime public library file:" + error_msg); |
| } |
| |
| // Check the system libraries. |
| |
| // Check current search path and add the rest of search path configured for |
| // the default namepsace. |
| char default_search_paths[PATH_MAX]; |
| android_get_LD_LIBRARY_PATH(default_search_paths, sizeof(default_search_paths)); |
| |
| std::vector<std::string> library_search_paths = android::base::Split(default_search_paths, ":"); |
| |
| // Remove everything pointing outside of /system/lib* and |
| // /apex/com.android.*/lib*. |
| std::unordered_set<std::string> system_library_search_paths; |
| |
| for (const auto& path : library_search_paths) { |
| for (const auto& regex : kSystemPathRegexes) { |
| if (std::regex_match(path, regex)) { |
| system_library_search_paths.insert(path); |
| break; |
| } |
| } |
| } |
| |
| // These paths should be tested too - this is because apps may rely on some |
| // libraries being available there. |
| system_library_search_paths.insert(kSystemLibraryPath); |
| system_library_search_paths.insert(kArtApexLibraryPath); |
| |
| if (!check_path(env, clazz, kSystemLibraryPath, system_library_search_paths, |
| system_public_libraries, |
| /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) { |
| success = false; |
| } |
| |
| // Pre-Treble devices use ld.config.vndk_lite.txt, where the default namespace |
| // isn't isolated. That means it can successfully load libraries in /apex, so |
| // don't complain about that in that case. |
| bool check_absence = !android::base::GetBoolProperty("ro.vndk.lite", false); |
| |
| // Check the runtime libraries. |
| if (!check_path(env, clazz, kArtApexLibraryPath, {kArtApexLibraryPath}, |
| runtime_public_libraries, |
| // System.loadLibrary("icuuc") would fail since a copy exists in /system. |
| // TODO(b/124218500): Change to true when the bug is resolved. |
| /*test_system_load_library=*/true, |
| check_absence, &errors)) { |
| success = false; |
| } |
| |
| // Check the product libraries, if /product/lib exists. |
| if (is_directory(kProductLibraryPath.c_str())) { |
| if (!check_path(env, clazz, kProductLibraryPath, {kProductLibraryPath}, |
| product_public_libraries, |
| /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) { |
| success = false; |
| } |
| } |
| |
| // Check the vendor libraries. |
| if (!check_path(env, clazz, kVendorLibraryPath, {kVendorLibraryPath}, vendor_public_libraries, |
| /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) { |
| success = false; |
| } |
| |
| if (!success) { |
| std::string error_str; |
| for (const auto& line : errors) { |
| error_str += line + '\n'; |
| } |
| return env->NewStringUTF(error_str.c_str()); |
| } |
| |
| return nullptr; |
| } |
| |
| extern "C" JNIEXPORT jstring JNICALL Java_android_jni_cts_LinkerNamespacesHelper_tryDlopen( |
| JNIEnv* env, |
| jclass clazz, |
| jstring lib) { |
| ScopedUtfChars soname(env, lib); |
| std::string error_str = try_dlopen(soname.c_str()); |
| |
| if (!error_str.empty()) { |
| return env->NewStringUTF(error_str.c_str()); |
| } |
| return nullptr; |
| } |
| |
| extern "C" JNIEXPORT jint JNICALL Java_android_jni_cts_LinkerNamespacesHelper_getLibAbi( |
| JNIEnv* env, |
| jclass clazz) { |
| #ifdef __aarch64__ |
| return 1; // ARM64 |
| #elif __arm__ |
| return 2; |
| #elif __x86_64__ |
| return 3; |
| #elif i386 |
| return 4; |
| #else |
| return 0; |
| #endif |
| } |