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