linker: hide the pointer to soinfo

Handle no longer is a pointer to soinfo of
a corresponding library. This is done to
prevent access to linker internal fields.

Bug: http://b/25593965
Change-Id: I62bff0d0e5b2dc842e6bf0babb30fcc4c000be24
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
index a7c3fb0..2fc8af0 100644
--- a/linker/dlfcn.cpp
+++ b/linker/dlfcn.cpp
@@ -68,7 +68,7 @@
 static void* dlopen_ext(const char* filename, int flags,
                         const android_dlextinfo* extinfo, void* caller_addr) {
   ScopedPthreadMutexLocker locker(&g_dl_mutex);
-  soinfo* result = do_dlopen(filename, flags, extinfo, caller_addr);
+  void* result = do_dlopen(filename, flags, extinfo, caller_addr);
   if (result == nullptr) {
     __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
     return nullptr;
@@ -86,8 +86,6 @@
   return dlopen_ext(filename, flags, nullptr, caller_addr);
 }
 
-extern android_namespace_t* g_anonymous_namespace;
-
 void* dlsym_impl(void* handle, const char* symbol, const char* version, void* caller_addr) {
   ScopedPthreadMutexLocker locker(&g_dl_mutex);
   void* result;
@@ -116,9 +114,11 @@
 
 int dlclose(void* handle) {
   ScopedPthreadMutexLocker locker(&g_dl_mutex);
-  do_dlclose(reinterpret_cast<soinfo*>(handle));
-  // dlclose has no defined errors.
-  return 0;
+  int result = do_dlclose(handle);
+  if (result != 0) {
+    __bionic_format_dlerror("dlclose failed", linker_get_error_buffer());
+  }
+  return result;
 }
 
 int dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), void* data) {
@@ -263,6 +263,7 @@
     __libdl_info->local_group_root_ = __libdl_info;
     __libdl_info->soname_ = "libdl.so";
     __libdl_info->target_sdk_version_ = __ANDROID_API__;
+    __libdl_info->generate_handle();
 #if defined(__work_around_b_24465209__)
     strlcpy(__libdl_info->old_name_, __libdl_info->soname_, sizeof(__libdl_info->old_name_));
 #endif
diff --git a/linker/linked_list.h b/linker/linked_list.h
index 88386b0..092e831 100644
--- a/linker/linked_list.h
+++ b/linker/linked_list.h
@@ -43,7 +43,7 @@
     return *this;
   }
 
-  T* operator*() {
+  T* const operator*() {
     return entry_->element;
   }
 
@@ -190,15 +190,15 @@
     return nullptr;
   }
 
