Add permitted_when_isolated_path to linker namespaces

The permitted_when_isolated_path is a way to white-list
directories not present in search-path. It is ignored for
not isolated namespaces.

Bug: http://b/25853516
Bug: http://b/22548808
Change-Id: Ib1538037268eea69323ea49968a34a4a1d1938a5
diff --git a/libc/include/android/dlext.h b/libc/include/android/dlext.h
index ed9a3b9..7979c43 100644
--- a/libc/include/android/dlext.h
+++ b/libc/include/android/dlext.h
@@ -152,16 +152,20 @@
  * 2. In directories specified by DT_RUNPATH of the "needed by" binary.
  * 3. deault_library_path (This of this as namespace-local default library path)
  *
- * When is_isolated is true the resulted namespace requires all of the libraries
- * to be on the search path; the search_path is ld_library_path:default_library_path.
+ * When is_isolated is true the resulting namespace requires all of the libraries
+ * to be on the search path or under the permitted_when_isolated_path; the search_path is
+ * ld_library_path:default_library_path. Note that the permitted_when_isolated_path path
+ * is not part of the search_path and does not affect the search order. It is a way
+ * to allow loading libraries from specific locations when using absolute path.
  *
- * If a library or any of its dependencies are outside of the search path and not
- * part of the public namespace dlopen will fail.
+ * If a library or any of its dependencies are outside of the permitted_when_isolated_path
+ * and search_path, and it is not part of the public namespace dlopen will fail.
  */
 extern struct android_namespace_t* android_create_namespace(const char* name,
                                                             const char* ld_library_path,
                                                             const char* default_library_path,
-                                                            bool is_isolated);
+                                                            bool is_isolated,
+                                                            const char* permitted_when_isolated_path);
 
 __END_DECLS
 
diff --git a/libdl/libdl.c b/libdl/libdl.c
index 3928ba2..af2f83e 100644
--- a/libdl/libdl.c
+++ b/libdl/libdl.c
@@ -57,6 +57,7 @@
 struct android_namespace_t* android_create_namespace(const char* name __unused,
                                                      const char* ld_library_path __unused,
                                                      const char* default_library_path __unused,
-                                                     bool isolated __unused) {
+                                                     bool isolated __unused,
+                                                     const char* permitted_when_isolated_path __unused) {
   return 0;
 }
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
index d07ec86..fde9f5c 100644
--- a/linker/dlfcn.cpp
+++ b/linker/dlfcn.cpp
@@ -193,11 +193,12 @@
 }
 
 android_namespace_t* android_create_namespace(const char* name, const char* ld_library_path,
-                                              const char* default_library_path, bool is_isolated) {
+                                              const char* default_library_path, bool is_isolated,
+                                              const char* permitted_when_isolated_path) {
   ScopedPthreadMutexLocker locker(&g_dl_mutex);
 
-  android_namespace_t* result = create_namespace(name, ld_library_path,
-                                                 default_library_path, is_isolated);
+  android_namespace_t* result = create_namespace(name, ld_library_path, default_library_path,
+                                                 is_isolated, permitted_when_isolated_path);
 
   if (result == nullptr) {
     __bionic_format_dlerror("android_create_namespace failed", linker_get_error_buffer());
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 82d0d9e..3b67625 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -94,6 +94,10 @@
     default_library_paths_ = library_paths;
   }
 
+  void set_permitted_paths(std::vector<std::string>&& permitted_paths) {
+    permitted_paths_ = permitted_paths;
+  }
+
   soinfo::soinfo_list_t& soinfo_list() { return soinfo_list_; }
 
   // For isolated namespaces - checks if the file is on the search path;
@@ -105,6 +109,7 @@
   bool is_isolated_;
   std::vector<std::string> ld_library_paths_;
   std::vector<std::string> default_library_paths_;
+  std::vector<std::string> permitted_paths_;
   soinfo::soinfo_list_t soinfo_list_;
 
   DISALLOW_COPY_AND_ASSIGN(android_namespace_t);
@@ -316,6 +321,12 @@
     }
   }
 
+  for (const auto& dir : permitted_paths_) {
+    if (file_is_under_dir(file, dir)) {
+      return true;
+    }
+  }
+
   return false;
 }
 
@@ -2285,7 +2296,7 @@
 
   // create anonymous namespace
   android_namespace_t* anon_ns =
