blob: 1407cc90d5c64f158dd2d91caa75ed78cbd9a569 [file] [log] [blame]
/*
* 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 <JNIHelp.h>
#include <libgen.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <list>
#include <string>
#include <unordered_set>
#include <vector>
#include "ScopedLocalRef.h"
#include "ScopedUtfChars.h"
#if defined(__LP64__)
static const std::string kSystemLibraryPath = "/system/lib64";
static const std::string kVendorLibraryPath = "/vendor/lib64";
#else
static const std::string kSystemLibraryPath = "/system/lib";
static const std::string kVendorLibraryPath = "/vendor/lib";
#endif
// This is not complete list - just a small subset
// of the libraries that should reside in /system/lib
// (in addition to kSystemPublicLibraries)
static std::unordered_set<std::string> kSystemLibraries = {
"libart.so",
"libandroid_runtime.so",
"libbinder.so",
"libcutils.so",
"libgui.so",
"libmedia.so",
"libnativehelper.so",
"libstagefright.so",
"libui.so",
"libutils.so",
};
template <typename F>
static bool for_each_file(const std::string& dir, F functor, std::string* error_msg) {
auto dir_deleter = [](DIR* handle) { closedir(handle); };
std::unique_ptr<DIR, decltype(dir_deleter)> dirp(opendir(dir.c_str()), dir_deleter);
if (dirp == nullptr) {
*error_msg = strerror(errno);
return false;
}
dirent* dp;
while ((dp = readdir(dirp.get())) != nullptr) {
// skip "." and ".."
if (strcmp(".", dp->d_name) == 0 ||
strcmp("..", dp->d_name) == 0) {
continue;
}
if (!functor(dp->d_name, error_msg)) {
return false;
}
}
return true;
}
static bool should_be_accessible(const std::string& public_library_path,
const std::unordered_set<std::string>& public_libraries,
const std::string& path) {
std::string name = basename(path.c_str());
return (public_libraries.find(name) != public_libraries.end()) &&
(public_library_path + "/" + name == path);
}
static bool is_directory(const std::string path) {
struct stat sb;
if (stat(path.c_str(), &sb) != -1) {
return S_ISDIR(sb.st_mode);
}
return false;
}
static bool is_libdl(const std::string path) {
return kSystemLibraryPath + "/libdl.so" == path;
}
static bool check_lib(const std::string& public_library_path,
const std::unordered_set<std::string>& public_libraries,
const std::string& path,
std::string* error_msg) {
if (is_libdl(path)) {
// TODO (dimitry): we skip check for libdl.so because
// 1. Linker will fail to check accessibility because it imposes as libdl.so (see http://b/27106625)
// 2. It is impractical to dlopen libdl.so because this library already depends
// on it in order to have dlopen()
return true;
}
auto dlcloser = [](void* handle) { dlclose(handle); };
std::unique_ptr<void, decltype(dlcloser)> handle(dlopen(path.c_str(), RTLD_NOW), dlcloser);
if (should_be_accessible(public_library_path, public_libraries, path)) {
if (handle.get() == nullptr) {
*error_msg = "The library \"" + path + "\" should be accessible but isn't: " + dlerror();
return false;
}
} else if (handle.get() != nullptr) {
*error_msg = "The library \"" + path + "\" should not be accessible";
return false;
} else { // (handle == nullptr && !shouldBeAccessible(path))
// Check the error message
std::string err = dlerror();
if (err.find("dlopen failed: library \"" + path + "\"") != 0 ||
err.find("is not accessible for the namespace \"classloader-namespace\"") == std::string::npos) {
*error_msg = "unexpected dlerror: " + err;
return false;
}
}
return true;
}
static bool check_libs(const std::string& public_library_path,
const std::unordered_set<std::string>& public_libraries,
const std::unordered_set<std::string>& mandatory_files,
std::string* error) {
std::list<std::string> dirs;
dirs.push_back(public_library_path);
while (!dirs.empty()) {
const auto dir = dirs.front();
dirs.pop_front();
bool success = for_each_file(dir, [&](const char* name, std::string* error_msg) {
std::string path = dir + "/" + name;
if (is_directory(path)) {
dirs.push_back(path);
return true;
}
return check_lib(public_library_path, public_libraries, path, error_msg);
}, error);
if (!success) {
return false;
}
// Check mandatory files - the grey list
for (const auto& name : mandatory_files) {
std::string path = public_library_path + "/" + name;
if (!check_lib(public_library_path, public_libraries, path, error)) {
return false;
}
}
}
return true;
}
static bool jobject_array_to_set(JNIEnv* env,
jobjectArray java_libraries_array,
std::unordered_set<std::string>* libraries,
std::string* error_msg) {
size_t size = env->GetArrayLength(java_libraries_array);
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());
// 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 = "public library line is malformed: " + soname;
return false;
}
#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 true;
}
extern "C" JNIEXPORT jstring JNICALL
Java_android_jni_cts_LinkerNamespacesHelper_runAccessibilityTestImpl(
JNIEnv* env,
jclass clazz __attribute__((unused)),
jobjectArray java_system_public_libraries,
jobjectArray java_vendor_public_libraries) {
std::string error;
std::unordered_set<std::string> vendor_public_libraries;
std::unordered_set<std::string> system_public_libraries;
std::unordered_set<std::string> empty_set;
if (!jobject_array_to_set(env, java_vendor_public_libraries, &vendor_public_libraries, &error)) {
return env->NewStringUTF(("Vendor " + error).c_str());
}
if (!jobject_array_to_set(env, java_system_public_libraries, &system_public_libraries, &error)) {
return env->NewStringUTF(("System " + error).c_str());
}
if (!check_libs(kSystemLibraryPath, system_public_libraries, kSystemLibraries, &error) ||
!check_libs(kVendorLibraryPath, vendor_public_libraries, empty_set, &error)) {
return env->NewStringUTF(error.c_str());
}
return nullptr;
}