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 <fcntl.h>
24 #include <jni.h>
25 #include <libgen.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 
31 #include <queue>
32 #include <regex>
33 #include <string>
34 #include <unordered_set>
35 #include <vector>
36 
37 #include <android-base/file.h>
38 #include <android-base/properties.h>
39 #include <android-base/strings.h>
40 #include <nativehelper/JNIHelp.h>
41 #include <nativehelper/ScopedLocalRef.h>
42 #include <nativehelper/ScopedUtfChars.h>
43 
44 #if defined(__LP64__)
45 #define LIB_DIR "lib64"
46 #else
47 #define LIB_DIR "lib"
48 #endif
49 
50 static const std::string kSystemLibraryPath = "/system/" LIB_DIR;
51 static const std::string kArtApexLibraryPath = "/apex/com.android.art/" LIB_DIR;
52 static const std::string kVendorLibraryPath = "/vendor/" LIB_DIR;
53 static const std::string kProductLibraryPath = "/product/" LIB_DIR;
54 
55 static const std::vector<std::regex> kSystemPathRegexes = {
56     std::regex("/system/lib(64)?"),
57     std::regex("/apex/com\\.android\\.[^/]*/lib(64)?"),
58     std::regex("/system/lib/arm(64)?"), // when CTS runs in ARM ABI on non-ARM CPU. http://b/149852946
59 };
60 
61 static const std::string kWebViewPlatSupportLib = "libwebviewchromium_plat_support.so";
62 
is_directory(const char * path)63 static bool is_directory(const char* path) {
64   struct stat sb;
65   if (stat(path, &sb) != -1) {
66     return S_ISDIR(sb.st_mode);
67   }
68 
69   return false;
70 }
71 
not_accessible(const std::string & err)72 static bool not_accessible(const std::string& err) {
73   return err.find("dlopen failed: library \"") == 0 &&
74          err.find("is not accessible for the namespace \"classloader-namespace\"") != std::string::npos;
75 }
76 
not_found(const std::string & err)77 static bool not_found(const std::string& err) {
78   return err.find("dlopen failed: library \"") == 0 &&
79          err.find("\" not found") != std::string::npos;
80 }
81 
wrong_arch(const std::string & library,const std::string & err)82 static bool wrong_arch(const std::string& library, const std::string& err) {
83   // https://issuetracker.google.com/37428428
84   // It's okay to not be able to load a library because it's for another
85   // architecture (typically on an x86 device, when we come across an arm library).
86   return err.find("dlopen failed: \"" + library + "\" has unexpected e_machine: ") == 0;
87 }
88 
is_library_on_path(const std::unordered_set<std::string> & library_search_paths,const std::string & baselib,const std::string & path)89 static bool is_library_on_path(const std::unordered_set<std::string>& library_search_paths,
90                                const std::string& baselib,
91                                const std::string& path) {
92   std::string tail = '/' + baselib;
93   if (!android::base::EndsWith(path, tail)) return false;
94   return library_search_paths.count(path.substr(0, path.size() - tail.size())) > 0;
95 }
96 
try_dlopen(const std::string & path)97 static std::string try_dlopen(const std::string& path) {
98   // try to load the lib using dlopen().
99   void *handle = dlopen(path.c_str(), RTLD_NOW);
100   std::string error;
101 
102   bool loaded_in_native = handle != nullptr;
103   if (loaded_in_native) {
104     dlclose(handle);
105   } else {
106     error = dlerror();
107   }
108   return error;
109 }
110 
111 // Tests if a file can be loaded or not. Returns empty string on success. On any failure
112 // returns the error message from dlerror().
load_library(JNIEnv * env,jclass clazz,const std::string & path,bool test_system_load_library)113 static std::string load_library(JNIEnv* env, jclass clazz, const std::string& path,
114                                 bool test_system_load_library) {
115   std::string error = try_dlopen(path);
116   bool loaded_in_native = error.empty();
117 
118   if (android::base::EndsWith(path, '/' + kWebViewPlatSupportLib)) {
119     // Don't try to load this library from Java. Otherwise, the lib is initialized via
120     // JNI_OnLoad and it fails since WebView is not loaded in this test process.
121     return error;
122   }
123 
124   // try to load the same lib using System.load() in Java to see if it gives consistent
125   // result with dlopen.
126   static jmethodID java_load =
127       env->GetStaticMethodID(clazz, "loadWithSystemLoad", "(Ljava/lang/String;)Ljava/lang/String;");
128   ScopedLocalRef<jstring> jpath(env, env->NewStringUTF(path.c_str()));
129   jstring java_load_errmsg = jstring(env->CallStaticObjectMethod(clazz, java_load, jpath.get()));
130   bool java_load_ok = env->GetStringLength(java_load_errmsg) == 0;
131 
132   jstring java_load_lib_errmsg;
133   bool java_load_lib_ok = java_load_ok;
134   if (test_system_load_library && java_load_ok) {
135     // If System.load() works then test System.loadLibrary() too. Cannot test
136     // the other way around since System.loadLibrary() might very well find the
137     // library somewhere else and hence work when System.load() fails.
138     std::string baselib = basename(path.c_str());
139     ScopedLocalRef<jstring> jname(env, env->NewStringUTF(baselib.c_str()));
140     static jmethodID java_load_lib = env->GetStaticMethodID(
141         clazz, "loadWithSystemLoadLibrary", "(Ljava/lang/String;)Ljava/lang/String;");
142     java_load_lib_errmsg = jstring(env->CallStaticObjectMethod(clazz, java_load_lib, jname.get()));
143     java_load_lib_ok = env->GetStringLength(java_load_lib_errmsg) == 0;
144   }
145 
146   if (loaded_in_native != java_load_ok || java_load_ok != java_load_lib_ok) {
147     const std::string java_load_error(ScopedUtfChars(env, java_load_errmsg).c_str());
148     error = "Inconsistent result for library \"" + path + "\": dlopen() " +
149             (loaded_in_native ? "succeeded" : "failed (" + error + ")") +
150             ", System.load() " +
151             (java_load_ok ? "succeeded" : "failed (" + java_load_error + ")");
152     if (test_system_load_library) {
153       const std::string java_load_lib_error(ScopedUtfChars(env, java_load_lib_errmsg).c_str());
154       error += ", System.loadLibrary() " +
155                (java_load_lib_ok ? "succeeded" : "failed (" + java_load_lib_error + ")");
156     }
157   }
158 
159   if (loaded_in_native && java_load_ok) {
160     // Unload the shared lib loaded in Java. Since we don't have a method in Java for unloading a
161     // lib other than destroying the classloader, here comes a trick; we open the same library
162     // again with dlopen to get the handle for the lib and then calls dlclose twice (since we have
163     // opened the lib twice; once in Java, once in here). This works because dlopen returns the
164     // the same handle for the same shared lib object.
165     void* handle = dlopen(path.c_str(), RTLD_NOW);
166     dlclose(handle);
167     dlclose(handle); // don't delete this line. it's not a mistake (see comment above).
168   }
169 
170   return error;
171 }
172 
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)173 static bool check_lib(JNIEnv* env,
174                       jclass clazz,
175                       const std::string& path,
176                       const std::unordered_set<std::string>& library_search_paths,
177                       const std::unordered_set<std::string>& public_library_basenames,
178                       bool test_system_load_library,
179                       bool check_absence,
180                       /*out*/ std::vector<std::string>* errors) {
181   std::string err = load_library(env, clazz, path, test_system_load_library);
182   bool loaded = err.empty();
183 
184   // The current restrictions on public libraries:
185   //  - It must exist only in the top level directory of "library_search_paths".
186   //  - No library with the same name can be found in a sub directory.
187   //  - Each public library does not contain any directory components.
188 
189   std::string baselib = basename(path.c_str());
190   bool is_public = public_library_basenames.find(baselib) != public_library_basenames.end();
191 
192   // Special casing for symlinks in APEXes. For bundled APEXes, some files in
193   // the APEXes could be symlinks pointing to libraries in /system/lib to save
194   // storage. In that case, use the realpath so that `is_in_search_path` is
195   // correctly determined
196   bool is_in_search_path;
197   std::string realpath;
198   if (android::base::StartsWith(path, "/apex/") && android::base::Realpath(path, &realpath)) {
199     is_in_search_path = is_library_on_path(library_search_paths, baselib, realpath);
200   } else {
201     is_in_search_path = is_library_on_path(library_search_paths, baselib, path);
202   }
203 
204   if (is_public) {
205     if (is_in_search_path) {
206       if (!loaded) {
207         errors->push_back("The library \"" + path +
208                           "\" is a public library but it cannot be loaded: " + err);
209         return false;
210       }
211     } else {  // !is_in_search_path
212       if (loaded) {
213         errors->push_back("The library \"" + path +
214                           "\" is a public library that was loaded from a subdirectory.");
215         return false;
216       }
217     }
218   } else {  // !is_public
219     // If the library loaded successfully but is in a subdirectory then it is
220     // still not public. That is the case e.g. for
221     // /apex/com.android.runtime/lib{,64}/bionic/lib*.so.
222     if (loaded && is_in_search_path && check_absence) {
223       errors->push_back("The library \"" + path + "\" is not a public library but it loaded.");
224       return false;
225     }
226   }
227 
228   if (!loaded && !not_accessible(err) && !not_found(err) && !wrong_arch(path, err)) {
229     errors->push_back("unexpected dlerror: " + err);
230     return false;
231   }
232 
233   return true;
234 }
235 
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)236 static bool check_path(JNIEnv* env,
237                        jclass clazz,
238                        const std::string& library_path,
239                        const std::unordered_set<std::string>& library_search_paths,
240                        const std::unordered_set<std::string>& public_library_basenames,
241                        bool test_system_load_library,
242                        bool check_absence,
243                        /*out*/ std::vector<std::string>* errors) {
244   bool success = true;
245   std::queue<std::string> dirs;
246   dirs.push(library_path);
247   while (!dirs.empty()) {
248     std::string dir = dirs.front();
249     dirs.pop();
250 
251     std::unique_ptr<DIR, decltype(&closedir)> dirp(opendir(dir.c_str()), closedir);
252     if (dirp == nullptr) {
253       errors->push_back("Failed to open " + dir + ": " + strerror(errno));
254       success = false;
255       continue;
256     }
257 
258     dirent* dp;
259     while ((dp = readdir(dirp.get())) != nullptr) {
260       // skip "." and ".."
261       if (strcmp(".", dp->d_name) == 0 || strcmp("..", dp->d_name) == 0) {
262         continue;
263       }
264 
265       std::string path = dir + "/" + dp->d_name;
266       if (is_directory(path.c_str())) {
267         dirs.push(path);
268       } else if (!check_lib(env, clazz, path, library_search_paths, public_library_basenames,
269                             test_system_load_library, check_absence, errors)) {
270         success = false;
271       }
272     }
273   }
274 
275   return success;
276 }
277 
jobject_array_to_set(JNIEnv * env,jobjectArray java_libraries_array,std::unordered_set<std::string> * libraries,std::string * error_msg)278 static bool jobject_array_to_set(JNIEnv* env,
279                                  jobjectArray java_libraries_array,
280                                  std::unordered_set<std::string>* libraries,
281                                  std::string* error_msg) {
282   error_msg->clear();
283   size_t size = env->GetArrayLength(java_libraries_array);
284   bool success = true;
285   for (size_t i = 0; i<size; ++i) {
286     ScopedLocalRef<jstring> java_soname(
287         env, (jstring) env->GetObjectArrayElement(java_libraries_array, i));
288     std::string soname(ScopedUtfChars(env, java_soname.get()).c_str());
289 
290     // Verify that the name doesn't contain any directory components.
291     if (soname.rfind('/') != std::string::npos) {
292       *error_msg += "\n---Illegal value, no directories allowed: " + soname;
293       continue;
294     }
295 
296     // Check to see if the string ends in " 32" or " 64" to indicate the
297     // library is only public for one bitness.
298     size_t space_pos = soname.rfind(' ');
299     if (space_pos != std::string::npos) {
300       std::string type = soname.substr(space_pos + 1);
301       if (type != "32" && type != "64") {
302         *error_msg += "\n---Illegal value at end of line (only 32 or 64 allowed): " + soname;
303         success = false;
304         continue;
305       }
306 #if defined(__LP64__)
307       if (type == "32") {
308         // Skip this, it's a 32 bit only public library.
309         continue;
310       }
311 #else
312       if (type == "64") {
313         // Skip this, it's a 64 bit only public library.
314         continue;
315       }
316 #endif
317       soname.resize(space_pos);
318     }
319 
320     libraries->insert(soname);
321   }
322 
323   return success;
324 }
325 
326 // This is not public function but only known way to get search path of the default namespace.
327 extern "C" void android_get_LD_LIBRARY_PATH(char*, size_t) __attribute__((__weak__));
328 
329 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)330     Java_android_jni_cts_LinkerNamespacesHelper_runAccessibilityTestImpl(
331         JNIEnv* env,
332         jclass clazz,
333         jobjectArray java_system_public_libraries,
334         jobjectArray java_runtime_public_libraries,
335         jobjectArray java_vendor_public_libraries,
336         jobjectArray java_product_public_libraries) {
337   bool success = true;
338   std::vector<std::string> errors;
339   std::string error_msg;
340   std::unordered_set<std::string> vendor_public_libraries;
341   if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries,
342                             &error_msg)) {
343     success = false;
344     errors.push_back("Errors in vendor public library file:" + error_msg);
345   }
346 
347   std::unordered_set<std::string> system_public_libraries;
348   if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries,
349                             &error_msg)) {
350     success = false;
351     errors.push_back("Errors in system public library file:" + error_msg);
352   }
353 
354   std::unordered_set<std::string> product_public_libraries;
355   if (!jobject_array_to_set(env, java_product_public_libraries, &product_public_libraries,
356                             &error_msg)) {
357     success = false;
358     errors.push_back("Errors in product public library file:" + error_msg);
359   }
360 
361   std::unordered_set<std::string> runtime_public_libraries;
362   if (!jobject_array_to_set(env, java_runtime_public_libraries, &runtime_public_libraries,
363                             &error_msg)) {
364     success = false;
365     errors.push_back("Errors in runtime public library file:" + error_msg);
366   }
367 
368   // Check the system libraries.
369 
370   // Check current search path and add the rest of search path configured for
371   // the default namepsace.
372   char default_search_paths[PATH_MAX];
373   android_get_LD_LIBRARY_PATH(default_search_paths, sizeof(default_search_paths));
374 
375   std::vector<std::string> library_search_paths = android::base::Split(default_search_paths, ":");
376 
377   // Remove everything pointing outside of /system/lib* and
378   // /apex/com.android.*/lib*.
379   std::unordered_set<std::string> system_library_search_paths;
380 
381   for (const auto& path : library_search_paths) {
382     for (const auto& regex : kSystemPathRegexes) {
383       if (std::regex_match(path, regex)) {
384         system_library_search_paths.insert(path);
385         break;
386       }
387     }
388   }
389 
390   // These paths should be tested too - this is because apps may rely on some
391   // libraries being available there.
392   system_library_search_paths.insert(kSystemLibraryPath);
393   system_library_search_paths.insert(kArtApexLibraryPath);
394 
395   if (!check_path(env, clazz, kSystemLibraryPath, system_library_search_paths,
396                   system_public_libraries,
397                   /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
398     success = false;
399   }
400 
401   // Pre-Treble devices use ld.config.vndk_lite.txt, where the default namespace
402   // isn't isolated. That means it can successfully load libraries in /apex, so
403   // don't complain about that in that case.
404   bool check_absence = !android::base::GetBoolProperty("ro.vndk.lite", false);
405 
406   // Check the runtime libraries.
407   if (!check_path(env, clazz, kArtApexLibraryPath, {kArtApexLibraryPath},
408                   runtime_public_libraries,
409                   /*test_system_load_library=*/true,
410                   check_absence, &errors)) {
411     success = false;
412   }
413 
414   // Check the product libraries, if /product/lib exists.
415   if (is_directory(kProductLibraryPath.c_str())) {
416     if (!check_path(env, clazz, kProductLibraryPath, {kProductLibraryPath},
417                     product_public_libraries,
418                     /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
419       success = false;
420     }
421   }
422 
423   // Check the vendor libraries.
424   if (!check_path(env, clazz, kVendorLibraryPath, {kVendorLibraryPath}, vendor_public_libraries,
425                   /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
426     success = false;
427   }
428 
429   if (!success) {
430     std::string error_str;
431     for (const auto& line : errors) {
432       error_str += line + '\n';
433     }
434     return env->NewStringUTF(error_str.c_str());
435   }
436 
437   return nullptr;
438 }
439 
Java_android_jni_cts_LinkerNamespacesHelper_tryDlopen(JNIEnv * env,jclass clazz,jstring lib)440 extern "C" JNIEXPORT jstring JNICALL Java_android_jni_cts_LinkerNamespacesHelper_tryDlopen(
441         JNIEnv* env,
442         jclass clazz,
443         jstring lib) {
444     ScopedUtfChars soname(env, lib);
445     std::string error_str = try_dlopen(soname.c_str());
446 
447     if (!error_str.empty()) {
448       return env->NewStringUTF(error_str.c_str());
449     }
450     return nullptr;
451 }
452 
Java_android_jni_cts_LinkerNamespacesHelper_getLibAbi(JNIEnv * env,jclass clazz)453 extern "C" JNIEXPORT jint JNICALL Java_android_jni_cts_LinkerNamespacesHelper_getLibAbi(
454         JNIEnv* env,
455         jclass clazz) {
456 #ifdef __aarch64__
457     return 1; // ARM64
458 #elif __arm__
459     return 2;
460 #elif __x86_64__
461     return 3;
462 #elif i386
463     return 4;
464 #else
465     return 0;
466 #endif
467 }
468