-      create_namespace("(anonymous)", nullptr, anon_ns_library_path, false);
+      create_namespace("(anonymous)", nullptr, anon_ns_library_path, false, nullptr);
 
   if (anon_ns == nullptr) {
     g_public_namespace_initialized = false;
@@ -2299,7 +2310,8 @@
 android_namespace_t* create_namespace(const char* name,
                                       const char* ld_library_path,
                                       const char* default_library_path,
-                                      bool is_isolated) {
+                                      bool is_isolated,
+                                      const char* permitted_when_isolated_path) {
   if (!g_public_namespace_initialized) {
     DL_ERR("cannot create namespace: public namespace is not initialized.");
     return nullptr;
@@ -2308,15 +2320,18 @@
   ProtectedDataGuard guard;
   std::vector<std::string> ld_library_paths;
   std::vector<std::string> default_library_paths;
+  std::vector<std::string> permitted_paths;
 
   parse_path(ld_library_path, ":", &ld_library_paths);
   parse_path(default_library_path, ":", &default_library_paths);
+  parse_path(permitted_when_isolated_path, ":", &permitted_paths);
 
   android_namespace_t* ns = new (g_namespace_allocator.alloc()) android_namespace_t();
   ns->set_name(name);
   ns->set_isolated(is_isolated);
   ns->set_ld_library_paths(std::move(ld_library_paths));
   ns->set_default_library_paths(std::move(default_library_paths));
+  ns->set_permitted_paths(std::move(permitted_paths));
 
   // TODO(dimtiry): Should this be global group of caller's namespace?
   auto global_group = make_global_group(&g_default_namespace);
diff --git a/linker/linker.h b/linker/linker.h
index b391fc3..49329c7 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -446,6 +446,7 @@
 
 bool init_namespaces(const char* public_ns_sonames, const char* anon_ns_library_path);
 android_namespace_t* create_namespace(const char* name, const char* ld_library_path,
-                                      const char* default_library_path, bool is_isolated);
+                                      const char* default_library_path, bool is_isolated,
+                                      const char* permitted_when_isolated_path);
 
 #endif
diff --git a/linker/linker_utils.cpp b/linker/linker_utils.cpp
index db43d38..1b0c4e4 100644
--- a/linker/linker_utils.cpp
+++ b/linker/linker_utils.cpp
@@ -66,9 +66,18 @@
   const char* haystack = file.c_str();
   size_t needle_len = strlen(needle);
 
-  return (strncmp(haystack, needle, needle_len) == 0 &&
-          haystack[needle_len] == '/' &&
-          strchr(haystack + needle_len + 1, '/') == nullptr);
+  return strncmp(haystack, needle, needle_len) == 0 &&
+         haystack[needle_len] == '/' &&
+         strchr(haystack + needle_len + 1, '/') == nullptr;
+}
+
+bool file_is_under_dir(const std::string& file, const std::string& dir) {
+  const char* needle = dir.c_str();
+  const char* haystack = file.c_str();
+  size_t needle_len = strlen(needle);
+
+  return strncmp(haystack, needle, needle_len) == 0 &&
+         haystack[needle_len] == '/';
 }
 
 const char* const kZipFileSeparator = "!/";
diff --git a/linker/linker_utils.h b/linker/linker_utils.h
index 65ffbdc5..987eabd 100644
--- a/linker/linker_utils.h
+++ b/linker/linker_utils.h
@@ -22,6 +22,7 @@
 
 bool normalize_path(const char* path, std::string* normalized_path);
 bool file_is_in_dir(const std::string& file, const std::string& dir);
+bool file_is_under_dir(const std::string& file, const std::string& dir);
 bool parse_zip_path(const char* input_path, std::string* zip_path, std::string* entry_path);
 
 off64_t page_start(off64_t offset);
diff --git a/linker/tests/linker_utils_test.cpp b/linker/tests/linker_utils_test.cpp
index 3be9b3f..fd749fa 100644
--- a/linker/tests/linker_utils_test.cpp
+++ b/linker/tests/linker_utils_test.cpp
@@ -54,6 +54,18 @@
   ASSERT_FALSE(file_is_in_dir("/file", "/"));
 }
 