-  iterator begin() {
+  iterator begin() const {
     return iterator(head_);
   }
 
-  iterator end() {
+  iterator end() const {
     return iterator(nullptr);
   }
 
-  iterator find(T* value) {
+  iterator find(T* value) const {
     for (LinkedListEntry<T>* e = head_; e != nullptr; e = e->next) {
       if (e->element == value) {
         return iterator(e);
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 2cdfd2c..855b32b 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -103,7 +103,23 @@
     permitted_paths_ = permitted_paths;
   }
 
-  soinfo::soinfo_list_t& soinfo_list() { return soinfo_list_; }
+  void add_soinfo(soinfo* si) {
+    soinfo_list_.push_back(si);
+  }
+
+  void add_soinfos(const soinfo::soinfo_list_t& soinfos) {
+    for (auto si : soinfos) {
+      add_soinfo(si);
+    }
+  }
+
+  void remove_soinfo(soinfo* si) {
+    soinfo_list_.remove_if([&](soinfo* candidate) {
+      return si == candidate;
+    });
+  }
+
+  const soinfo::soinfo_list_t& soinfo_list() const { return soinfo_list_; }
 
   // For isolated namespaces - checks if the file is on the search path;
   // always returns true for not isolated namespace.
@@ -121,7 +137,8 @@
 };
 
 android_namespace_t g_default_namespace;
-android_namespace_t* g_anonymous_namespace = &g_default_namespace;
+static std::unordered_map<uintptr_t, soinfo*> g_soinfo_handles_map;
+static android_namespace_t* g_anonymous_namespace = &g_default_namespace;
 
 static ElfW(Addr) get_elf_exec_load_bias(const ElfW(Ehdr)* elf);
 
@@ -283,7 +300,8 @@
   sonext->next = si;
   sonext = si;
 
-  ns->soinfo_list().push_back(si);
+  si->generate_handle();
+  ns->add_soinfo(si);
 
   TRACE("name %s: allocated soinfo @ %p", name, si);
   return si;
@@ -332,9 +350,7 @@
   }
 
   // remove from the namespace
-  si->get_namespace()->soinfo_list().remove_if([&](soinfo* candidate) {
-    return si == candidate;
-  });
+  si->get_namespace()->remove_soinfo(si);
 
   si->~soinfo();
   g_soinfo_allocator.free(si);
@@ -830,6 +846,10 @@
   this->namespace_ = ns;
 }
 
+soinfo::~soinfo() {
+  g_soinfo_handles_map.erase(handle_);
+}
+
 static uint32_t calculate_elf_hash(const char* name) {
   const uint8_t* name_bytes = reinterpret_cast<const uint8_t*>(name);
   uint32_t h = 0, g;
@@ -1252,21 +1272,21 @@
                                             void* handle) {
   SymbolName symbol_name(name);
 
-  soinfo::soinfo_list_t& soinfo_list = ns->soinfo_list();
-  soinfo::soinfo_list_t::iterator start = soinfo_list.begin();
+  auto& soinfo_list = ns->soinfo_list();
+  auto start = soinfo_list.begin();
 
   if (handle == RTLD_NEXT) {
     if (caller == nullptr) {
       return nullptr;
     } else {
-      soinfo::soinfo_list_t::iterator it = soinfo_list.find(caller);
+      auto it = soinfo_list.find(caller);
       CHECK (it != soinfo_list.end());
       start = ++it;
     }
   }
 
   const ElfW(Sym)* s = nullptr;
-  for (soinfo::soinfo_list_t::iterator it = start, end = soinfo_list.end(); it != end; ++it) {
+  for (auto it = start, end = soinfo_list.end(); it != end; ++it) {
     soinfo* si = *it;
     // Do not skip RTLD_LOCAL libraries in dlsym(RTLD_DEFAULT, ...)
     // if the library is opened by application with target api level <= 22
@@ -1638,7 +1658,7 @@
     if (si == nullptr) {
       si = g_public_namespace.find_if(predicate);
       if (si != nullptr) {
-        ns->soinfo_list().push_back(si);
+        ns->add_soinfo(si);
       }
     }
 
@@ -1812,7 +1832,7 @@
     });
 
     if (candidate != nullptr) {
-      ns->soinfo_list().push_back(candidate);
+      ns->add_soinfo(candidate);
       task->set_soinfo(candidate);
       return true;
     }
@@ -2179,7 +2199,7 @@
   parse_LD_LIBRARY_PATH(ld_library_path);
 }
 
-soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo,
+void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo,
                   void* caller_addr) {
   soinfo* const caller = find_containing_library(caller_addr);
 
@@ -2223,9 +2243,10 @@
   soinfo* si = find_library(ns, name, flags, extinfo, caller);
   if (si != nullptr) {
     si->call_constructors();
+    return si->to_handle();
   }
 
-  return si;
+  return nullptr;
 }
 
 int do_dladdr(const void* addr, Dl_info* info) {
@@ -2251,6 +2272,19 @@
   return 1;
 }
 
+static soinfo* soinfo_from_handle(void* handle) {
+  if ((reinterpret_cast<uintptr_t>(handle) & 1) != 0) {
+    auto it = g_soinfo_handles_map.find(reinterpret_cast<uintptr_t>(handle));
+    if (it == g_soinfo_handles_map.end()) {
+      return nullptr;
+    } else {
+      return it->second;
+    }
+  }
+
+  return static_cast<soinfo*>(handle);
+}
+
 bool do_dlsym(void* handle, const char* sym_name, const char* sym_ver,
               void* caller_addr, void** symbol) {
 #if !defined(__LP64__)
@@ -2282,7 +2316,12 @@
   if (handle == RTLD_DEFAULT || handle == RTLD_NEXT) {
     sym = dlsym_linear_lookup(ns, sym_name, vi, &found, caller, handle);
   } else {
-    sym = dlsym_handle_lookup(reinterpret_cast<soinfo*>(handle), &found, sym_name, vi);
+    soinfo* si = soinfo_from_handle(handle);
+    if (si == nullptr) {
+      DL_ERR("dlsym failed: invalid handle: %p", handle);
+      return false;
+    }
+    sym = dlsym_handle_lookup(si, &found, sym_name, vi);
   }
 
   if (sym != nullptr) {
@@ -2301,9 +2340,16 @@
   return false;
 }
 
-void do_dlclose(soinfo* si) {
+int do_dlclose(void* handle) {
   ProtectedDataGuard guard;
+  soinfo* si = soinfo_from_handle(handle);
+  if (si == nullptr) {
+    DL_ERR("invalid handle: %p", handle);
+    return -1;
+  }
+
   soinfo_unload(si);
+  return 0;
 }
 
 bool init_namespaces(const char* public_ns_sonames, const char* anon_ns_library_path) {
@@ -2390,12 +2436,10 @@
 
   if ((type & ANDROID_NAMESPACE_TYPE_SHARED) != 0) {
     // If shared - clone the caller namespace
-    auto& soinfo_list = caller_ns->soinfo_list();
-    std::copy(soinfo_list.begin(), soinfo_list.end(), std::back_inserter(ns->soinfo_list()));
+    ns->add_soinfos(caller_ns->soinfo_list());
   } else {
     // If not shared - copy only the global group
-    auto global_group = make_global_group(caller_ns);
-    std::copy(global_group.begin(), global_group.end(), std::back_inserter(ns->soinfo_list()));
+    ns->add_soinfos(make_global_group(caller_ns));
   }
 
   return ns;
@@ -3239,6 +3283,39 @@
   return local_group_root_->target_sdk_version_;
 }
 
+uintptr_t soinfo::get_handle() const {
+  CHECK(has_min_version(3));
+  CHECK(handle_ != 0);
+  return handle_;
+}
+
+void* soinfo::to_handle() {
+  if (get_application_target_sdk_version() <= 23 || !has_min_version(3)) {
+    return this;
+  }
+
+  return reinterpret_cast<void*>(get_handle());
+}
+
+void soinfo::generate_handle() {
+  CHECK(has_min_version(3));
+  CHECK(handle_ == 0); // Make sure this is the first call
+
+  // Make sure the handle is unique and does not collide
+  // with special values which are RTLD_DEFAULT and RTLD_NEXT.
+  do {
+    arc4random_buf(&handle_, sizeof(handle_));
+    // the least significant bit for the handle is always 1
+    // making it easy to test the type of handle passed to
+    // dl* functions.
+    handle_ = handle_ | 1;
+  } while (handle_ == reinterpret_cast<uintptr_t>(RTLD_DEFAULT) ||
+           handle_ == reinterpret_cast<uintptr_t>(RTLD_NEXT) ||
+           g_soinfo_handles_map.find(handle_) != g_soinfo_handles_map.end());
+
+  g_soinfo_handles_map[handle_] = this;
+}
+
 bool soinfo::prelink_image() {
   /* Extract dynamic section */
   ElfW(Word) dynamic_flags = 0;
@@ -4212,7 +4289,7 @@
   // before get_libdl_info().
   solist = get_libdl_info();
   sonext = get_libdl_info();
-  g_default_namespace.soinfo_list().push_back(get_libdl_info());
+  g_default_namespace.add_soinfo(get_libdl_info());
 
   // We have successfully fixed our own relocations. It's safe to run
   // the main part of the linker now.
diff --git a/linker/linker.h b/linker/linker.h
index 9145454..81f93ac 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -268,6 +268,7 @@
  public:
   soinfo(android_namespace_t* ns, const char* name, const struct stat* file_stat,
          off64_t file_offset, int rtld_flags);
+  ~soinfo();
 
   void call_constructors();
   void call_destructors();
@@ -346,6 +347,10 @@
   void set_mapped_by_caller(bool reserved_map);
   bool is_mapped_by_caller() const;
 
+  uintptr_t get_handle() const;
+  void generate_handle();
+  void* to_handle();
+
  private:
   bool elf_lookup(SymbolName& symbol_name, const version_info* vi, uint32_t* symbol_index) const;
   ElfW(Sym)* elf_addr_lookup(const void* addr);
@@ -410,6 +415,7 @@
   // version >= 3
   std::vector<std::string> dt_runpath_;
   android_namespace_t* namespace_;
+  uintptr_t handle_;
 
   friend soinfo* get_libdl_info();
 };
@@ -432,8 +438,8 @@
 
 void do_android_get_LD_LIBRARY_PATH(char*, size_t);
 void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path);
-soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, void* caller_addr);
-void do_dlclose(soinfo* si);
+void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, void* caller_addr);
+int do_dlclose(void* handle);
 
 int do_dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), void* data);
 
diff --git a/tests/dlext_test.cpp b/tests/dlext_test.cpp
index 66d8859..bbdc024 100644
--- a/tests/dlext_test.cpp
+++ b/tests/dlext_test.cpp
@@ -1050,3 +1050,19 @@
 
   ASSERT_TRUE(ns_get_dlopened_string_anon() != ns_get_dlopened_string_private());
 }
+
+TEST(dlext, dlopen_handle_value_platform) {
+  void* handle = dlopen("libtest_dlsym_from_this.so", RTLD_NOW | RTLD_LOCAL);
+  ASSERT_TRUE((reinterpret_cast<uintptr_t>(handle) & 1) != 0)
+          << "dlopen should return odd value for the handle";
+  dlclose(handle);
+}
+
+TEST(dlext, dlopen_handle_value_app_compat) {
+  android_set_application_target_sdk_version(23);
+  void* handle = dlopen("libtest_dlsym_from_this.so", RTLD_NOW | RTLD_LOCAL);
+  ASSERT_TRUE(reinterpret_cast<uintptr_t>(handle) % sizeof(uintptr_t) == 0)
+          << "dlopen should return valid pointer";
+  dlclose(handle);
+}
+