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