+TEST(linker_utils, file_is_under_dir_smoke) {
+  ASSERT_TRUE(file_is_under_dir("/foo/bar/file", "/foo/bar"));
+  ASSERT_TRUE(file_is_under_dir("/foo/bar/file", "/foo"));
+
+  ASSERT_FALSE(file_is_under_dir("/foo/bar/file", "/bar/foo"));
+
+  ASSERT_TRUE(file_is_under_dir("/file", ""));
+  ASSERT_TRUE(file_is_under_dir("/foo/bar/file", ""));
+  ASSERT_FALSE(file_is_under_dir("/file", "/"));
+  ASSERT_FALSE(file_is_under_dir("/foo/bar/file", "/"));
+}
+
 TEST(linker_utils, parse_zip_path_smoke) {
   std::string zip_path;
   std::string entry_path;
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index 97b5208..5327e36 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -628,10 +628,10 @@
   handle_public = dlopen((lib_path + "/public_namespace_libs/" + g_public_lib).c_str(), RTLD_NOW | RTLD_NOLOAD);
   ASSERT_TRUE(handle_public != nullptr) << dlerror();
 
-  android_namespace_t* ns1 = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false);
+  android_namespace_t* ns1 = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false, nullptr);
   ASSERT_TRUE(ns1 != nullptr) << dlerror();
 
-  android_namespace_t* ns2 = android_create_namespace("private_isolated", nullptr, (lib_path + "/private_namespace_libs").c_str(), true);
+  android_namespace_t* ns2 = android_create_namespace("private_isolated", nullptr, (lib_path + "/private_namespace_libs").c_str(), true, nullptr);
   ASSERT_TRUE(ns2 != nullptr) << dlerror();
 
   // This should not have affect search path for default namespace:
@@ -732,13 +732,13 @@
 
   ASSERT_TRUE(android_init_namespaces(path.c_str(), nullptr)) << dlerror();
 
-  android_namespace_t* ns_not_isolated = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false);
+  android_namespace_t* ns_not_isolated = android_create_namespace("private", nullptr, (lib_path + "/private_namespace_libs").c_str(), false, nullptr);
   ASSERT_TRUE(ns_not_isolated != nullptr) << dlerror();
 
-  android_namespace_t* ns_isolated = android_create_namespace("private_isolated1", nullptr, (lib_path + "/private_namespace_libs").c_str(), true);
+  android_namespace_t* ns_isolated = android_create_namespace("private_isolated1", nullptr, (lib_path + "/private_namespace_libs").c_str(), true, nullptr);
   ASSERT_TRUE(ns_isolated != nullptr) << dlerror();
 
-  android_namespace_t* ns_isolated2 = android_create_namespace("private_isolated2", (lib_path + "/private_namespace_libs").c_str(), nullptr, true);
+  android_namespace_t* ns_isolated2 = android_create_namespace("private_isolated2", (lib_path + "/private_namespace_libs").c_str(), nullptr, true, lib_path.c_str());
   ASSERT_TRUE(ns_isolated2 != nullptr) << dlerror();
 
   ASSERT_TRUE(dlopen(root_lib, RTLD_NOW) == nullptr);
@@ -772,14 +772,15 @@
 
   extinfo.library_namespace = ns_isolated2;
 
+  // this should work because isolation_path for private_isolated2 includes lib_path
   handle2 = android_dlopen_ext(root_lib, RTLD_NOW, &extinfo);
-  ASSERT_TRUE(handle2 == nullptr);
-  ASSERT_STREQ("dlopen failed: library \"libnstest_private_external.so\" not found", dlerror());
+  ASSERT_TRUE(handle2 != nullptr) << dlerror();
+  dlclose(handle2);
 
   // Check dlopen by absolute path
   handle2 = android_dlopen_ext(lib_private_external_path.c_str(), RTLD_NOW, &extinfo);
-  ASSERT_TRUE(handle2 == nullptr);
-  ASSERT_EQ("dlopen failed: library \"" + lib_private_external_path + "\" is not accessible for the namespace \"private_isolated2\"", dlerror());
+  ASSERT_TRUE(handle2 != nullptr) << dlerror();
+  dlclose(handle2);
 
   typedef const char* (*fn_t)();
   fn_t ns_get_local_string = reinterpret_cast<fn_t>(dlsym(handle1, "ns_get_local_string"));
@@ -824,7 +825,7 @@
   android_namespace_t* ns = android_create_namespace(
                                 "private", nullptr,
                                 (lib_path + "/private_namespace_libs").c_str(),
-                                false);
+                                false, nullptr);
 
   ASSERT_TRUE(ns != nullptr) << dlerror();