xref: /aosp_15_r20/cts/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * Tests accessibility of platform native libraries
19  */
20 
21 #include <dirent.h>
22 #include <dlfcn.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <jni.h>
26 #include <libgen.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/stat.h>
30 #include <sys/system_properties.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33 
34 #include <queue>
35 #include <regex>
36 #include <string>
37 #include <unordered_set>
38 #include <vector>
39 
40 #include "android-base/file.h"
41 #include "android-base/macros.h"
42 #include "android-base/properties.h"
43 #include "android-base/strings.h"
44 #include "nativehelper/scoped_local_ref.h"
45 #include "nativehelper/scoped_utf_chars.h"
46 
47 #if defined(__LP64__)
48 #define LIB_DIR "lib64"
49 #else
50 #define LIB_DIR "lib"
51 #endif
52 
53 static const std::string kSystemLibraryPath = "/system/" LIB_DIR;
54 
55 // APEX library paths to check for either presence or absence of public
56 // libraries.
57 static const std::vector<std::string> kApexLibraryPaths = {
58   "/apex/com.android.art/" LIB_DIR,
59   "/apex/com.android.i18n/" LIB_DIR,
60   "/apex/com.android.neuralnetworks/" LIB_DIR,
61   "/apex/com.android.runtime/" LIB_DIR,
62 };
63 
64 static const std::vector<std::regex> kSystemPathRegexes = {
65     std::regex("/system/lib(64)?"),
66     std::regex("/apex/com\\.android\\.[^/]*/lib(64)?"),
67     std::regex("/system/(lib/arm|lib64/arm64)"), // when CTS runs in ARM ABI on non-ARM CPU. http://b/149852946
68 };
69 
70 // Full paths to libraries in system or APEX search paths that are not public
71 // but still may or may not be possible to load in an app.
72 static const std::vector<std::string> kOtherLoadableLibrariesInSearchPaths = {
73   // This library may be loaded using DF_1_GLOBAL into the global group in
74   // app_process, which is necessary to make it override some symbols in libc in
75   // all DSO's. As a side effect it also gets inherited into the classloader
76   // namespaces constructed in libnativeloader, and is hence possible to dlopen
77   // even though there is no linker namespace link for it.
78   "/apex/com.android.art/" LIB_DIR "/libsigchain.so",
79 };
80 
81 static const std::string kWebViewPlatSupportLib = "libwebviewchromium_plat_support.so";
82 
running_with_native_bridge()83 static inline bool running_with_native_bridge() {
84   static const prop_info* pi = __system_property_find("ro.dalvik.vm.isa." ABI_STRING);
85   return pi != nullptr;
86 }
87 
not_accessible(const std::string & err)88 static bool not_accessible(const std::string& err) {
89   return err.find("dlopen failed: library \"") == 0 &&
90          err.find("is not accessible for the namespace \"") != std::string::npos;
91 }
92 
not_found(const std::string & err)93 static bool not_found(const std::string& err) {
94   return err.find("dlopen failed: library \"") == 0 &&
95          err.find("\" not found") != std::string::npos;
96 }
97 
wrong_arch(const std::string & library,const std::string & err)98 static bool wrong_arch(const std::string& library, const std::string& err) {
99   // https://issuetracker.google.com/37428428
100   // It's okay to not be able to load a library because it's for another
101   // architecture (typically on an x86 device, when we come across an arm library).
102   return err.find("dlopen failed: \"" + library + "\" has unexpected e_machine: ") == 0;
103 }
104 
is_library_on_path(const std::unordered_set<std::string> & library_search_paths,const std::string & baselib,const std::string & path)105 static bool is_library_on_path(const std::unordered_set<std::string>& library_search_paths,
106                                const std::string& baselib,
107                                const std::string& path) {
108   std::string tail = '/' + baselib;
109   if (!android::base::EndsWith(path, tail)) return false;
110   return library_search_paths.count(path.substr(0, path.size() - tail.size())) > 0;
111 }
112 
try_dlopen(const std::string & path)113 static std::string try_dlopen(const std::string& path) {
114   // try to load the lib using dlopen().
115   void *handle = dlopen(path.c_str(), RTLD_NOW);
116   std::string error;
117 
118   bool loaded_in_native = handle != nullptr;
119   if (loaded_in_native) {
120     dlclose(handle);
121   } else {
122     error = dlerror();
123   }
124   return error;
125 }
126 
127 // Tests if a file can be loaded or not. Returns empty string on success. On any failure
128 // returns the error message from dlerror().
load_library(JNIEnv * env,jclass clazz,const std::string & path,bool test_system_load_library)129 static std::string load_library(JNIEnv* env, jclass clazz, const std::string& path,
130                                 bool test_system_load_library) {
131   std::string error = try_dlopen(path);
132   bool loaded_in_native = error.empty();
133 
134   if (android::base::EndsWith(path, '/' + kWebViewPlatSupportLib)) {
135     // Don't try to load this library from Java. Otherwise, the lib is initialized via
136     // JNI_OnLoad and it fails since WebView is not loaded in this test process.
137     return error;
138   }
139 
140   // try to load the same lib using System.load() in Java to see if it gives consistent
141   // result with dlopen.
142   static jmethodID java_load =
143       env->GetStaticMethodID(clazz, "loadWithSystemLoad", "(Ljava/lang/String;)Ljava/lang/String;");
144   ScopedLocalRef<jstring> jpath(env, env->NewStringUTF(path.c_str()));
145   jstring java_load_errmsg = jstring(env->CallStaticObjectMethod(clazz, java_load, jpath.get()));
146   bool java_load_ok = env->GetStringLength(java_load_errmsg) == 0;
147 
148   jstring java_load_lib_errmsg;
149   bool java_load_lib_ok = java_load_ok;
150   if (test_system_load_library && java_load_ok) {
151     // If System.load() works then test System.loadLibrary() too. Cannot test
152     // the other way around since System.loadLibrary() might very well find the
153     // library somewhere else and hence work when System.load() fails.
154     std::string baselib = basename(path.c_str());
155     ScopedLocalRef<jstring> jname(env, env->NewStringUTF(baselib.c_str()));
156     static jmethodID java_load_lib = env->GetStaticMethodID(
157         clazz, "loadWithSystemLoadLibrary", "(Ljava/lang/String;)Ljava/lang/String;");
158     java_load_lib_errmsg = jstring(env->CallStaticObjectMethod(clazz, java_load_lib, jname.get()));
159     java_load_lib_ok = env->GetStringLength(java_load_lib_errmsg) == 0;
160   }
161 
162   // When running with native bridge it is allowed to be able to open both device host and guest
163   // libraries from java, while native dlopen doesn't allow opening device host libraries from
164   // the device guest code.
165   if ((!running_with_native_bridge() && loaded_in_native != java_load_ok) ||
166       java_load_ok != java_load_lib_ok) {
167     const std::string java_load_error(ScopedUtfChars(env, java_load_errmsg).c_str());
168     error = "Inconsistent result for library \"" + path + "\": dlopen() " +
169             (loaded_in_native ? "succeeded" : "failed (" + error + ")") + ", System.load() " +
170             (java_load_ok ? "succeeded" : "failed (" + java_load_error + ")");
171     if (test_system_load_library) {
172       const std::string java_load_lib_error(ScopedUtfChars(env, java_load_lib_errmsg).c_str());
173       error += ", System.loadLibrary() " +
174                (java_load_lib_ok ? "succeeded" : "failed (" + java_load_lib_error + ")");
175     }
176   }
177 
178   if (loaded_in_native && java_load_ok) {
179     // Unload the shared lib loaded in Java. Since we don't have a method in Java for unloading a
180     // lib other than destroying the classloader, here comes a trick; we open the same library
181     // again with dlopen to get the handle for the lib and then calls dlclose twice (since we have
182     // opened the lib twice; once in Java, once in here). This works because dlopen returns the
183     // the same handle for the same shared lib object.
184     void* handle = dlopen(path.c_str(), RTLD_NOW);
185     dlclose(handle);
186     dlclose(handle); // don't delete this line. it's not a mistake (see comment above).
187   }
188 
189   return error;
190 }
191 
skip_subdir_load_check(const std::string & path)192 static bool skip_subdir_load_check(const std::string& path) {
193   static bool vndk_lite = android::base::GetBoolProperty("ro.vndk.lite", false);
194   static const std::string system_vndk_dir = kSystemLibraryPath + "/vndk-sp-";
195   return vndk_lite && android::base::StartsWith(path, system_vndk_dir);
196 }
197 
198 // Checks that a .so library can or cannot be loaded with dlopen() and
199 // System.load(), as appropriate by the other settings:
200 // -  clazz: The java class instance of android.jni.cts.LinkerNamespacesHelper,
201 //    used for calling System.load() and System.loadLibrary().
202 // -  path: Full path to the library to load.
203 // -  library_search_paths: Directories that should be searched for public
204 //    libraries. They should not be loaded from a subdirectory of these.
205 // -  public_library_basenames: File names without paths of expected public
206 //    libraries.
207 // -  test_system_load_library: Try loading with System.loadLibrary() as well.
208 // -  check_absence: Raise an error if it is a non-public library but still is
209 //    loaded successfully from a searched directory.
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,std::vector<std::string> * errors)210 static bool check_lib(JNIEnv* env,
211                       jclass clazz,
212                       const std::string& path,
213                       const std::unordered_set<std::string>& library_search_paths,
214                       const std::unordered_set<std::string>& public_library_basenames,
215                       bool test_system_load_library,
216                       bool check_absence,
217                       /*out*/ std::vector<std::string>* errors) {
218   std::string err = load_library(env, clazz, path, test_system_load_library);
219   bool loaded = err.empty();
220 
221   // The current restrictions on public libraries:
222   //  - It must exist only in the top level directory of "library_search_paths".
223   //  - No library with the same name can be found in a sub directory.
224   //  - Each public library does not contain any directory components.
225 
226   std::string baselib = basename(path.c_str());
227   bool is_public = public_library_basenames.find(baselib) != public_library_basenames.end();
228 
229   // Special casing for symlinks in APEXes. For bundled APEXes, some files in
230   // the APEXes could be symlinks pointing to libraries in /system/lib to save
231   // storage. In that case, use the realpath so that `is_in_search_path` is
232   // correctly determined
233   bool is_in_search_path;
234   std::string realpath;
235   if (android::base::StartsWith(path, "/apex/") && android::base::Realpath(path, &realpath)) {
236     is_in_search_path = is_library_on_path(library_search_paths, baselib, realpath);
237   } else {
238     is_in_search_path = is_library_on_path(library_search_paths, baselib, path);
239   }
240 
241   if (is_public) {
242     if (is_in_search_path) {
243       if (!loaded) {
244         errors->push_back("The library \"" + path +
245                           "\" is a public library but it cannot be loaded: " + err);
246         return false;
247       }
248     } else {  // !is_in_search_path
249       if (loaded && !skip_subdir_load_check(path)) {
250         errors->push_back("The library \"" + path +
251                           "\" is a public library that was loaded from a subdirectory.");
252         return false;
253       }
254     }
255   } else {  // !is_public
256     // If the library loaded successfully but is in a subdirectory then it is
257     // still not public. That is the case e.g. for
258     // /apex/com.android.runtime/lib{,64}/bionic/lib*.so.
259     if (loaded && is_in_search_path && check_absence &&
260         (std::find(kOtherLoadableLibrariesInSearchPaths.begin(),
261                    kOtherLoadableLibrariesInSearchPaths.end(), path) ==
262          kOtherLoadableLibrariesInSearchPaths.end())) {
263       errors->push_back("The library \"" + path + "\" is not a public library but it loaded.");
264       return false;
265     }
266   }
267 
268   if (!loaded && !not_accessible(err) && !not_found(err) && !wrong_arch(path, err)) {
269     errors->push_back("unexpected dlerror: " + err);
270     return false;
271   }
272 
273   return true;
274 }
275 
276 // Calls check_lib for every file found recursively within library_path.
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,std::vector<std::string> * errors)277 static bool check_path(JNIEnv* env,
278                        jclass clazz,
279                        const std::string& library_path,
280                        const std::unordered_set<std::string>& library_search_paths,
281                        const std::unordered_set<std::string>& public_library_basenames,
282                        bool test_system_load_library,
283                        bool check_absence,
284                        /*out*/ std::vector<std::string>* errors) {
285   bool success = true;
286   std::queue<std::string> dirs;
287   dirs.push(library_path);
288   while (!dirs.empty()) {
289     std::string dir = dirs.front();
290     dirs.pop();
291 
292     std::unique_ptr<DIR, decltype(&closedir)> dirp(opendir(dir.c_str()), closedir);
293     if (dirp == nullptr) {
294       errors->push_back("Failed to open " + dir + ": " + strerror(errno));
295       success = false;
296       continue;
297     }
298 
299     dirent* dp;
300     while ((dp = readdir(dirp.get())) != nullptr) {
301       // skip "." and ".."
302       if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) {
303         continue;
304       }
305 
306       std::string path = dir + "/" + dp->d_name;
307       // We cannot just load hwasan libraries into a non-hwasan process, so
308       // we are skipping those.
309       if (path.find("hwasan") != std::string::npos) {
310         continue;
311       }
312       struct stat sb;
313       // Use lstat to not dereference a symlink. If it links out of library_path
314       // it can be ignored because the Bionic linker derefences symlinks before
315       // checking the path. If it links inside library_path we'll get to the
316       // link target anyway.
317       if (lstat(path.c_str(), &sb) != -1) {
318         if (S_ISDIR(sb.st_mode)) {
319           dirs.push(path);
320         } else if (!S_ISLNK(sb.st_mode) &&
321                    !check_lib(env, clazz, path, library_search_paths, public_library_basenames,
322                               test_system_load_library, check_absence, errors)) {
323           success = false;
324         }
325       }
326     }
327   }
328 
329   return success;
330 }
331 
jobject_array_to_set(JNIEnv * env,jobjectArray java_libraries_array,std::unordered_set<std::string> * libraries,std::string * error_msgs)332 static bool jobject_array_to_set(JNIEnv* env,
333                                  jobjectArray java_libraries_array,
334                                  std::unordered_set<std::string>* libraries,
335                                  std::string* error_msgs) {
336   error_msgs->clear();
337   size_t size = env->GetArrayLength(java_libraries_array);
338   bool success = true;
339   for (size_t i = 0; i<size; ++i) {
340     ScopedLocalRef<jstring> java_soname(
341         env, (jstring) env->GetObjectArrayElement(java_libraries_array, i));
342     std::string soname(ScopedUtfChars(env, java_soname.get()).c_str());
343 
344     // Verify that the name doesn't contain any directory components.
345     if (soname.rfind('/') != std::string::npos) {
346       *error_msgs += "\n---Illegal value, no directories allowed: " + soname;
347       continue;
348     }
349 
350     // Check to see if the string ends in " 32" or " 64" to indicate the
351     // library is only public for one bitness.
352     size_t space_pos = soname.rfind(' ');
353     if (space_pos != std::string::npos) {
354       std::string type = soname.substr(space_pos + 1);
355       if (type != "32" && type != "64") {
356         *error_msgs += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname;
357         success = false;
358         continue;
359       }
360 #if defined(__LP64__)
361       if (type == "32") {
362         // Skip this, it's a 32 bit only public library.
363         continue;
364       }
365 #else
366       if (type == "64") {
367         // Skip this, it's a 64 bit only public library.
368         continue;
369       }
370 #endif
371       soname.resize(space_pos);
372     }
373 
374     libraries->insert(soname);
375   }
376 
377   return success;
378 }
379 
380 // This is not public function but only known way to get search path of the default namespace.
381 extern "C" void android_get_LD_LIBRARY_PATH(char*, size_t) __attribute__((__weak__));
382 
383 extern "C" JNIEXPORT jstring JNICALL
Java_android_jni_cts_LinkerNamespacesHelper_runAccessibilityTestImpl(JNIEnv * env,jclass clazz,jobjectArray java_system_public_libraries,jobjectArray java_apex_public_libraries)384     Java_android_jni_cts_LinkerNamespacesHelper_runAccessibilityTestImpl(
385         JNIEnv* env,
386         jclass clazz,
387         jobjectArray java_system_public_libraries,
388         jobjectArray java_apex_public_libraries) {
389   bool success = true;
390   std::vector<std::string> errors;
391   std::string error_msgs;
392   std::unordered_set<std::string> system_public_libraries;
393   if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries,
394                             &error_msgs)) {
395     success = false;
396     errors.push_back("Errors in system public library list:" + error_msgs);
397   }
398   std::unordered_set<std::string> apex_public_libraries;
399   if (!jobject_array_to_set(env, java_apex_public_libraries, &apex_public_libraries,
400                             &error_msgs)) {
401     success = false;
402     errors.push_back("Errors in APEX public library list:" + error_msgs);
403   }
404 
405   // Check the system libraries.
406 
407   // Check current search path and add the rest of search path configured for
408   // the default namepsace.
409   char default_search_paths[PATH_MAX];
410   android_get_LD_LIBRARY_PATH(default_search_paths, sizeof(default_search_paths));
411 
412   std::vector<std::string> library_search_paths = android::base::Split(default_search_paths, ":");
413 
414   // Remove everything pointing outside of /system/lib* and
415   // /apex/com.android.*/lib*.
416   std::unordered_set<std::string> system_library_search_paths;
417 
418   for (const std::string& path : library_search_paths) {
419     for (const std::regex& regex : kSystemPathRegexes) {
420       if (std::regex_match(path, regex)) {
421         system_library_search_paths.insert(path);
422         break;
423       }
424     }
425   }
426 
427   // These paths should be tested too - this is because apps may rely on some
428   // libraries being available there.
429   system_library_search_paths.insert(kSystemLibraryPath);
430   system_library_search_paths.insert(kApexLibraryPaths.begin(), kApexLibraryPaths.end());
431 
432   if (!check_path(env, clazz, kSystemLibraryPath, system_library_search_paths,
433                   system_public_libraries,
434                   /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
435     success = false;
436   }
437 
438   // Pre-Treble devices use ld.config.vndk_lite.txt, where the default namespace
439   // isn't isolated. That means it can successfully load libraries in /apex, so
440   // don't complain about that in that case.
441   bool check_absence = !android::base::GetBoolProperty("ro.vndk.lite", false);
442 
443   // Check the APEX libraries.
444   for (const std::string& apex_path : kApexLibraryPaths) {
445     if (!check_path(env, clazz, apex_path, {apex_path},
446                     apex_public_libraries,
447                     /*test_system_load_library=*/true,
448                     check_absence, &errors)) {
449       success = false;
450     }
451   }
452 
453   if (!success) {
454     std::string error_str;
455     for (const std::string& line : errors) {
456       error_str += line + '\n';
457     }
458     return env->NewStringUTF(error_str.c_str());
459   }
460 
461   return nullptr;
462 }
463 
Java_android_jni_cts_LinkerNamespacesHelper_tryDlopen(JNIEnv * env,jclass clazz,jstring lib)464 extern "C" JNIEXPORT jstring JNICALL Java_android_jni_cts_LinkerNamespacesHelper_tryDlopen(
465         JNIEnv* env,
466         jclass clazz,
467         jstring lib) {
468     ScopedUtfChars soname(env, lib);
469     std::string error_str = try_dlopen(soname.c_str());
470 
471     if (!error_str.empty()) {
472       return env->NewStringUTF(error_str.c_str());
473     }
474     return nullptr;
475 }
476 
Java_android_jni_cts_LinkerNamespacesHelper_getLibAbi(JNIEnv * env,jclass clazz)477 extern "C" JNIEXPORT jint JNICALL Java_android_jni_cts_LinkerNamespacesHelper_getLibAbi(
478         JNIEnv* env,
479         jclass clazz) {
480 #ifdef __aarch64__
481     return 1; // ARM64
482 #elif __arm__
483     return 2;
484 #elif __x86_64__
485     return 3;
486 #elif i386
487     return 4;
488 #else
489     return 0;
490 #endif
491 }
492