| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <android/api-level.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <pthread.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/mman.h> |
| #include <sys/param.h> |
| #include <unistd.h> |
| |
| #include <new> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| // Private C library headers. |
| #include "private/bionic_globals.h" |
| #include "private/bionic_tls.h" |
| #include "private/KernelArgumentBlock.h" |
| #include "private/ScopedPthreadMutexLocker.h" |
| #include "private/ScopeGuard.h" |
| |
| #include "linker.h" |
| #include "linker_block_allocator.h" |
| #include "linker_gdb_support.h" |
| #include "linker_debug.h" |
| #include "linker_dlwarning.h" |
| #include "linker_sleb128.h" |
| #include "linker_phdr.h" |
| #include "linker_relocs.h" |
| #include "linker_reloc_iterators.h" |
| #include "linker_utils.h" |
| |
| #include "android-base/strings.h" |
| #include "ziparchive/zip_archive.h" |
| |
| extern void __libc_init_globals(KernelArgumentBlock&); |
| extern void __libc_init_AT_SECURE(KernelArgumentBlock&); |
| |
| extern "C" void _start(); |
| |
| // Override macros to use C++ style casts. |
| #undef ELF_ST_TYPE |
| #define ELF_ST_TYPE(x) (static_cast<uint32_t>(x) & 0xf) |
| |
| struct android_namespace_t { |
| public: |
| android_namespace_t() : name_(nullptr), is_isolated_(false) {} |
| |
| const char* get_name() const { return name_; } |
| void set_name(const char* name) { name_ = name; } |
| |
| bool is_isolated() const { return is_isolated_; } |
| void set_isolated(bool isolated) { is_isolated_ = isolated; } |
| |
| const std::vector<std::string>& get_ld_library_paths() const { |
| return ld_library_paths_; |
| } |
| void set_ld_library_paths(std::vector<std::string>&& library_paths) { |
| ld_library_paths_ = library_paths; |
| } |
| |
| const std::vector<std::string>& get_default_library_paths() const { |
| return default_library_paths_; |
| } |
| void set_default_library_paths(std::vector<std::string>&& library_paths) { |
| default_library_paths_ = library_paths; |
| } |
| |
| const std::vector<std::string>& get_permitted_paths() const { |
| return permitted_paths_; |
| } |
| void set_permitted_paths(std::vector<std::string>&& permitted_paths) { |
| permitted_paths_ = permitted_paths; |
| } |
| |
| 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); |
| si->add_secondary_namespace(this); |
| } |
| } |
| |
| 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. |
| bool is_accessible(const std::string& path); |
| |
| private: |
| const char* name_; |
| 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); |
| }; |
| |
| android_namespace_t 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); |
| |
| static LinkerTypeAllocator<soinfo> g_soinfo_allocator; |
| static LinkerTypeAllocator<LinkedListEntry<soinfo>> g_soinfo_links_allocator; |
| |
| static LinkerTypeAllocator<android_namespace_t> g_namespace_allocator; |
| static LinkerTypeAllocator<LinkedListEntry<android_namespace_t>> g_namespace_list_allocator; |
| |
| static soinfo* solist; |
| static soinfo* sonext; |
| static soinfo* somain; // main process, always the one after libdl_info |
| |
| #if defined(__LP64__) |
| static const char* const kSystemLibDir = "/system/lib64"; |
| static const char* const kVendorLibDir = "/vendor/lib64"; |
| static const char* const kAsanSystemLibDir = "/data/lib64"; |
| static const char* const kAsanVendorLibDir = "/data/vendor/lib64"; |
| #else |
| static const char* const kSystemLibDir = "/system/lib"; |
| static const char* const kVendorLibDir = "/vendor/lib"; |
| static const char* const kAsanSystemLibDir = "/data/lib"; |
| static const char* const kAsanVendorLibDir = "/data/vendor/lib"; |
| #endif |
| |
| static const char* const kDefaultLdPaths[] = { |
| kSystemLibDir, |
| kVendorLibDir, |
| nullptr |
| }; |
| |
| static const char* const kAsanDefaultLdPaths[] = { |
| kAsanSystemLibDir, |
| kSystemLibDir, |
| kAsanVendorLibDir, |
| kVendorLibDir, |
| nullptr |
| }; |
| |
| // Is ASAN enabled? |
| static bool g_is_asan = false; |
| |
| static bool is_system_library(const std::string& realpath) { |
| for (const auto& dir : g_default_namespace.get_default_library_paths()) { |
| if (file_is_in_dir(realpath, dir)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Checks if the file exists and not a directory. |
| static bool file_exists(const char* path) { |
| int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC)); |
| if (fd == -1) { |
| return false; |
| } else { |
| close(fd); |
| return true; |
| } |
| } |
| static std::string dirname(const char *path); |
| |
| // TODO(dimitry): The grey-list is a workaround for http://b/26394120 --- |
| // gradually remove libraries from this list until it is gone. |
| static bool is_greylisted(const char* name, const soinfo* needed_by) { |
| static const char* const kLibraryGreyList[] = { |
| "libandroid_runtime.so", |
| "libbinder.so", |
| "libcrypto.so", |
| "libcutils.so", |
| "libexpat.so", |
| "libgui.so", |
| "libmedia.so", |
| "libnativehelper.so", |
| "libskia.so", |
| "libssl.so", |
| "libstagefright.so", |
| "libsqlite.so", |
| "libui.so", |
| "libutils.so", |
| "libvorbisidec.so", |
| nullptr |
| }; |
| |
| // limit greylisting to apps targeting sdk version 23 and below |
| if (get_application_target_sdk_version() > 23) { |
| return false; |
| } |
| |
| // if the library needed by a system library - implicitly assume it |
| // is greylisted |
| |
| if (needed_by != nullptr && is_system_library(needed_by->get_realpath())) { |
| return true; |
| } |
| |
| // if this is an absolute path - make sure it points to /system/lib(64) |
| if (name[0] == '/' && dirname(name) == kSystemLibDir) { |
| // and reduce the path to basename |
| name = basename(name); |
| } |
| |
| for (size_t i = 0; kLibraryGreyList[i] != nullptr; ++i) { |
| if (strcmp(name, kLibraryGreyList[i]) == 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| // END OF WORKAROUND |
| |
| static const ElfW(Versym) kVersymNotNeeded = 0; |
| static const ElfW(Versym) kVersymGlobal = 1; |
| |
| static const char* const* g_default_ld_paths; |
| static std::vector<std::string> g_ld_preload_names; |
| |
| static std::vector<soinfo*> g_ld_preloads; |
| |
| static bool g_public_namespace_initialized; |
| static soinfo::soinfo_list_t g_public_namespace; |
| |
| __LIBC_HIDDEN__ int g_ld_debug_verbosity; |
| |
| __LIBC_HIDDEN__ abort_msg_t* g_abort_message = nullptr; // For debuggerd. |
| |
| static std::string dirname(const char *path) { |
| const char* last_slash = strrchr(path, '/'); |
| if (last_slash == path) return "/"; |
| else if (last_slash == nullptr) return "."; |
| else |
| return std::string(path, last_slash - path); |
| } |
| |
| #if STATS |
| struct linker_stats_t { |
| int count[kRelocMax]; |
| }; |
| |
| static linker_stats_t linker_stats; |
| |
| void count_relocation(RelocationKind kind) { |
| ++linker_stats.count[kind]; |
| } |
| #else |
| void count_relocation(RelocationKind) { |
| } |
| #endif |
| |
| #if COUNT_PAGES |
| uint32_t bitmask[4096]; |
| #endif |
| |
| static char __linker_dl_err_buf[768]; |
| |
| char* linker_get_error_buffer() { |
| return &__linker_dl_err_buf[0]; |
| } |
| |
| size_t linker_get_error_buffer_size() { |
| return sizeof(__linker_dl_err_buf); |
| } |
| |
| static void notify_gdb_of_load(soinfo* info) { |
| if (info->is_linker() || info->is_main_executable()) { |
| // gdb already knows about the linker and the main executable. |
| return; |
| } |
| |
| link_map* map = &(info->link_map_head); |
| |
| map->l_addr = info->load_bias; |
| // link_map l_name field is not const. |
| map->l_name = const_cast<char*>(info->get_realpath()); |
| map->l_ld = info->dynamic; |
| |
| CHECK(map->l_name != nullptr); |
| CHECK(map->l_name[0] != '\0'); |
| |
| notify_gdb_of_load(map); |
| } |
| |
| static void notify_gdb_of_unload(soinfo* info) { |
| notify_gdb_of_unload(&(info->link_map_head)); |
| } |
| |
| bool android_namespace_t::is_accessible(const std::string& file) { |
| if (!is_isolated_) { |
| return true; |
| } |
| |
| for (const auto& dir : ld_library_paths_) { |
| if (file_is_in_dir(file, dir)) { |
| return true; |
| } |
| } |
| |
| for (const auto& dir : default_library_paths_) { |
| if (file_is_in_dir(file, dir)) { |
| return true; |
| } |
| } |
| |
| for (const auto& dir : permitted_paths_) { |
| if (file_is_under_dir(file, dir)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| LinkedListEntry<soinfo>* SoinfoListAllocator::alloc() { |
| return g_soinfo_links_allocator.alloc(); |
| } |
| |
| void SoinfoListAllocator::free(LinkedListEntry<soinfo>* entry) { |
| g_soinfo_links_allocator.free(entry); |
| } |
| |
| LinkedListEntry<android_namespace_t>* NamespaceListAllocator::alloc() { |
| return g_namespace_list_allocator.alloc(); |
| } |
| |
| void NamespaceListAllocator::free(LinkedListEntry<android_namespace_t>* entry) { |
| g_namespace_list_allocator.free(entry); |
| } |
| |
| static soinfo* soinfo_alloc(android_namespace_t* ns, const char* name, |
| struct stat* file_stat, off64_t file_offset, |
| uint32_t rtld_flags) { |
| if (strlen(name) >= PATH_MAX) { |
| DL_ERR("library name \"%s\" too long", name); |
| return nullptr; |
| } |
| |
| soinfo* si = new (g_soinfo_allocator.alloc()) soinfo(ns, name, file_stat, |
| file_offset, rtld_flags); |
| |
| sonext->next = si; |
| sonext = si; |
| |
| si->generate_handle(); |
| ns->add_soinfo(si); |
| |
| TRACE("name %s: allocated soinfo @ %p", name, si); |
| return si; |
| } |
| |
| static void soinfo_free(soinfo* si) { |
| if (si == nullptr) { |
| return; |
| } |
| |
| if (si->base != 0 && si->size != 0) { |
| if (!si->is_mapped_by_caller()) { |
| munmap(reinterpret_cast<void*>(si->base), si->size); |
| } else { |
| // remap the region as PROT_NONE, MAP_ANONYMOUS | MAP_NORESERVE |
| mmap(reinterpret_cast<void*>(si->base), si->size, PROT_NONE, |
| MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); |
| } |
| } |
| |
| soinfo *prev = nullptr, *trav; |
| |
| TRACE("name %s: freeing soinfo @ %p", si->get_realpath(), si); |
| |
| for (trav = solist; trav != nullptr; trav = trav->next) { |
| if (trav == si) { |
| break; |
| } |
| prev = trav; |
| } |
| |
| if (trav == nullptr) { |
| // si was not in solist |
| DL_ERR("name \"%s\"@%p is not in solist!", si->get_realpath(), si); |
| return; |
| } |
| |
| // clear links to/from si |
| si->remove_all_links(); |
| |
| // prev will never be null, because the first entry in solist is |
| // always the static libdl_info. |
| prev->next = si->next; |
| if (si == sonext) { |
| sonext = prev; |
| } |
| |
| si->~soinfo(); |
| g_soinfo_allocator.free(si); |
| } |
| |
| // For every path element this function checks of it exists, and is a directory, |
| // and normalizes it: |
| // 1. For regular path it converts it to realpath() |
| // 2. For path in a zip file it uses realpath on the zipfile |
| // normalizes entry name by calling normalize_path function. |
| static void resolve_paths(std::vector<std::string>& paths, |
| std::vector<std::string>* resolved_paths) { |
| resolved_paths->clear(); |
| for (const auto& path : paths) { |
| char resolved_path[PATH_MAX]; |
| const char* original_path = path.c_str(); |
| if (realpath(original_path, resolved_path) != nullptr) { |
| struct stat s; |
| if (stat(resolved_path, &s) == 0) { |
| if (S_ISDIR(s.st_mode)) { |
| resolved_paths->push_back(resolved_path); |
| } else { |
| DL_WARN("Warning: \"%s\" is not a directory (excluding from path)", resolved_path); |
| continue; |
| } |
| } else { |
| DL_WARN("Warning: cannot stat file \"%s\": %s", resolved_path, strerror(errno)); |
| continue; |
| } |
| } else { |
| std::string zip_path; |
| std::string entry_path; |
| |
| std::string normalized_path; |
| |
| if (!normalize_path(original_path, &normalized_path)) { |
| DL_WARN("Warning: unable to normalize \"%s\"", original_path); |
| continue; |
| } |
| |
| if (parse_zip_path(normalized_path.c_str(), &zip_path, &entry_path)) { |
| if (realpath(zip_path.c_str(), resolved_path) == nullptr) { |
| DL_WARN("Warning: unable to resolve \"%s\": %s", zip_path.c_str(), strerror(errno)); |
| continue; |
| } |
| |
| resolved_paths->push_back(std::string(resolved_path) + kZipFileSeparator + entry_path); |
| } |
| } |
| } |
| } |
| |
| static void split_path(const char* path, const char* delimiters, |
| std::vector<std::string>* paths) { |
| if (path != nullptr && path[0] != 0) { |
| *paths = android::base::Split(path, delimiters); |
| } |
| } |
| |
| static void parse_path(const char* path, const char* delimiters, |
| std::vector<std::string>* resolved_paths) { |
| std::vector<std::string> paths; |
| split_path(path, delimiters, &paths); |
| resolve_paths(paths, resolved_paths); |
| } |
| |
| static void parse_LD_LIBRARY_PATH(const char* path) { |
| std::vector<std::string> ld_libary_paths; |
| parse_path(path, ":", &ld_libary_paths); |
| g_default_namespace.set_ld_library_paths(std::move(ld_libary_paths)); |
| } |
| |
| void soinfo::set_dt_runpath(const char* path) { |
| if (!has_min_version(3)) { |
| return; |
| } |
| |
| std::vector<std::string> runpaths; |
| |
| split_path(path, ":", &runpaths); |
| |
| std::string origin = dirname(get_realpath()); |
| // FIXME: add $LIB and $PLATFORM. |
| std::pair<std::string, std::string> substs[] = {{"ORIGIN", origin}}; |
| for (auto&& s : runpaths) { |
| size_t pos = 0; |
| while (pos < s.size()) { |
| pos = s.find("$", pos); |
| if (pos == std::string::npos) break; |
| for (const auto& subst : substs) { |
| const std::string& token = subst.first; |
| const std::string& replacement = subst.second; |
| if (s.substr(pos + 1, token.size()) == token) { |
| s.replace(pos, token.size() + 1, replacement); |
| // -1 to compensate for the ++pos below. |
| pos += replacement.size() - 1; |
| break; |
| } else if (s.substr(pos + 1, token.size() + 2) == "{" + token + "}") { |
| s.replace(pos, token.size() + 3, replacement); |
| pos += replacement.size() - 1; |
| break; |
| } |
| } |
| // Skip $ in case it did not match any of the known substitutions. |
| ++pos; |
| } |
| } |
| |
| resolve_paths(runpaths, &dt_runpath_); |
| } |
| |
| static void parse_LD_PRELOAD(const char* path) { |
| g_ld_preload_names.clear(); |
| if (path != nullptr) { |
| // We have historically supported ':' as well as ' ' in LD_PRELOAD. |
| g_ld_preload_names = android::base::Split(path, " :"); |
| std::remove_if(g_ld_preload_names.begin(), |
| g_ld_preload_names.end(), |
| [] (const std::string& s) { return s.empty(); }); |
| } |
| } |
| |
| static bool realpath_fd(int fd, std::string* realpath) { |
| std::vector<char> buf(PATH_MAX), proc_self_fd(PATH_MAX); |
| __libc_format_buffer(&proc_self_fd[0], proc_self_fd.size(), "/proc/self/fd/%d", fd); |
| if (readlink(&proc_self_fd[0], &buf[0], buf.size()) == -1) { |
| PRINT("readlink(\"%s\") failed: %s [fd=%d]", &proc_self_fd[0], strerror(errno), fd); |
| return false; |
| } |
| |
| *realpath = &buf[0]; |
| return true; |
| } |
| |
| #if defined(__arm__) |
| |
| // For a given PC, find the .so that it belongs to. |
| // Returns the base address of the .ARM.exidx section |
| // for that .so, and the number of 8-byte entries |
| // in that section (via *pcount). |
| // |
| // Intended to be called by libc's __gnu_Unwind_Find_exidx(). |
| // |
| // This function is exposed via dlfcn.cpp and libdl.so. |
| _Unwind_Ptr dl_unwind_find_exidx(_Unwind_Ptr pc, int* pcount) { |
| uintptr_t addr = reinterpret_cast<uintptr_t>(pc); |
| |
| for (soinfo* si = solist; si != 0; si = si->next) { |
| if ((addr >= si->base) && (addr < (si->base + si->size))) { |
| *pcount = si->ARM_exidx_count; |
| return reinterpret_cast<_Unwind_Ptr>(si->ARM_exidx); |
| } |
| } |
| *pcount = 0; |
| return nullptr; |
| } |
| |
| #endif |
| |
| // Here, we only have to provide a callback to iterate across all the |
| // loaded libraries. gcc_eh does the rest. |
| int do_dl_iterate_phdr(int (*cb)(dl_phdr_info* info, size_t size, void* data), void* data) { |
| int rv = 0; |
| for (soinfo* si = solist; si != nullptr; si = si->next) { |
| dl_phdr_info dl_info; |
| dl_info.dlpi_addr = si->link_map_head.l_addr; |
| dl_info.dlpi_name = si->link_map_head.l_name; |
| dl_info.dlpi_phdr = si->phdr; |
| dl_info.dlpi_phnum = si->phnum; |
| rv = cb(&dl_info, sizeof(dl_phdr_info), data); |
| if (rv != 0) { |
| break; |
| } |
| } |
| return rv; |
| } |
| |
| const ElfW(Versym)* soinfo::get_versym(size_t n) const { |
| if (has_min_version(2) && versym_ != nullptr) { |
| return versym_ + n; |
| } |
| |
| return nullptr; |
| } |
| |
| ElfW(Addr) soinfo::get_verneed_ptr() const { |
| if (has_min_version(2)) { |
| return verneed_ptr_; |
| } |
| |
| return 0; |
| } |
| |
| size_t soinfo::get_verneed_cnt() const { |
| if (has_min_version(2)) { |
| return verneed_cnt_; |
| } |
| |
| return 0; |
| } |
| |
| ElfW(Addr) soinfo::get_verdef_ptr() const { |
| if (has_min_version(2)) { |
| return verdef_ptr_; |
| } |
| |
| return 0; |
| } |
| |
| size_t soinfo::get_verdef_cnt() const { |
| if (has_min_version(2)) { |
| return verdef_cnt_; |
| } |
| |
| return 0; |
| } |
| |
| template<typename F> |
| static bool for_each_verdef(const soinfo* si, F functor) { |
| if (!si->has_min_version(2)) { |
| return true; |
| } |
| |
| uintptr_t verdef_ptr = si->get_verdef_ptr(); |
| if (verdef_ptr == 0) { |
| return true; |
| } |
| |
| size_t offset = 0; |
| |
| size_t verdef_cnt = si->get_verdef_cnt(); |
| for (size_t i = 0; i<verdef_cnt; ++i) { |
| const ElfW(Verdef)* verdef = reinterpret_cast<ElfW(Verdef)*>(verdef_ptr + offset); |
| size_t verdaux_offset = offset + verdef->vd_aux; |
| offset += verdef->vd_next; |
| |
| if (verdef->vd_version != 1) { |
| DL_ERR("unsupported verdef[%zd] vd_version: %d (expected 1) library: %s", |
| i, verdef->vd_version, si->get_realpath()); |
| return false; |
| } |
| |
| if ((verdef->vd_flags & VER_FLG_BASE) != 0) { |
| // "this is the version of the file itself. It must not be used for |
| // matching a symbol. It can be used to match references." |
| // |
| // http://www.akkadia.org/drepper/symbol-versioning |
| continue; |
| } |
| |
| if (verdef->vd_cnt == 0) { |
| DL_ERR("invalid verdef[%zd] vd_cnt == 0 (version without a name)", i); |
| return false; |
| } |
| |
| const ElfW(Verdaux)* verdaux = reinterpret_cast<ElfW(Verdaux)*>(verdef_ptr + verdaux_offset); |
| |
| if (functor(i, verdef, verdaux) == true) { |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool soinfo::find_verdef_version_index(const version_info* vi, ElfW(Versym)* versym) const { |
| if (vi == nullptr) { |
| *versym = kVersymNotNeeded; |
| return true; |
| } |
| |
| *versym = kVersymGlobal; |
| |
| return for_each_verdef(this, |
| [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) { |
| if (verdef->vd_hash == vi->elf_hash && |
| strcmp(vi->name, get_string(verdaux->vda_name)) == 0) { |
| *versym = verdef->vd_ndx; |
| return true; |
| } |
| |
| return false; |
| } |
| ); |
| } |
| |
| bool soinfo::find_symbol_by_name(SymbolName& symbol_name, |
| const version_info* vi, |
| const ElfW(Sym)** symbol) const { |
| uint32_t symbol_index; |
| bool success = |
| is_gnu_hash() ? |
| gnu_lookup(symbol_name, vi, &symbol_index) : |
| elf_lookup(symbol_name, vi, &symbol_index); |
| |
| if (success) { |
| *symbol = symbol_index == 0 ? nullptr : symtab_ + symbol_index; |
| } |
| |
| return success; |
| } |
| |
| static bool is_symbol_global_and_defined(const soinfo* si, const ElfW(Sym)* s) { |
| if (ELF_ST_BIND(s->st_info) == STB_GLOBAL || |
| ELF_ST_BIND(s->st_info) == STB_WEAK) { |
| return s->st_shndx != SHN_UNDEF; |
| } else if (ELF_ST_BIND(s->st_info) != STB_LOCAL) { |
| DL_WARN("unexpected ST_BIND value: %d for \"%s\" in \"%s\"", |
| ELF_ST_BIND(s->st_info), si->get_string(s->st_name), si->get_realpath()); |
| } |
| |
| return false; |
| } |
| |
| static const ElfW(Versym) kVersymHiddenBit = 0x8000; |
| |
| static inline bool is_versym_hidden(const ElfW(Versym)* versym) { |
| // the symbol is hidden if bit 15 of versym is set. |
| return versym != nullptr && (*versym & kVersymHiddenBit) != 0; |
| } |
| |
| static inline bool check_symbol_version(const ElfW(Versym) verneed, |
| const ElfW(Versym)* verdef) { |
| return verneed == kVersymNotNeeded || |
| verdef == nullptr || |
| verneed == (*verdef & ~kVersymHiddenBit); |
| } |
| |
| bool soinfo::gnu_lookup(SymbolName& symbol_name, |
| const version_info* vi, |
| uint32_t* symbol_index) const { |
| uint32_t hash = symbol_name.gnu_hash(); |
| uint32_t h2 = hash >> gnu_shift2_; |
| |
| uint32_t bloom_mask_bits = sizeof(ElfW(Addr))*8; |
| uint32_t word_num = (hash / bloom_mask_bits) & gnu_maskwords_; |
| ElfW(Addr) bloom_word = gnu_bloom_filter_[word_num]; |
| |
| *symbol_index = 0; |
| |
| TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p (gnu)", |
| symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); |
| |
| // test against bloom filter |
| if ((1 & (bloom_word >> (hash % bloom_mask_bits)) & (bloom_word >> (h2 % bloom_mask_bits))) == 0) { |
| TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p", |
| symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); |
| |
| return true; |
| } |
| |
| // bloom test says "probably yes"... |
| uint32_t n = gnu_bucket_[hash % gnu_nbucket_]; |
| |
| if (n == 0) { |
| TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p", |
| symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); |
| |
| return true; |
| } |
| |
| // lookup versym for the version definition in this library |
| // note the difference between "version is not requested" (vi == nullptr) |
| // and "version not found". In the first case verneed is kVersymNotNeeded |
| // which implies that the default version can be accepted; the second case results in |
| // verneed = 1 (kVersymGlobal) and implies that we should ignore versioned symbols |
| // for this library and consider only *global* ones. |
| ElfW(Versym) verneed = 0; |
| if (!find_verdef_version_index(vi, &verneed)) { |
| return false; |
| } |
| |
| do { |
| ElfW(Sym)* s = symtab_ + n; |
| const ElfW(Versym)* verdef = get_versym(n); |
| // skip hidden versions when verneed == kVersymNotNeeded (0) |
| if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) { |
| continue; |
| } |
| if (((gnu_chain_[n] ^ hash) >> 1) == 0 && |
| check_symbol_version(verneed, verdef) && |
| strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 && |
| is_symbol_global_and_defined(this, s)) { |
| TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd", |
| symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(s->st_value), |
| static_cast<size_t>(s->st_size)); |
| *symbol_index = n; |
| return true; |
| } |
| } while ((gnu_chain_[n++] & 1) == 0); |
| |
| TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p", |
| symbol_name.get_name(), get_realpath(), reinterpret_cast<void*>(base)); |
| |
| return true; |
| } |
| |
| bool soinfo::elf_lookup(SymbolName& symbol_name, |
| const version_info* vi, |
| uint32_t* symbol_index) const { |
| uint32_t hash = symbol_name.elf_hash(); |
| |
| TRACE_TYPE(LOOKUP, "SEARCH %s in %s@%p h=%x(elf) %zd", |
| symbol_name.get_name(), get_realpath(), |
| reinterpret_cast<void*>(base), hash, hash % nbucket_); |
| |
| ElfW(Versym) verneed = 0; |
| if (!find_verdef_version_index(vi, &verneed)) { |
| return false; |
| } |
| |
| for (uint32_t n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { |
| ElfW(Sym)* s = symtab_ + n; |
| const ElfW(Versym)* verdef = get_versym(n); |
| |
| // skip hidden versions when verneed == 0 |
| if (verneed == kVersymNotNeeded && is_versym_hidden(verdef)) { |
| continue; |
| } |
| |
| if (check_symbol_version(verneed, verdef) && |
| strcmp(get_string(s->st_name), symbol_name.get_name()) == 0 && |
| is_symbol_global_and_defined(this, s)) { |
| TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd", |
| symbol_name.get_name(), get_realpath(), |
| reinterpret_cast<void*>(s->st_value), |
| static_cast<size_t>(s->st_size)); |
| *symbol_index = n; |
| return true; |
| } |
| } |
| |
| TRACE_TYPE(LOOKUP, "NOT FOUND %s in %s@%p %x %zd", |
| symbol_name.get_name(), get_realpath(), |
| reinterpret_cast<void*>(base), hash, hash % nbucket_); |
| |
| *symbol_index = 0; |
| return true; |
| } |
| |
| soinfo::soinfo(android_namespace_t* ns, const char* realpath, |
| const struct stat* file_stat, off64_t file_offset, |
| int rtld_flags) { |
| memset(this, 0, sizeof(*this)); |
| |
| if (realpath != nullptr) { |
| realpath_ = realpath; |
| } |
| |
| flags_ = FLAG_NEW_SOINFO; |
| version_ = SOINFO_VERSION; |
| |
| if (file_stat != nullptr) { |
| this->st_dev_ = file_stat->st_dev; |
| this->st_ino_ = file_stat->st_ino; |
| this->file_offset_ = file_offset; |
| } |
| |
| this->rtld_flags_ = rtld_flags; |
| this->primary_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; |
| |
| while (*name_bytes) { |
| h = (h << 4) + *name_bytes++; |
| g = h & 0xf0000000; |
| h ^= g; |
| h ^= g >> 24; |
| } |
| |
| return h; |
| } |
| |
| uint32_t SymbolName::elf_hash() { |
| if (!has_elf_hash_) { |
| elf_hash_ = calculate_elf_hash(name_); |
| has_elf_hash_ = true; |
| } |
| |
| return elf_hash_; |
| } |
| |
| uint32_t SymbolName::gnu_hash() { |
| if (!has_gnu_hash_) { |
| uint32_t h = 5381; |
| const uint8_t* name = reinterpret_cast<const uint8_t*>(name_); |
| while (*name != 0) { |
| h += (h << 5) + *name++; // h*33 + c = h + h * 32 + c = h + h << 5 + c |
| } |
| |
| gnu_hash_ = h; |
| has_gnu_hash_ = true; |
| } |
| |
| return gnu_hash_; |
| } |
| |
| bool soinfo_do_lookup(soinfo* si_from, const char* name, const version_info* vi, |
| soinfo** si_found_in, const soinfo::soinfo_list_t& global_group, |
| const soinfo::soinfo_list_t& local_group, const ElfW(Sym)** symbol) { |
| SymbolName symbol_name(name); |
| const ElfW(Sym)* s = nullptr; |
| |
| /* "This element's presence in a shared object library alters the dynamic linker's |
| * symbol resolution algorithm for references within the library. Instead of starting |
| * a symbol search with the executable file, the dynamic linker starts from the shared |
| * object itself. If the shared object fails to supply the referenced symbol, the |
| * dynamic linker then searches the executable file and other shared objects as usual." |
| * |
| * http://www.sco.com/developers/gabi/2012-12-31/ch5.dynamic.html |
| * |
| * Note that this is unlikely since static linker avoids generating |
| * relocations for -Bsymbolic linked dynamic executables. |
| */ |
| if (si_from->has_DT_SYMBOLIC) { |
| DEBUG("%s: looking up %s in local scope (DT_SYMBOLIC)", si_from->get_realpath(), name); |
| if (!si_from->find_symbol_by_name(symbol_name, vi, &s)) { |
| return false; |
| } |
| |
| if (s != nullptr) { |
| *si_found_in = si_from; |
| } |
| } |
| |
| // 1. Look for it in global_group |
| if (s == nullptr) { |
| bool error = false; |
| global_group.visit([&](soinfo* global_si) { |
| DEBUG("%s: looking up %s in %s (from global group)", |
| si_from->get_realpath(), name, global_si->get_realpath()); |
| if (!global_si->find_symbol_by_name(symbol_name, vi, &s)) { |
| error = true; |
| return false; |
| } |
| |
| if (s != nullptr) { |
| *si_found_in = global_si; |
| return false; |
| } |
| |
| return true; |
| }); |
| |
| if (error) { |
| return false; |
| } |
| } |
| |
| // 2. Look for it in the local group |
| if (s == nullptr) { |
| bool error = false; |
| local_group.visit([&](soinfo* local_si) { |
| if (local_si == si_from && si_from->has_DT_SYMBOLIC) { |
| // we already did this - skip |
| return true; |
| } |
| |
| DEBUG("%s: looking up %s in %s (from local group)", |
| si_from->get_realpath(), name, local_si->get_realpath()); |
| if (!local_si->find_symbol_by_name(symbol_name, vi, &s)) { |
| error = true; |
| return false; |
| } |
| |
| if (s != nullptr) { |
| *si_found_in = local_si; |
| return false; |
| } |
| |
| return true; |
| }); |
| |
| if (error) { |
| return false; |
| } |
| } |
| |
| if (s != nullptr) { |
| TRACE_TYPE(LOOKUP, "si %s sym %s s->st_value = %p, " |
| "found in %s, base = %p, load bias = %p", |
| si_from->get_realpath(), name, reinterpret_cast<void*>(s->st_value), |
| (*si_found_in)->get_realpath(), reinterpret_cast<void*>((*si_found_in)->base), |
| reinterpret_cast<void*>((*si_found_in)->load_bias)); |
| } |
| |
| *symbol = s; |
| return true; |
| } |
| |
| class ProtectedDataGuard { |
| public: |
| ProtectedDataGuard() { |
| if (ref_count_++ == 0) { |
| protect_data(PROT_READ | PROT_WRITE); |
| } |
| } |
| |
| ~ProtectedDataGuard() { |
| if (ref_count_ == 0) { // overflow |
| __libc_fatal("Too many nested calls to dlopen()"); |
| } |
| |
| if (--ref_count_ == 0) { |
| protect_data(PROT_READ); |
| } |
| } |
| private: |
| void protect_data(int protection) { |
| g_soinfo_allocator.protect_all(protection); |
| g_soinfo_links_allocator.protect_all(protection); |
| g_namespace_allocator.protect_all(protection); |
| g_namespace_list_allocator.protect_all(protection); |
| } |
| |
| static size_t ref_count_; |
| }; |
| |
| size_t ProtectedDataGuard::ref_count_ = 0; |
| |
| // Each size has it's own allocator. |
| template<size_t size> |
| class SizeBasedAllocator { |
| public: |
| static void* alloc() { |
| return allocator_.alloc(); |
| } |
| |
| static void free(void* ptr) { |
| allocator_.free(ptr); |
| } |
| |
| private: |
| static LinkerBlockAllocator allocator_; |
| }; |
| |
| template<size_t size> |
| LinkerBlockAllocator SizeBasedAllocator<size>::allocator_(size); |
| |
| template<typename T> |
| class TypeBasedAllocator { |
| public: |
| static T* alloc() { |
| return reinterpret_cast<T*>(SizeBasedAllocator<sizeof(T)>::alloc()); |
| } |
| |
| static void free(T* ptr) { |
| SizeBasedAllocator<sizeof(T)>::free(ptr); |
| } |
| }; |
| |
| class LoadTask { |
| public: |
| struct deleter_t { |
| void operator()(LoadTask* t) { |
| t->~LoadTask(); |
| TypeBasedAllocator<LoadTask>::free(t); |
| } |
| }; |
| |
| static deleter_t deleter; |
| |
| static LoadTask* create(const char* name, soinfo* needed_by, |
| std::unordered_map<const soinfo*, ElfReader>* readers_map) { |
| LoadTask* ptr = TypeBasedAllocator<LoadTask>::alloc(); |
| return new (ptr) LoadTask(name, needed_by, readers_map); |
| } |
| |
| const char* get_name() const { |
| return name_; |
| } |
| |
| soinfo* get_needed_by() const { |
| return needed_by_; |
| } |
| |
| soinfo* get_soinfo() const { |
| return si_; |
| } |
| |
| void set_soinfo(soinfo* si) { |
| si_ = si; |
| } |
| |
| off64_t get_file_offset() const { |
| return file_offset_; |
| } |
| |
| void set_file_offset(off64_t offset) { |
| file_offset_ = offset; |
| } |
| |
| int get_fd() const { |
| return fd_; |
| } |
| |
| void set_fd(int fd, bool assume_ownership) { |
| fd_ = fd; |
| close_fd_ = assume_ownership; |
| } |
| |
| const android_dlextinfo* get_extinfo() const { |
| return extinfo_; |
| } |
| |
| void set_extinfo(const android_dlextinfo* extinfo) { |
| extinfo_ = extinfo; |
| } |
| |
| bool is_dt_needed() const { |
| return is_dt_needed_; |
| } |
| |
| void set_dt_needed(bool is_dt_needed) { |
| is_dt_needed_ = is_dt_needed; |
| } |
| |
| const ElfReader& get_elf_reader() const { |
| CHECK(si_ != nullptr); |
| return (*elf_readers_map_)[si_]; |
| } |
| |
| ElfReader& get_elf_reader() { |
| CHECK(si_ != nullptr); |
| return (*elf_readers_map_)[si_]; |
| } |
| |
| std::unordered_map<const soinfo*, ElfReader>* get_readers_map() { |
| return elf_readers_map_; |
| } |
| |
| bool read(const char* realpath, off64_t file_size) { |
| ElfReader& elf_reader = get_elf_reader(); |
| return elf_reader.Read(realpath, fd_, file_offset_, file_size); |
| } |
| |
| bool load() { |
| ElfReader& elf_reader = get_elf_reader(); |
| if (!elf_reader.Load(extinfo_)) { |
| return false; |
| } |
| |
| si_->base = elf_reader.load_start(); |
| si_->size = elf_reader.load_size(); |
| si_->set_mapped_by_caller(elf_reader.is_mapped_by_caller()); |
| si_->load_bias = elf_reader.load_bias(); |
| si_->phnum = elf_reader.phdr_count(); |
| si_->phdr = elf_reader.loaded_phdr(); |
| |
| return true; |
| } |
| |
| private: |
| LoadTask(const char* name, soinfo* needed_by, |
| std::unordered_map<const soinfo*, ElfReader>* readers_map) |
| : name_(name), needed_by_(needed_by), si_(nullptr), |
| fd_(-1), close_fd_(false), file_offset_(0), elf_readers_map_(readers_map), |
| is_dt_needed_(false) {} |
| |
| ~LoadTask() { |
| if (fd_ != -1 && close_fd_) { |
| close(fd_); |
| } |
| } |
| |
| const char* name_; |
| soinfo* needed_by_; |
| soinfo* si_; |
| const android_dlextinfo* extinfo_; |
| int fd_; |
| bool close_fd_; |
| off64_t file_offset_; |
| std::unordered_map<const soinfo*, ElfReader>* elf_readers_map_; |
| // TODO(dimitry): needed by workaround for http://b/26394120 (the grey-list) |
| bool is_dt_needed_; |
| // END OF WORKAROUND |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(LoadTask); |
| }; |
| |
| LoadTask::deleter_t LoadTask::deleter; |
| |
| template <typename T> |
| using linked_list_t = LinkedList<T, TypeBasedAllocator<LinkedListEntry<T>>>; |
| |
| typedef linked_list_t<soinfo> SoinfoLinkedList; |
| typedef linked_list_t<const char> StringLinkedList; |
| typedef std::vector<LoadTask*> LoadTaskList; |
| |
| |
| // This function walks down the tree of soinfo dependencies |
| // in breadth-first order and |
| // * calls action(soinfo* si) for each node, and |
| // * terminates walk if action returns false. |
| // |
| // walk_dependencies_tree returns false if walk was terminated |
| // by the action and true otherwise. |
| template<typename F> |
| static bool walk_dependencies_tree(soinfo* root_soinfos[], size_t root_soinfos_size, F action) { |
| SoinfoLinkedList visit_list; |
| SoinfoLinkedList visited; |
| |
| for (size_t i = 0; i < root_soinfos_size; ++i) { |
| visit_list.push_back(root_soinfos[i]); |
| } |
| |
| soinfo* si; |
| while ((si = visit_list.pop_front()) != nullptr) { |
| if (visited.contains(si)) { |
| continue; |
| } |
| |
| if (!action(si)) { |
| return false; |
| } |
| |
| visited.push_back(si); |
| |
| si->get_children().for_each([&](soinfo* child) { |
| visit_list.push_back(child); |
| }); |
| } |
| |
| return true; |
| } |
| |
| |
| static const ElfW(Sym)* dlsym_handle_lookup(soinfo* root, soinfo* skip_until, |
| soinfo** found, SymbolName& symbol_name, |
| const version_info* vi) { |
| const ElfW(Sym)* result = nullptr; |
| bool skip_lookup = skip_until != nullptr; |
| |
| walk_dependencies_tree(&root, 1, [&](soinfo* current_soinfo) { |
| if (skip_lookup) { |
| skip_lookup = current_soinfo != skip_until; |
| return true; |
| } |
| |
| if (!current_soinfo->find_symbol_by_name(symbol_name, vi, &result)) { |
| result = nullptr; |
| return false; |
| } |
| |
| if (result != nullptr) { |
| *found = current_soinfo; |
| return false; |
| } |
| |
| return true; |
| }); |
| |
| return result; |
| } |
| |
| static const ElfW(Sym)* dlsym_linear_lookup(android_namespace_t* ns, |
| const char* name, |
| const version_info* vi, |
| soinfo** found, |
| soinfo* caller, |
| void* handle); |
| |
| // This is used by dlsym(3). It performs symbol lookup only within the |
| // specified soinfo object and its dependencies in breadth first order. |
| static const ElfW(Sym)* dlsym_handle_lookup(soinfo* si, soinfo** found, |
| const char* name, const version_info* vi) { |
| // According to man dlopen(3) and posix docs in the case when si is handle |
| // of the main executable we need to search not only in the executable and its |
| // dependencies but also in all libraries loaded with RTLD_GLOBAL. |
| // |
| // Since RTLD_GLOBAL is always set for the main executable and all dt_needed shared |
| // libraries and they are loaded in breath-first (correct) order we can just execute |
| // dlsym(RTLD_DEFAULT, ...); instead of doing two stage lookup. |
| if (si == somain) { |
| return dlsym_linear_lookup(&g_default_namespace, name, vi, found, nullptr, RTLD_DEFAULT); |
| } |
| |
| SymbolName symbol_name(name); |
| return dlsym_handle_lookup(si, nullptr, found, symbol_name, vi); |
| } |
| |
| /* This is used by dlsym(3) to performs a global symbol lookup. If the |
| start value is null (for RTLD_DEFAULT), the search starts at the |
| beginning of the global solist. Otherwise the search starts at the |
| specified soinfo (for RTLD_NEXT). |
| */ |
| static const ElfW(Sym)* dlsym_linear_lookup(android_namespace_t* ns, |
| const char* name, |
| const version_info* vi, |
| soinfo** found, |
| soinfo* caller, |
| void* handle) { |
| SymbolName symbol_name(name); |
| |
| auto& soinfo_list = ns->soinfo_list(); |
| auto start = soinfo_list.begin(); |
| |
| if (handle == RTLD_NEXT) { |
| if (caller == nullptr) { |
| return nullptr; |
| } else { |
| auto it = soinfo_list.find(caller); |
| CHECK (it != soinfo_list.end()); |
| start = ++it; |
| } |
| } |
| |
| const ElfW(Sym)* s = nullptr; |
| 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 |
| // See http://b/21565766 |
| if ((si->get_rtld_flags() & RTLD_GLOBAL) == 0 && si->get_target_sdk_version() > 22) { |
| continue; |
| } |
| |
| if (!si->find_symbol_by_name(symbol_name, vi, &s)) { |
| return nullptr; |
| } |
| |
| if (s != nullptr) { |
| *found = si; |
| break; |
| } |
| } |
| |
| // If not found - use dlsym_handle_lookup for caller's |
| // local_group unless it is part of the global group in which |
| // case we already did it. |
| if (s == nullptr && caller != nullptr && |
| (caller->get_rtld_flags() & RTLD_GLOBAL) == 0) { |
| return dlsym_handle_lookup(caller->get_local_group_root(), |
| (handle == RTLD_NEXT) ? caller : nullptr, found, symbol_name, vi); |
| } |
| |
| if (s != nullptr) { |
| TRACE_TYPE(LOOKUP, "%s s->st_value = %p, found->base = %p", |
| name, reinterpret_cast<void*>(s->st_value), reinterpret_cast<void*>((*found)->base)); |
| } |
| |
| return s; |
| } |
| |
| soinfo* find_containing_library(const void* p) { |
| ElfW(Addr) address = reinterpret_cast<ElfW(Addr)>(p); |
| for (soinfo* si = solist; si != nullptr; si = si->next) { |
| if (address >= si->base && address - si->base < si->size) { |
| return si; |
| } |
| } |
| return nullptr; |
| } |
| |
| ElfW(Sym)* soinfo::find_symbol_by_address(const void* addr) { |
| return is_gnu_hash() ? gnu_addr_lookup(addr) : elf_addr_lookup(addr); |
| } |
| |
| static bool symbol_matches_soaddr(const ElfW(Sym)* sym, ElfW(Addr) soaddr) { |
| return sym->st_shndx != SHN_UNDEF && |
| soaddr >= sym->st_value && |
| soaddr < sym->st_value + sym->st_size; |
| } |
| |
| ElfW(Sym)* soinfo::gnu_addr_lookup(const void* addr) { |
| ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias; |
| |
| for (size_t i = 0; i < gnu_nbucket_; ++i) { |
| uint32_t n = gnu_bucket_[i]; |
| |
| if (n == 0) { |
| continue; |
| } |
| |
| do { |
| ElfW(Sym)* sym = symtab_ + n; |
| if (symbol_matches_soaddr(sym, soaddr)) { |
| return sym; |
| } |
| } while ((gnu_chain_[n++] & 1) == 0); |
| } |
| |
| return nullptr; |
| } |
| |
| ElfW(Sym)* soinfo::elf_addr_lookup(const void* addr) { |
| ElfW(Addr) soaddr = reinterpret_cast<ElfW(Addr)>(addr) - load_bias; |
| |
| // Search the library's symbol table for any defined symbol which |
| // contains this address. |
| for (size_t i = 0; i < nchain_; ++i) { |
| ElfW(Sym)* sym = symtab_ + i; |
| if (symbol_matches_soaddr(sym, soaddr)) { |
| return sym; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| class ZipArchiveCache { |
| public: |
| ZipArchiveCache() {} |
| ~ZipArchiveCache(); |
| |
| bool get_or_open(const char* zip_path, ZipArchiveHandle* handle); |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ZipArchiveCache); |
| |
| std::unordered_map<std::string, ZipArchiveHandle> cache_; |
| }; |
| |
| bool ZipArchiveCache::get_or_open(const char* zip_path, ZipArchiveHandle* handle) { |
| std::string key(zip_path); |
| |
| auto it = cache_.find(key); |
| if (it != cache_.end()) { |
| *handle = it->second; |
| return true; |
| } |
| |
| int fd = TEMP_FAILURE_RETRY(open(zip_path, O_RDONLY | O_CLOEXEC)); |
| if (fd == -1) { |
| return false; |
| } |
| |
| if (OpenArchiveFd(fd, "", handle) != 0) { |
| // invalid zip-file (?) |
| CloseArchive(handle); |
| close(fd); |
| return false; |
| } |
| |
| cache_[key] = *handle; |
| return true; |
| } |
| |
| ZipArchiveCache::~ZipArchiveCache() { |
| for (const auto& it : cache_) { |
| CloseArchive(it.second); |
| } |
| } |
| |
| static int open_library_in_zipfile(ZipArchiveCache* zip_archive_cache, |
| const char* const input_path, |
| off64_t* file_offset, std::string* realpath) { |
| std::string normalized_path; |
| if (!normalize_path(input_path, &normalized_path)) { |
| return -1; |
| } |
| |
| const char* const path = normalized_path.c_str(); |
| TRACE("Trying zip file open from path \"%s\" -> normalized \"%s\"", input_path, path); |
| |
| // Treat an '!/' separator inside a path as the separator between the name |
| // of the zip file on disk and the subdirectory to search within it. |
| // For example, if path is "foo.zip!/bar/bas/x.so", then we search for |
| // "bar/bas/x.so" within "foo.zip". |
| const char* const separator = strstr(path, kZipFileSeparator); |
| if (separator == nullptr) { |
| return -1; |
| } |
| |
| char buf[512]; |
| if (strlcpy(buf, path, sizeof(buf)) >= sizeof(buf)) { |
| PRINT("Warning: ignoring very long library path: %s", path); |
| return -1; |
| } |
| |
| buf[separator - path] = '\0'; |
| |
| const char* zip_path = buf; |
| const char* file_path = &buf[separator - path + 2]; |
| int fd = TEMP_FAILURE_RETRY(open(zip_path, O_RDONLY | O_CLOEXEC)); |
| if (fd == -1) { |
| return -1; |
| } |
| |
| ZipArchiveHandle handle; |
| if (!zip_archive_cache->get_or_open(zip_path, &handle)) { |
| // invalid zip-file (?) |
| close(fd); |
| return -1; |
| } |
| |
| ZipEntry entry; |
| |
| if (FindEntry(handle, ZipString(file_path), &entry) != 0) { |
| // Entry was not found. |
| close(fd); |
| return -1; |
| } |
| |
| // Check if it is properly stored |
| if (entry.method != kCompressStored || (entry.offset % PAGE_SIZE) != 0) { |
| close(fd); |
| return -1; |
| } |
| |
| *file_offset = entry.offset; |
| |
| if (realpath_fd(fd, realpath)) { |
| *realpath += separator; |
| } else { |
| PRINT("warning: unable to get realpath for the library \"%s\". Will use given path.", |
| normalized_path.c_str()); |
| *realpath = normalized_path; |
| } |
| |
| return fd; |
| } |
| |
| static bool format_path(char* buf, size_t buf_size, const char* path, const char* name) { |
| int n = __libc_format_buffer(buf, buf_size, "%s/%s", path, name); |
| if (n < 0 || n >= static_cast<int>(buf_size)) { |
| PRINT("Warning: ignoring very long library path: %s/%s", path, name); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int open_library_on_paths(ZipArchiveCache* zip_archive_cache, |
| const char* name, off64_t* file_offset, |
| const std::vector<std::string>& paths, |
| std::string* realpath) { |
| for (const auto& path : paths) { |
| char buf[512]; |
| if (!format_path(buf, sizeof(buf), path.c_str(), name)) { |
| continue; |
| } |
| |
| int fd = -1; |
| if (strstr(buf, kZipFileSeparator) != nullptr) { |
| fd = open_library_in_zipfile(zip_archive_cache, buf, file_offset, realpath); |
| } |
| |
| if (fd == -1) { |
| fd = TEMP_FAILURE_RETRY(open(buf, O_RDONLY | O_CLOEXEC)); |
| if (fd != -1) { |
| *file_offset = 0; |
| if (!realpath_fd(fd, realpath)) { |
| PRINT("warning: unable to get realpath for the library \"%s\". Will use given path.", buf); |
| *realpath = buf; |
| } |
| } |
| } |
| |
| if (fd != -1) { |
| return fd; |
| } |
| } |
| |
| return -1; |
| } |
| |
| static int open_library(android_namespace_t* ns, |
| ZipArchiveCache* zip_archive_cache, |
| const char* name, soinfo *needed_by, |
| off64_t* file_offset, std::string* realpath) { |
| TRACE("[ opening %s ]", name); |
| |
| // If the name contains a slash, we should attempt to open it directly and not search the paths. |
| if (strchr(name, '/') != nullptr) { |
| int fd = -1; |
| |
| if (strstr(name, kZipFileSeparator) != nullptr) { |
| fd = open_library_in_zipfile(zip_archive_cache, name, file_offset, realpath); |
| } |
| |
| if (fd == -1) { |
| fd = TEMP_FAILURE_RETRY(open(name, O_RDONLY | O_CLOEXEC)); |
| if (fd != -1) { |
| *file_offset = 0; |
| if (!realpath_fd(fd, realpath)) { |
| PRINT("warning: unable to get realpath for the library \"%s\". Will use given path.", name); |
| *realpath = name; |
| } |
| } |
| } |
| |
| return fd; |
| } |
| |
| // Otherwise we try LD_LIBRARY_PATH first, and fall back to the default library path |
| int fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_ld_library_paths(), realpath); |
| if (fd == -1 && needed_by != nullptr) { |
| fd = open_library_on_paths(zip_archive_cache, name, file_offset, needed_by->get_dt_runpath(), realpath); |
| // Check if the library is accessible |
| if (fd != -1 && !ns->is_accessible(*realpath)) { |
| fd = -1; |
| } |
| } |
| |
| if (fd == -1) { |
| fd = open_library_on_paths(zip_archive_cache, name, file_offset, ns->get_default_library_paths(), realpath); |
| } |
| |
| // TODO(dimitry): workaround for http://b/26394120 (the grey-list) |
| if (fd == -1 && ns != &g_default_namespace && is_greylisted(name, needed_by)) { |
| // try searching for it on default_namespace default_library_path |
| fd = open_library_on_paths(zip_archive_cache, name, file_offset, |
| g_default_namespace.get_default_library_paths(), realpath); |
| } |
| // END OF WORKAROUND |
| |
| return fd; |
| } |
| |
| static const char* fix_dt_needed(const char* dt_needed, const char* sopath __unused) { |
| #if !defined(__LP64__) |
| // Work around incorrect DT_NEEDED entries for old apps: http://b/21364029 |
| if (get_application_target_sdk_version() <= 22) { |
| const char* bname = basename(dt_needed); |
| if (bname != dt_needed) { |
| DL_WARN("library \"%s\" has invalid DT_NEEDED entry \"%s\"", sopath, dt_needed); |
| add_dlwarning(sopath, "invalid DT_NEEDED entry", dt_needed); |
| } |
| |
| return bname; |
| } |
| #endif |
| return dt_needed; |
| } |
| |
| template<typename F> |
| static void for_each_dt_needed(const soinfo* si, F action) { |
| for (const ElfW(Dyn)* d = si->dynamic; d->d_tag != DT_NULL; ++d) { |
| if (d->d_tag == DT_NEEDED) { |
| action(fix_dt_needed(si->get_string(d->d_un.d_val), si->get_realpath())); |
| } |
| } |
| } |
| |
| template<typename F> |
| static void for_each_dt_needed(const ElfReader& elf_reader, F action) { |
| for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) { |
| if (d->d_tag == DT_NEEDED) { |
| action(fix_dt_needed(elf_reader.get_string(d->d_un.d_val), elf_reader.name())); |
| } |
| } |
| } |
| |
| static bool load_library(android_namespace_t* ns, |
| LoadTask* task, |
| LoadTaskList* load_tasks, |
| int rtld_flags, |
| const std::string& realpath) { |
| off64_t file_offset = task->get_file_offset(); |
| const char* name = task->get_name(); |
| const android_dlextinfo* extinfo = task->get_extinfo(); |
| |
| if ((file_offset % PAGE_SIZE) != 0) { |
| DL_ERR("file offset for the library \"%s\" is not page-aligned: %" PRId64, name, file_offset); |
| return false; |
| } |
| if (file_offset < 0) { |
| DL_ERR("file offset for the library \"%s\" is negative: %" PRId64, name, file_offset); |
| return false; |
| } |
| |
| struct stat file_stat; |
| if (TEMP_FAILURE_RETRY(fstat(task->get_fd(), &file_stat)) != 0) { |
| DL_ERR("unable to stat file for the library \"%s\": %s", name, strerror(errno)); |
| return false; |
| } |
| if (file_offset >= file_stat.st_size) { |
| DL_ERR("file offset for the library \"%s\" >= file size: %" PRId64 " >= %" PRId64, |
| name, file_offset, file_stat.st_size); |
| return false; |
| } |
| |
| // Check for symlink and other situations where |
| // file can have different names, unless ANDROID_DLEXT_FORCE_LOAD is set |
| if (extinfo == nullptr || (extinfo->flags & ANDROID_DLEXT_FORCE_LOAD) == 0) { |
| auto predicate = [&](soinfo* si) { |
| return si->get_st_dev() != 0 && |
| si->get_st_ino() != 0 && |
| si->get_st_dev() == file_stat.st_dev && |
| si->get_st_ino() == file_stat.st_ino && |
| si->get_file_offset() == file_offset; |
| }; |
| |
| soinfo* si = ns->soinfo_list().find_if(predicate); |
| |
| // check public namespace |
| if (si == nullptr) { |
| si = g_public_namespace.find_if(predicate); |
| if (si != nullptr) { |
| ns->add_soinfo(si); |
| } |
| } |
| |
| if (si != nullptr) { |
| TRACE("library \"%s\" is already loaded under different name/path \"%s\" - " |
| "will return existing soinfo", name, si->get_realpath()); |
| task->set_soinfo(si); |
| return true; |
| } |
| } |
| |
| if ((rtld_flags & RTLD_NOLOAD) != 0) { |
| DL_ERR("library \"%s\" wasn't loaded and RTLD_NOLOAD prevented it", name); |
| return false; |
| } |
| |
| if (!ns->is_accessible(realpath)) { |
| // TODO(dimitry): workaround for http://b/26394120 - the grey-list |
| const soinfo* needed_by = task->is_dt_needed() ? task->get_needed_by() : nullptr; |
| if (is_greylisted(name, needed_by)) { |
| // print warning only if needed by non-system library |
| if (needed_by == nullptr || !is_system_library(needed_by->get_realpath())) { |
| const soinfo* needed_or_dlopened_by = task->get_needed_by(); |
| const char* sopath = needed_or_dlopened_by == nullptr ? "(unknown)" : |
| needed_or_dlopened_by->get_realpath(); |
| DL_WARN("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"" |
| " - the access is temporarily granted as a workaround for http://b/26394120, note that the access" |
| " will be removed in future releases of Android.", |
| name, realpath.c_str(), sopath, ns->get_name()); |
| add_dlwarning(sopath, "unauthorized access to", name); |
| } |
| } else { |
| // do not load libraries if they are not accessible for the specified namespace. |
| const char* needed_or_dlopened_by = task->get_needed_by() == nullptr ? |
| "(unknown)" : |
| task->get_needed_by()->get_realpath(); |
| |
| DL_ERR("library \"%s\" needed or dlopened by \"%s\" is not accessible for the namespace \"%s\"", |
| name, needed_or_dlopened_by, ns->get_name()); |
| |
| PRINT("library \"%s\" (\"%s\") needed or dlopened by \"%s\" is not accessible for the" |
| " namespace: [name=\"%s\", ld_library_paths=\"%s\", default_library_paths=\"%s\"," |
| " permitted_paths=\"%s\"]", |
| name, realpath.c_str(), |
| needed_or_dlopened_by, |
| ns->get_name(), |
| android::base::Join(ns->get_ld_library_paths(), ':').c_str(), |
| android::base::Join(ns->get_default_library_paths(), ':').c_str(), |
| android::base::Join(ns->get_permitted_paths(), ':').c_str()); |
| return false; |
| } |
| } |
| |
| soinfo* si = soinfo_alloc(ns, realpath.c_str(), &file_stat, file_offset, rtld_flags); |
| if (si == nullptr) { |
| return false; |
| } |
| |
| task->set_soinfo(si); |
| |
| // Read the ELF header and some of the segments. |
| if (!task->read(realpath.c_str(), file_stat.st_size)) { |
| soinfo_free(si); |
| task->set_soinfo(nullptr); |
| return false; |
| } |
| |
| // find and set DT_RUNPATH and dt_soname |
| // Note that these field values are temporary and are |
| // going to be overwritten on soinfo::prelink_image |
| // with values from PT_LOAD segments. |
| const ElfReader& elf_reader = task->get_elf_reader(); |
| for (const ElfW(Dyn)* d = elf_reader.dynamic(); d->d_tag != DT_NULL; ++d) { |
| if (d->d_tag == DT_RUNPATH) { |
| si->set_dt_runpath(elf_reader.get_string(d->d_un.d_val)); |
| } |
| if (d->d_tag == DT_SONAME) { |
| si->set_soname(elf_reader.get_string(d->d_un.d_val)); |
| } |
| } |
| |
| for_each_dt_needed(task->get_elf_reader(), [&](const char* name) { |
| load_tasks->push_back(LoadTask::create(name, si, task->get_readers_map())); |
| }); |
| |
| return true; |
| } |
| |
| static bool load_library(android_namespace_t* ns, |
| LoadTask* task, |
| ZipArchiveCache* zip_archive_cache, |
| LoadTaskList* load_tasks, |
| int rtld_flags) { |
| const char* name = task->get_name(); |
| soinfo* needed_by = task->get_needed_by(); |
| const android_dlextinfo* extinfo = task->get_extinfo(); |
| |
| off64_t file_offset; |
| std::string realpath; |
| if (extinfo != nullptr && (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) != 0) { |
| file_offset = 0; |
| if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) { |
| file_offset = extinfo->library_fd_offset; |
| } |
| |
| if (!realpath_fd(extinfo->library_fd, &realpath)) { |
| PRINT("warning: unable to get realpath for the library \"%s\" by extinfo->library_fd. " |
| "Will use given name.", name); |
| realpath = name; |
| } |
| |
| task->set_fd(extinfo->library_fd, false); |
| task->set_file_offset(file_offset); |
| return load_library(ns, task, load_tasks, rtld_flags, realpath); |
| } |
| |
| // Open the file. |
| int fd = open_library(ns, zip_archive_cache, name, needed_by, &file_offset, &realpath); |
| if (fd == -1) { |
| DL_ERR("library \"%s\" not found", name); |
| return false; |
| } |
| |
| task->set_fd(fd, true); |
| task->set_file_offset(file_offset); |
| |
| return load_library(ns, task, load_tasks, rtld_flags, realpath); |
| } |
| |
| // Returns true if library was found and false in 2 cases |
| // 1. (for default namespace only) The library was found but loaded under different |
| // target_sdk_version (*candidate != nullptr) |
| // 2. The library was not found by soname (*candidate is nullptr) |
| static bool find_loaded_library_by_soname(android_namespace_t* ns, |
| const char* name, soinfo** candidate) { |
| *candidate = nullptr; |
| |
| // Ignore filename with path. |
| if (strchr(name, '/') != nullptr) { |
| return false; |
| } |
| |
| uint32_t target_sdk_version = get_application_target_sdk_version(); |
| |
| return !ns->soinfo_list().visit([&](soinfo* si) { |
| const char* soname = si->get_soname(); |
| if (soname != nullptr && (strcmp(name, soname) == 0)) { |
| // If the library was opened under different target sdk version |
| // skip this step and try to reopen it. The exceptions are |
| // "libdl.so" and global group. There is no point in skipping |
| // them because relocation process is going to use them |
| // in any case. |
| bool is_libdl = si == solist; |
| if (is_libdl || (si->get_dt_flags_1() & DF_1_GLOBAL) != 0 || |
| !si->is_linked() || si->get_target_sdk_version() == target_sdk_version || |
| ns != &g_default_namespace) { |
| *candidate = si; |
| return false; |
| } else if (*candidate == nullptr) { |
| // for the different sdk version in the default namespace |
| // remember the first library. |
| *candidate = si; |
| } |
| } |
| |
| return true; |
| }); |
| } |
| |
| static bool find_library_internal(android_namespace_t* ns, |
| LoadTask* task, |
| ZipArchiveCache* zip_archive_cache, |
| LoadTaskList* load_tasks, |
| int rtld_flags) { |
| soinfo* candidate; |
| |
| if (find_loaded_library_by_soname(ns, task->get_name(), &candidate)) { |
| task->set_soinfo(candidate); |
| return true; |
| } |
| |
| if (ns != &g_default_namespace) { |
| // check public namespace |
| candidate = g_public_namespace.find_if([&](soinfo* si) { |
| return strcmp(task->get_name(), si->get_soname()) == 0; |
| }); |
| |
| if (candidate != nullptr) { |
| ns->add_soinfo(candidate); |
| task->set_soinfo(candidate); |
| return true; |
| } |
| } |
| |
| // Library might still be loaded, the accurate detection |
| // of this fact is done by load_library. |
| TRACE("[ \"%s\" find_loaded_library_by_soname failed (*candidate=%s@%p). Trying harder...]", |
| task->get_name(), candidate == nullptr ? "n/a" : candidate->get_realpath(), candidate); |
| |
| if (load_library(ns, task, zip_archive_cache, load_tasks, rtld_flags)) { |
| return true; |
| } else { |
| // In case we were unable to load the library but there |
| // is a candidate loaded under the same soname but different |
| // sdk level - return it anyways. |
| if (candidate != nullptr) { |
| task->set_soinfo(candidate); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static void soinfo_unload(soinfo* si); |
| static void soinfo_unload(soinfo* soinfos[], size_t count); |
| |
| // TODO: this is slightly unusual way to construct |
| // the global group for relocation. Not every RTLD_GLOBAL |
| // library is included in this group for backwards-compatibility |
| // reasons. |
| // |
| // This group consists of the main executable, LD_PRELOADs |
| // and libraries with the DF_1_GLOBAL flag set. |
| static soinfo::soinfo_list_t make_global_group(android_namespace_t* ns) { |
| soinfo::soinfo_list_t global_group; |
| ns->soinfo_list().for_each([&](soinfo* si) { |
| if ((si->get_dt_flags_1() & DF_1_GLOBAL) != 0) { |
| global_group.push_back(si); |
| } |
| }); |
| |
| return global_group; |
| } |
| |
| // This function provides a list of libraries to be shared |
| // by the namespace. For the default namespace this is the global |
| // group (see make_global_group). For all others this is a group |
| // of RTLD_GLOBAL libraries (which includes the global group from |
| // the default namespace). |
| static soinfo::soinfo_list_t get_shared_group(android_namespace_t* ns) { |
| if (ns == &g_default_namespace) { |
| return make_global_group(ns); |
| } |
| |
| soinfo::soinfo_list_t shared_group; |
| ns->soinfo_list().for_each([&](soinfo* si) { |
| if ((si->get_rtld_flags() & RTLD_GLOBAL) != 0) { |
| shared_group.push_back(si); |
| } |
| }); |
| |
| return shared_group; |
| } |
| |
| static void shuffle(std::vector<LoadTask*>* v) { |
| for (size_t i = 0, size = v->size(); i < size; ++i) { |
| size_t n = size - i; |
| size_t r = arc4random_uniform(n); |
| std::swap((*v)[n-1], (*v)[r]); |
| } |
| } |
| |
| // add_as_children - add first-level loaded libraries (i.e. library_names[], but |
| // not their transitive dependencies) as children of the start_with library. |
| // This is false when find_libraries is called for dlopen(), when newly loaded |
| // libraries must form a disjoint tree. |
| static bool find_libraries(android_namespace_t* ns, |
| soinfo* start_with, |
| const char* const library_names[], |
| size_t library_names_count, soinfo* soinfos[], |
| std::vector<soinfo*>* ld_preloads, |
| size_t ld_preloads_count, int rtld_flags, |
| const android_dlextinfo* extinfo, |
| bool add_as_children) { |
| // Step 0: prepare. |
| LoadTaskList load_tasks; |
| std::unordered_map<const soinfo*, ElfReader> readers_map; |
| |
| for (size_t i = 0; i < library_names_count; ++i) { |
| const char* name = library_names[i]; |
| load_tasks.push_back(LoadTask::create(name, start_with, &readers_map)); |
| } |
| |
| // Construct global_group. |
| soinfo::soinfo_list_t global_group = make_global_group(ns); |
| |
| // If soinfos array is null allocate one on stack. |
| // The array is needed in case of failure; for example |
| // when library_names[] = {libone.so, libtwo.so} and libone.so |
| // is loaded correctly but libtwo.so failed for some reason. |
| // In this case libone.so should be unloaded on return. |
| // See also implementation of failure_guard below. |
| |
| if (soinfos == nullptr) { |
| size_t soinfos_size = sizeof(soinfo*)*library_names_count; |
| soinfos = reinterpret_cast<soinfo**>(alloca(soinfos_size)); |
| memset(soinfos, 0, soinfos_size); |
| } |
| |
| // list of libraries to link - see step 2. |
| size_t soinfos_count = 0; |
| |
| auto scope_guard = make_scope_guard([&]() { |
| for (LoadTask* t : load_tasks) { |
| LoadTask::deleter(t); |
| } |
| }); |
| |
| auto failure_guard = make_scope_guard([&]() { |
| // Housekeeping |
| soinfo_unload(soinfos, soinfos_count); |
| }); |
| |
| ZipArchiveCache zip_archive_cache; |
| |
| // Step 1: expand the list of load_tasks to include |
| // all DT_NEEDED libraries (do not load them just yet) |
| for (size_t i = 0; i<load_tasks.size(); ++i) { |
| LoadTask* task = load_tasks[i]; |
| soinfo* needed_by = task->get_needed_by(); |
| |
| bool is_dt_needed = needed_by != nullptr && (needed_by != start_with || add_as_children); |
| task->set_extinfo(is_dt_needed ? nullptr : extinfo); |
| task->set_dt_needed(is_dt_needed); |
| |
| if(!find_library_internal(ns, task, &zip_archive_cache, &load_tasks, rtld_flags)) { |
| return false; |
| } |
| |
| soinfo* si = task->get_soinfo(); |
| |
| if (is_dt_needed) { |
| needed_by->add_child(si); |
| } |
| |
| if (si->is_linked()) { |
| si->increment_ref_count(); |
| } |
| |
| // When ld_preloads is not null, the first |
| // ld_preloads_count libs are in fact ld_preloads. |
| if (ld_preloads != nullptr && soinfos_count < ld_preloads_count) { |
| ld_preloads->push_back(si); |
| } |
| |
| if (soinfos_count < library_names_count) { |
| soinfos[soinfos_count++] = si; |
| } |
| } |
| |
| // Step 2: Load libraries in random order (see b/24047022) |
| LoadTaskList load_list; |
| for (auto&& task : load_tasks) { |
| soinfo* si = task->get_soinfo(); |
| auto pred = [&](const LoadTask* t) { |
| return t->get_soinfo() == si; |
| }; |
| |
| if (!si->is_linked() && |
| std::find_if(load_list.begin(), load_list.end(), pred) == load_list.end() ) { |
| load_list.push_back(task); |
| } |
| } |
| shuffle(&load_list); |
| |
| for (auto&& task : load_list) { |
| if (!task->load()) { |
| return false; |
| } |
| } |
| |
| // Step 3: pre-link all DT_NEEDED libraries in breadth first order. |
| for (auto&& task : load_tasks) { |
| soinfo* si = task->get_soinfo(); |
| if (!si->is_linked() && !si->prelink_image()) { |
| return false; |
| } |
| } |
| |
| // Step 4: Add LD_PRELOADed libraries to the global group for |
| // future runs. There is no need to explicitly add them to |
| // the global group for this run because they are going to |
| // appear in the local group in the correct order. |
| if (ld_preloads != nullptr) { |
| for (auto&& si : *ld_preloads) { |
| si->set_dt_flags_1(si->get_dt_flags_1() | DF_1_GLOBAL); |
| } |
| } |
| |
| |
| // Step 5: link libraries. |
| soinfo::soinfo_list_t local_group; |
| walk_dependencies_tree( |
| (start_with != nullptr && add_as_children) ? &start_with : soinfos, |
| (start_with != nullptr && add_as_children) ? 1 : soinfos_count, |
| [&] (soinfo* si) { |
| local_group.push_back(si); |
| return true; |
| }); |
| |
| // We need to increment ref_count in case |
| // the root of the local group was not linked. |
| bool was_local_group_root_linked = local_group.front()->is_linked(); |
| |
| bool linked = local_group.visit([&](soinfo* si) { |
| if (!si->is_linked()) { |
| if (!si->link_image(global_group, local_group, extinfo)) { |
| return false; |
| } |
| } |
| |
| return true; |
| }); |
| |
| if (linked) { |
| local_group.for_each([](soinfo* si) { |
| if (!si->is_linked()) { |
| si->set_linked(); |
| } |
| }); |
| |
| failure_guard.disable(); |
| } |
| |
| if (!was_local_group_root_linked) { |
| local_group.front()->increment_ref_count(); |
| } |
| |
| return linked; |
| } |
| |
| static soinfo* find_library(android_namespace_t* ns, |
| const char* name, int rtld_flags, |
| const android_dlextinfo* extinfo, |
| soinfo* needed_by) { |
| soinfo* si; |
| |
| if (name == nullptr) { |
| si = somain; |
| } else if (!find_libraries(ns, needed_by, &name, 1, &si, nullptr, 0, rtld_flags, |
| extinfo, /* add_as_children */ false)) { |
| return nullptr; |
| } |
| |
| return si; |
| } |
| |
| static void soinfo_unload(soinfo* root) { |
| if (root->is_linked()) { |
| root = root->get_local_group_root(); |
| } |
| |
| if (!root->can_unload()) { |
| TRACE("not unloading \"%s\" - the binary is flagged with NODELETE", root->get_realpath()); |
| return; |
| } |
| |
| soinfo_unload(&root, 1); |
| } |
| |
| static void soinfo_unload(soinfo* soinfos[], size_t count) { |
| // Note that the library can be loaded but not linked; |
| // in which case there is no root but we still need |
| // to walk the tree and unload soinfos involved. |
| // |
| // This happens on unsuccessful dlopen, when one of |
| // the DT_NEEDED libraries could not be linked/found. |
| if (count == 0) { |
| return; |
| } |
| |
| soinfo::soinfo_list_t unload_list; |
| for (size_t i = 0; i < count; ++i) { |
| soinfo* si = soinfos[i]; |
| |
| if (si->can_unload()) { |
| size_t ref_count = si->is_linked() ? si->decrement_ref_count() : 0; |
| if (ref_count == 0) { |
| unload_list.push_back(si); |
| } else { |
| TRACE("not unloading '%s' group, decrementing ref_count to %zd", |
| si->get_realpath(), ref_count); |
| } |
| } else { |
| TRACE("not unloading '%s' - the binary is flagged with NODELETE", si->get_realpath()); |
| return; |
| } |
| } |
| |
| // This is used to identify soinfos outside of the load-group |
| // note that we cannot have > 1 in the array and have any of them |
| // linked. This is why we can safely use the first one. |
| soinfo* root = soinfos[0]; |
| |
| soinfo::soinfo_list_t local_unload_list; |
| soinfo::soinfo_list_t external_unload_list; |
| soinfo* si = nullptr; |
| |
| while ((si = unload_list.pop_front()) != nullptr) { |
| if (local_unload_list.contains(si)) { |
| continue; |
| } |
| |
| local_unload_list.push_back(si); |
| |
| if (si->has_min_version(0)) { |
| soinfo* child = nullptr; |
| while ((child = si->get_children().pop_front()) != nullptr) { |
| TRACE("%s@%p needs to unload %s@%p", si->get_realpath(), si, |
| child->get_realpath(), child); |
| |
| if (local_unload_list.contains(child)) { |
| continue; |
| } else if (child->is_linked() && child->get_local_group_root() != root) { |
| external_unload_list.push_back(child); |
| } else { |
| unload_list.push_front(child); |
| } |
| } |
| } else { |
| #if !defined(__work_around_b_24465209__) |
| __libc_fatal("soinfo for \"%s\"@%p has no version", si->get_realpath(), si); |
| #else |
| PRINT("warning: soinfo for \"%s\"@%p has no version", si->get_realpath(), si); |
| for_each_dt_needed(si, [&] (const char* library_name) { |
| TRACE("deprecated (old format of soinfo): %s needs to unload %s", |
| si->get_realpath(), library_name); |
| |
| soinfo* needed = find_library(si->get_primary_namespace(), |
| library_name, RTLD_NOLOAD, nullptr, nullptr); |
| |
| if (needed != nullptr) { |
| // Not found: for example if symlink was deleted between dlopen and dlclose |
| // Since we cannot really handle errors at this point - print and continue. |
| PRINT("warning: couldn't find %s needed by %s on unload.", |
| library_name, si->get_realpath()); |
| return; |
| } else if (local_unload_list.contains(needed)) { |
| // already visited |
| return; |
| } else if (needed->is_linked() && needed->get_local_group_root() != root) { |
| // external group |
| external_unload_list.push_back(needed); |
| } else { |
| // local group |
| unload_list.push_front(needed); |
| } |
| }); |
| #endif |
| } |
| } |
| |
| local_unload_list.for_each([](soinfo* si) { |
| si->call_destructors(); |
| }); |
| |
| while ((si = local_unload_list.pop_front()) != nullptr) { |
| notify_gdb_of_unload(si); |
| soinfo_free(si); |
| } |
| |
| while ((si = external_unload_list.pop_front()) != nullptr) { |
| soinfo_unload(si); |
| } |
| } |
| |
| static std::string symbol_display_name(const char* sym_name, const char* sym_ver) { |
| if (sym_ver == nullptr) { |
| return sym_name; |
| } |
| |
| return std::string(sym_name) + ", version " + sym_ver; |
| } |
| |
| static android_namespace_t* get_caller_namespace(soinfo* caller) { |
| return caller != nullptr ? caller->get_primary_namespace() : g_anonymous_namespace; |
| } |
| |
| void do_android_get_LD_LIBRARY_PATH(char* buffer, size_t buffer_size) { |
| // Use basic string manipulation calls to avoid snprintf. |
| // snprintf indirectly calls pthread_getspecific to get the size of a buffer. |
| // When debug malloc is enabled, this call returns 0. This in turn causes |
| // snprintf to do nothing, which causes libraries to fail to load. |
| // See b/17302493 for further details. |
| // Once the above bug is fixed, this code can be modified to use |
| // snprintf again. |
| size_t required_len = 0; |
| for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) { |
| required_len += strlen(g_default_ld_paths[i]) + 1; |
| } |
| if (buffer_size < required_len) { |
| __libc_fatal("android_get_LD_LIBRARY_PATH failed, buffer too small: " |
| "buffer len %zu, required len %zu", buffer_size, required_len); |
| } |
| char* end = buffer; |
| for (size_t i = 0; g_default_ld_paths[i] != nullptr; ++i) { |
| if (i > 0) *end++ = ':'; |
| end = stpcpy(end, g_default_ld_paths[i]); |
| } |
| } |
| |
| void do_android_update_LD_LIBRARY_PATH(const char* ld_library_path) { |
| parse_LD_LIBRARY_PATH(ld_library_path); |
| } |
| |
| void* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo, |
| void* caller_addr) { |
| soinfo* const caller = find_containing_library(caller_addr); |
| |
| if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) { |
| DL_ERR("invalid flags to dlopen: %x", flags); |
| return nullptr; |
| } |
| |
| android_namespace_t* ns = get_caller_namespace(caller); |
| |
| if (extinfo != nullptr) { |
| if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) { |
| DL_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags); |
| return nullptr; |
| } |
| |
| if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 && |
| (extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) { |
| DL_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without " |
| "ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags); |
| return nullptr; |
| } |
| |
| if ((extinfo->flags & ANDROID_DLEXT_LOAD_AT_FIXED_ADDRESS) != 0 && |
| (extinfo->flags & (ANDROID_DLEXT_RESERVED_ADDRESS | ANDROID_DLEXT_RESERVED_ADDRESS_HINT)) != 0) { |
| DL_ERR("invalid extended flag combination: ANDROID_DLEXT_LOAD_AT_FIXED_ADDRESS is not " |
| "compatible with ANDROID_DLEXT_RESERVED_ADDRESS/ANDROID_DLEXT_RESERVED_ADDRESS_HINT"); |
| return nullptr; |
| } |
| |
| if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) { |
| if (extinfo->library_namespace == nullptr) { |
| DL_ERR("ANDROID_DLEXT_USE_NAMESPACE is set but extinfo->library_namespace is null"); |
| return nullptr; |
| } |
| ns = extinfo->library_namespace; |
| } |
| } |
| |
| std::string asan_name_holder; |
| |
| const char* translated_name = name; |
| if (g_is_asan) { |
| if (file_is_in_dir(name, kSystemLibDir)) { |
| asan_name_holder = std::string(kAsanSystemLibDir) + "/" + basename(name); |
| if (file_exists(asan_name_holder.c_str())) { |
| translated_name = asan_name_holder.c_str(); |
| PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name); |
| } |
| } else if (file_is_in_dir(name, kVendorLibDir)) { |
| asan_name_holder = std::string(kAsanVendorLibDir) + "/" + basename(name); |
| if (file_exists(asan_name_holder.c_str())) { |
| translated_name = asan_name_holder.c_str(); |
| PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name); |
| } |
| } |
| } |
| |
| ProtectedDataGuard guard; |
| soinfo* si = find_library(ns, translated_name, flags, extinfo, caller); |
| if (si != nullptr) { |
| si->call_constructors(); |
| return si->to_handle(); |
| } |
| |
| return nullptr; |
| } |
| |
| int do_dladdr(const void* addr, Dl_info* info) { |
| // Determine if this address can be found in any library currently mapped. |
| soinfo* si = find_containing_library(addr); |
| if (si == nullptr) { |
| return 0; |
| } |
| |
| memset(info, 0, sizeof(Dl_info)); |
| |
| info->dli_fname = si->get_realpath(); |
| // Address at which the shared object is loaded. |
| info->dli_fbase = reinterpret_cast<void*>(si->base); |
| |
| // Determine if any symbol in the library contains the specified address. |
| ElfW(Sym)* sym = si->find_symbol_by_address(addr); |
| if (sym != nullptr) { |
| info->dli_sname = si->get_string(sym->st_name); |
| info->dli_saddr = reinterpret_cast<void*>(si->resolve_symbol_address(sym)); |
| } |
| |
| 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__) |
| if (handle == nullptr) { |
| DL_ERR("dlsym failed: library handle is null"); |
| return false; |
| } |
| #endif |
| |
| if (sym_name == nullptr) { |
| DL_ERR("dlsym failed: symbol name is null"); |
| return false; |
| } |
| |
| soinfo* found = nullptr; |
| const ElfW(Sym)* sym = nullptr; |
| soinfo* caller = find_containing_library(caller_addr); |
| android_namespace_t* ns = get_caller_namespace(caller); |
| |
| version_info vi_instance; |
| version_info* vi = nullptr; |
| |
| if (sym_ver != nullptr) { |
| vi_instance.name = sym_ver; |
| vi_instance.elf_hash = calculate_elf_hash(sym_ver); |
| vi = &vi_instance; |
| } |
| |
| if (handle == RTLD_DEFAULT || handle == RTLD_NEXT) { |
| sym = dlsym_linear_lookup(ns, sym_name, vi, &found, caller, handle); |
| } else { |
| 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) { |
| uint32_t bind = ELF_ST_BIND(sym->st_info); |
| |
| if ((bind == STB_GLOBAL || bind == STB_WEAK) && sym->st_shndx != 0) { |
| *symbol = reinterpret_cast<void*>(found->resolve_symbol_address(sym)); |
| return true; |
| } |
| |
| DL_ERR("symbol \"%s\" found but not global", symbol_display_name(sym_name, sym_ver).c_str()); |
| return false; |
| } |
| |
| DL_ERR("undefined symbol: %s", symbol_display_name(sym_name, sym_ver).c_str()); |
| return false; |
| } |
| |
| 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) { |
| CHECK(public_ns_sonames != nullptr); |
| if (g_public_namespace_initialized) { |
| DL_ERR("public namespace has already been initialized."); |
| return false; |
| } |
| |
| std::vector<std::string> sonames = android::base::Split(public_ns_sonames, ":"); |
| |
| ProtectedDataGuard guard; |
| |
| auto failure_guard = make_scope_guard([&]() { |
| g_public_namespace.clear(); |
| }); |
| |
| for (const auto& soname : sonames) { |
| soinfo* candidate = nullptr; |
| |
| find_loaded_library_by_soname(&g_default_namespace, soname.c_str(), &candidate); |
| |
| if (candidate == nullptr) { |
| DL_ERR("error initializing public namespace: a library with soname \"%s\"" |
| " was not found in the default namespace", soname.c_str()); |
| return false; |
| } |
| |
| candidate->set_nodelete(); |
| g_public_namespace.push_back(candidate); |
| } |
| |
| g_public_namespace_initialized = true; |
| |
| // create anonymous namespace |
| // When the caller is nullptr - create_namespace will take global group |
| // from the anonymous namespace, which is fine because anonymous namespace |
| // is still pointing to the default one. |
| android_namespace_t* anon_ns = |
| create_namespace(nullptr, "(anonymous)", nullptr, anon_ns_library_path, |
| ANDROID_NAMESPACE_TYPE_REGULAR, nullptr, &g_default_namespace); |
| |
| if (anon_ns == nullptr) { |
| g_public_namespace_initialized = false; |
| return false; |
| } |
| g_anonymous_namespace = anon_ns; |
| failure_guard.disable(); |
| return true; |
| } |
| |
| android_namespace_t* create_namespace(const void* caller_addr, |
| const char* name, |
| const char* ld_library_path, |
| const char* default_library_path, |
| uint64_t type, |
| const char* permitted_when_isolated_path, |
| android_namespace_t* parent_namespace) { |
| if (!g_public_namespace_initialized) { |
| DL_ERR("cannot create namespace: public namespace is not initialized."); |
| return nullptr; |
| } |
| |
| if (parent_namespace == nullptr) { |
| // if parent_namespace is nullptr -> set it to the caller namespace |
| soinfo* caller_soinfo = find_containing_library(caller_addr); |
| |
| parent_namespace = caller_soinfo != nullptr ? |
| caller_soinfo->get_primary_namespace() : |
| g_anonymous_namespace; |
| } |
| |
| 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((type & ANDROID_NAMESPACE_TYPE_ISOLATED) != 0); |
| 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)); |
| |
| if ((type & ANDROID_NAMESPACE_TYPE_SHARED) != 0) { |
| // If shared - clone the parent namespace |
| ns->add_soinfos(parent_namespace->soinfo_list()); |
| } else { |
| // If not shared - copy only the shared group |
| ns->add_soinfos(get_shared_group(parent_namespace)); |
| } |
| |
| return ns; |
| } |
| |
| static ElfW(Addr) call_ifunc_resolver(ElfW(Addr) resolver_addr) { |
| typedef ElfW(Addr) (*ifunc_resolver_t)(void); |
| ifunc_resolver_t ifunc_resolver = reinterpret_cast<ifunc_resolver_t>(resolver_addr); |
| ElfW(Addr) ifunc_addr = ifunc_resolver(); |
| TRACE_TYPE(RELO, "Called ifunc_resolver@%p. The result is %p", |
| ifunc_resolver, reinterpret_cast<void*>(ifunc_addr)); |
| |
| return ifunc_addr; |
| } |
| |
| const version_info* VersionTracker::get_version_info(ElfW(Versym) source_symver) const { |
| if (source_symver < 2 || |
| source_symver >= version_infos.size() || |
| version_infos[source_symver].name == nullptr) { |
| return nullptr; |
| } |
| |
| return &version_infos[source_symver]; |
| } |
| |
| void VersionTracker::add_version_info(size_t source_index, |
| ElfW(Word) elf_hash, |
| const char* ver_name, |
| const soinfo* target_si) { |
| if (source_index >= version_infos.size()) { |
| version_infos.resize(source_index+1); |
| } |
| |
| version_infos[source_index].elf_hash = elf_hash; |
| version_infos[source_index].name = ver_name; |
| version_infos[source_index].target_si = target_si; |
| } |
| |
| bool VersionTracker::init_verneed(const soinfo* si_from) { |
| uintptr_t verneed_ptr = si_from->get_verneed_ptr(); |
| |
| if (verneed_ptr == 0) { |
| return true; |
| } |
| |
| size_t verneed_cnt = si_from->get_verneed_cnt(); |
| |
| for (size_t i = 0, offset = 0; i<verneed_cnt; ++i) { |
| const ElfW(Verneed)* verneed = reinterpret_cast<ElfW(Verneed)*>(verneed_ptr + offset); |
| size_t vernaux_offset = offset + verneed->vn_aux; |
| offset += verneed->vn_next; |
| |
| if (verneed->vn_version != 1) { |
| DL_ERR("unsupported verneed[%zd] vn_version: %d (expected 1)", i, verneed->vn_version); |
| return false; |
| } |
| |
| const char* target_soname = si_from->get_string(verneed->vn_file); |
| // find it in dependencies |
| soinfo* target_si = si_from->get_children().find_if([&](const soinfo* si) { |
| return si->get_soname() != nullptr && strcmp(si->get_soname(), target_soname) == 0; |
| }); |
| |
| if (target_si == nullptr) { |
| DL_ERR("cannot find \"%s\" from verneed[%zd] in DT_NEEDED list for \"%s\"", |
| target_soname, i, si_from->get_realpath()); |
| return false; |
| } |
| |
| for (size_t j = 0; j<verneed->vn_cnt; ++j) { |
| const ElfW(Vernaux)* vernaux = reinterpret_cast<ElfW(Vernaux)*>(verneed_ptr + vernaux_offset); |
| vernaux_offset += vernaux->vna_next; |
| |
| const ElfW(Word) elf_hash = vernaux->vna_hash; |
| const char* ver_name = si_from->get_string(vernaux->vna_name); |
| ElfW(Half) source_index = vernaux->vna_other; |
| |
| add_version_info(source_index, elf_hash, ver_name, target_si); |
| } |
| } |
| |
| return true; |
| } |
| |
| bool VersionTracker::init_verdef(const soinfo* si_from) { |
| return for_each_verdef(si_from, |
| [&](size_t, const ElfW(Verdef)* verdef, const ElfW(Verdaux)* verdaux) { |
| add_version_info(verdef->vd_ndx, verdef->vd_hash, |
| si_from->get_string(verdaux->vda_name), si_from); |
| return false; |
| } |
| ); |
| } |
| |
| bool VersionTracker::init(const soinfo* si_from) { |
| if (!si_from->has_min_version(2)) { |
| return true; |
| } |
| |
| return init_verneed(si_from) && init_verdef(si_from); |
| } |
| |
| bool soinfo::lookup_version_info(const VersionTracker& version_tracker, ElfW(Word) sym, |
| const char* sym_name, const version_info** vi) { |
| const ElfW(Versym)* sym_ver_ptr = get_versym(sym); |
| ElfW(Versym) sym_ver = sym_ver_ptr == nullptr ? 0 : *sym_ver_ptr; |
| |
| if (sym_ver != VER_NDX_LOCAL && sym_ver != VER_NDX_GLOBAL) { |
| *vi = version_tracker.get_version_info(sym_ver); |
| |
| if (*vi == nullptr) { |
| DL_ERR("cannot find verneed/verdef for version index=%d " |
| "referenced by symbol \"%s\" at \"%s\"", sym_ver, sym_name, get_realpath()); |
| return false; |
| } |
| } else { |
| // there is no version info |
| *vi = nullptr; |
| } |
| |
| return true; |
| } |
| |
| #if !defined(__mips__) |
| #if defined(USE_RELA) |
| static ElfW(Addr) get_addend(ElfW(Rela)* rela, ElfW(Addr) reloc_addr __unused) { |
| return rela->r_addend; |
| } |
| #else |
| static ElfW(Addr) get_addend(ElfW(Rel)* rel, ElfW(Addr) reloc_addr) { |
| if (ELFW(R_TYPE)(rel->r_info) == R_GENERIC_RELATIVE || |
| ELFW(R_TYPE)(rel->r_info) == R_GENERIC_IRELATIVE) { |
| return *reinterpret_cast<ElfW(Addr)*>(reloc_addr); |
| } |
| return 0; |
| } |
| #endif |
| |
| template<typename ElfRelIteratorT> |
| bool soinfo::relocate(const VersionTracker& version_tracker, ElfRelIteratorT&& rel_iterator, |
| const soinfo_list_t& global_group, const soinfo_list_t& local_group) { |
| for (size_t idx = 0; rel_iterator.has_next(); ++idx) { |
| const auto rel = rel_iterator.next(); |
| if (rel == nullptr) { |
| return false; |
| } |
| |
| ElfW(Word) type = ELFW(R_TYPE)(rel->r_info); |
| ElfW(Word) sym = ELFW(R_SYM)(rel->r_info); |
| |
| ElfW(Addr) reloc = static_cast<ElfW(Addr)>(rel->r_offset + load_bias); |
| ElfW(Addr) sym_addr = 0; |
| const char* sym_name = nullptr; |
| ElfW(Addr) addend = get_addend(rel, reloc); |
| |
| DEBUG("Processing \"%s\" relocation at index %zd", get_realpath(), idx); |
| if (type == R_GENERIC_NONE) { |
| continue; |
| } |
| |
| const ElfW(Sym)* s = nullptr; |
| soinfo* lsi = nullptr; |
| |
| if (sym != 0) { |
| sym_name = get_string(symtab_[sym].st_name); |
| const version_info* vi = nullptr; |
| |
| if (!lookup_version_info(version_tracker, sym, sym_name, &vi)) { |
| return false; |
| } |
| |
| if (!soinfo_do_lookup(this, sym_name, vi, &lsi, global_group, local_group, &s)) { |
| return false; |
| } |
| |
| if (s == nullptr) { |
| // We only allow an undefined symbol if this is a weak reference... |
| s = &symtab_[sym]; |
| if (ELF_ST_BIND(s->st_info) != STB_WEAK) { |
| DL_ERR("cannot locate symbol \"%s\" referenced by \"%s\"...", sym_name, get_realpath()); |
| return false; |
| } |
| |
| /* IHI0044C AAELF 4.5.1.1: |
| |
| Libraries are not searched to resolve weak references. |
| It is not an error for a weak reference to remain unsatisfied. |
| |
| During linking, the value of an undefined weak reference is: |
| - Zero if the relocation type is absolute |
| - The address of the place if the relocation is pc-relative |
| - The address of nominal base address if the relocation |
| type is base-relative. |
| */ |
| |
| switch (type) { |
| case R_GENERIC_JUMP_SLOT: |
| case R_GENERIC_GLOB_DAT: |
| case R_GENERIC_RELATIVE: |
| case R_GENERIC_IRELATIVE: |
| #if defined(__aarch64__) |
| case R_AARCH64_ABS64: |
| case R_AARCH64_ABS32: |
| case R_AARCH64_ABS16: |
| #elif defined(__x86_64__) |
| case R_X86_64_32: |
| case R_X86_64_64: |
| #elif defined(__arm__) |
| case R_ARM_ABS32: |
| #elif defined(__i386__) |
| case R_386_32: |
| #endif |
| /* |
| * The sym_addr was initialized to be zero above, or the relocation |
| * code below does not care about value of sym_addr. |
| * No need to do anything. |
| */ |
| break; |
| #if defined(__x86_64__) |
| case R_X86_64_PC32: |
| sym_addr = reloc; |
| break; |
| #elif defined(__i386__) |
| case R_386_PC32: |
| sym_addr = reloc; |
| break; |
| #endif |
| default: |
| DL_ERR("unknown weak reloc type %d @ %p (%zu)", type, rel, idx); |
| return false; |
| } |
| } else { // We got a definition. |
| #if !defined(__LP64__) |
| // When relocating dso with text_relocation .text segment is |
| // not executable. We need to restore elf flags before resolving |
| // STT_GNU_IFUNC symbol. |
| bool protect_segments = has_text_relocations && |
| lsi == this && |
| ELF_ST_TYPE(s->st_info) == STT_GNU_IFUNC; |
| if (protect_segments) { |
| if (phdr_table_protect_segments(phdr, phnum, load_bias) < 0) { |
| DL_ERR("can't protect segments for \"%s\": %s", |
| get_realpath(), strerror(errno)); |
| return false; |
| } |
| } |
| #endif |
| sym_addr = lsi->resolve_symbol_address(s); |
| #if !defined(__LP64__) |
| if (protect_segments) { |
| if (phdr_table_unprotect_segments(phdr, phnum, load_bias) < 0) { |
| DL_ERR("can't unprotect loadable segments for \"%s\": %s", |
| get_realpath(), strerror(errno)); |
| return false; |
| } |
| } |
| #endif |
| } |
| count_relocation(kRelocSymbol); |
| } |
| |
| switch (type) { |
| case R_GENERIC_JUMP_SLOT: |
| count_relocation(kRelocAbsolute); |
| MARK(rel->r_offset); |
| TRACE_TYPE(RELO, "RELO JMP_SLOT %16p <- %16p %s\n", |
| reinterpret_cast<void*>(reloc), |
| reinterpret_cast<void*>(sym_addr + addend), sym_name); |
| |
| *reinterpret_cast<ElfW(Addr)*>(reloc) = (sym_addr + addend); |
| break |