Add support for protected local symbol lookup.

Bug: http://code.google.com/p/android/issues/detail?id=66048
Change-Id: Ib334223df27adad9477fb241ab099c5e26df4a7d
diff --git a/linker/dlfcn.cpp b/linker/dlfcn.cpp
index 529e20f..efb829e 100644
--- a/linker/dlfcn.cpp
+++ b/linker/dlfcn.cpp
@@ -100,30 +100,23 @@
 
   soinfo* found = NULL;
   ElfW(Sym)* sym = NULL;
-  if (handle == RTLD_DEFAULT) {
-    sym = dlsym_linear_lookup(symbol, &found, NULL);
-  } else if (handle == RTLD_NEXT) {
-    void* caller_addr = __builtin_return_address(0);
-    soinfo* si = find_containing_library(caller_addr);
+  void* caller_addr = __builtin_return_address(0);
+  soinfo* caller_si = find_containing_library(caller_addr);
 
+  if (handle == RTLD_DEFAULT) {
+    sym = dlsym_linear_lookup(symbol, &found, NULL, caller_si);
+  } else if (handle == RTLD_NEXT) {
     sym = NULL;
-    if (si && si->next) {
-      sym = dlsym_linear_lookup(symbol, &found, si->next);
+    if (caller_si && caller_si->next) {
+      sym = dlsym_linear_lookup(symbol, &found, caller_si->next, caller_si);
     }
   } else {
     found = reinterpret_cast<soinfo*>(handle);
-    sym = dlsym_handle_lookup(found, symbol);
+    sym = dlsym_handle_lookup(found, symbol, caller_si);
   }
 
-  if (sym != NULL) {
-    unsigned bind = ELF_ST_BIND(sym->st_info);
-
-    if ((bind == STB_GLOBAL || bind == STB_WEAK) && sym->st_shndx != 0) {
-      return reinterpret_cast<void*>(sym->st_value + found->load_bias);
-    }
-
-    __bionic_format_dlerror("symbol found but not global", symbol);
-    return NULL;
+  if (sym != NULL && sym->st_shndx != 0) {
+    return reinterpret_cast<void*>(sym->st_value + found->load_bias);
   } else {
     __bionic_format_dlerror("undefined symbol", symbol);
     return NULL;
diff --git a/linker/linker.cpp b/linker/linker.cpp
index 4588948..442f7ce 100644
--- a/linker/linker.cpp
+++ b/linker/linker.cpp
@@ -125,6 +125,11 @@
     kRelocMax
 };
 
+enum class SymbolLookupScope {
+  kAllowLocal,
+  kExcludeLocal,
+};
+
 #if STATS
 struct linker_stats_t {
     int count[kRelocMax];
@@ -431,7 +436,7 @@
     return rv;
 }
 
-static ElfW(Sym)* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name) {
+static ElfW(Sym)* soinfo_elf_lookup(soinfo* si, unsigned hash, const char* name, const SymbolLookupScope& lookup_scope) {
   ElfW(Sym)* symtab = si->symtab;
   const char* strtab = si->strtab;
 
@@ -442,18 +447,30 @@
     ElfW(Sym)* s = symtab + n;
     if (strcmp(strtab + s->st_name, name)) continue;
 
-    /* only concern ourselves with global and weak symbol definitions */
     switch (ELF_ST_BIND(s->st_info)) {
       case STB_GLOBAL:
       case STB_WEAK:
         if (s->st_shndx == SHN_UNDEF) {
-        continue;
-      }
+          continue;
+        }
 
-      TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
+        TRACE_TYPE(LOOKUP, "FOUND %s in %s (%p) %zd",
                  name, si->name, reinterpret_cast<void*>(s->st_value),
                  static_cast<size_t>(s->st_size));
-      return s;
+        return s;
+      case STB_LOCAL:
+        if (lookup_scope != SymbolLookupScope::kAllowLocal) {
+          continue;
+        }
+        TRACE_TYPE(LOOKUP, "FOUND LOCAL %s in %s (%p) %zd",
+                name, si->name, reinterpret_cast<void*>(s->st_value),
+                static_cast<size_t>(s->st_size));
+        return s;
+      default:
+        const char* msg = "FATAL: Unexpected ST_BIND\n";
+        __libc_format_log(ANDROID_LOG_FATAL, "linker", "%s", msg);
+        write(2, msg, strlen(msg));
+        abort();
     }
   }
 
@@ -484,7 +501,7 @@
          */
 
         if (si == somain) {
-            s = soinfo_elf_lookup(si, elf_hash, name);
+            s = soinfo_elf_lookup(si, elf_hash, name, SymbolLookupScope::kAllowLocal);
             if (s != NULL) {
                 *lsi = si;
                 goto done;
@@ -501,7 +518,7 @@
             if (!si->has_DT_SYMBOLIC) {
                 DEBUG("%s: looking up %s in executable %s",
                       si->name, name, somain->name);
-                s = soinfo_elf_lookup(somain, elf_hash, name);
+                s = soinfo_elf_lookup(somain, elf_hash, name, SymbolLookupScope::kExcludeLocal);
                 if (s != NULL) {
                     *lsi = somain;
                     goto done;
@@ -518,7 +535,7 @@
              * and some the first non-weak definition.   This is system dependent.
              * Here we return the first definition found for simplicity.  */
 
-            s = soinfo_elf_lookup(si, elf_hash, name);
+            s = soinfo_elf_lookup(si, elf_hash, name, SymbolLookupScope::kAllowLocal);
             if (s != NULL) {
                 *lsi = si;
                 goto done;
@@ -532,7 +549,7 @@
             if (si->has_DT_SYMBOLIC) {
                 DEBUG("%s: looking up %s in executable %s after local scope",
                       si->name, name, somain->name);
-                s = soinfo_elf_lookup(somain, elf_hash, name);
+                s = soinfo_elf_lookup(somain, elf_hash, name, SymbolLookupScope::kExcludeLocal);
                 if (s != NULL) {
                     *lsi = somain;
                     goto done;
@@ -543,7 +560,7 @@
 
     /* Next, look for it in the preloads list */
     for (int i = 0; g_ld_preloads[i] != NULL; i++) {
-        s = soinfo_elf_lookup(g_ld_preloads[i], elf_hash, name);
+        s = soinfo_elf_lookup(g_ld_preloads[i], elf_hash, name, SymbolLookupScope::kExcludeLocal);
         if (s != NULL) {
             *lsi = g_ld_preloads[i];
             goto done;
@@ -553,7 +570,7 @@
     for (int i = 0; needed[i] != NULL; i++) {
         DEBUG("%s: looking up %s in %s",
               si->name, name, needed[i]->name);
-        s = soinfo_elf_lookup(needed[i], elf_hash, name);
+        s = soinfo_elf_lookup(needed[i], elf_hash, name, SymbolLookupScope::kExcludeLocal);
         if (s != NULL) {
             *lsi = needed[i];
             goto done;
@@ -582,8 +599,9 @@
    Binary Interface) where in Chapter 5 it discuss resolving "Shared
    Object Dependencies" in breadth first search order.
  */
-ElfW(Sym)* dlsym_handle_lookup(soinfo* si, const char* name) {
-    return soinfo_elf_lookup(si, elfhash(name), name);
+ElfW(Sym)* dlsym_handle_lookup(soinfo* si, const char* name, soinfo* caller) {
+    return soinfo_elf_lookup(si, elfhash(name), name,
+        caller == si ? SymbolLookupScope::kAllowLocal : SymbolLookupScope::kExcludeLocal);
 }
 
 /* This is used by dlsym(3) to performs a global symbol lookup. If the
@@ -591,7 +609,7 @@
    beginning of the global solist. Otherwise the search starts at the
    specified soinfo (for RTLD_NEXT).
  */
-ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* start) {
+ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* start, soinfo* caller) {
   unsigned elf_hash = elfhash(name);
 
   if (start == NULL) {
@@ -600,7 +618,8 @@
 
   ElfW(Sym)* s = NULL;
   for (soinfo* si = start; (s == NULL) && (si != NULL); si = si->next) {
-    s = soinfo_elf_lookup(si, elf_hash, name);
+    s = soinfo_elf_lookup(si, elf_hash, name,
+        caller == si ? SymbolLookupScope::kAllowLocal : SymbolLookupScope::kExcludeLocal);
     if (s != NULL) {
       *found = si;
       break;
diff --git a/linker/linker.h b/linker/linker.h
index 0a72d92..e1112e6 100644
--- a/linker/linker.h
+++ b/linker/linker.h
@@ -234,11 +234,11 @@
 soinfo* do_dlopen(const char* name, int flags, const android_dlextinfo* extinfo);
 void do_dlclose(soinfo* si);
 
-ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* start);
+ElfW(Sym)* dlsym_linear_lookup(const char* name, soinfo** found, soinfo* start, soinfo* caller_si);
 soinfo* find_containing_library(const void* addr);
 
 ElfW(Sym)* dladdr_find_symbol(soinfo* si, const void* addr);
-ElfW(Sym)* dlsym_handle_lookup(soinfo* si, const char* name);
+ElfW(Sym)* dlsym_handle_lookup(soinfo* si, const char* name, soinfo* caller_si);
 
 void debuggerd_init();
 extern "C" abort_msg_t* g_abort_message;
diff --git a/tests/dlfcn_test.cpp b/tests/dlfcn_test.cpp
index 8b89183..18963cf 100644
--- a/tests/dlfcn_test.cpp
+++ b/tests/dlfcn_test.cpp
@@ -50,6 +50,20 @@
   ASSERT_EQ(0, dlclose(self));
 }
 
+TEST(dlfcn, dlsym_local_symbol) {
+  void* handle = dlopen("libtest_local_symbol.so", RTLD_NOW);
+  ASSERT_TRUE(handle != NULL);
+  dlerror();
+  void* sym = dlsym(handle, "private_taxicab_number");
+  ASSERT_TRUE(sym == NULL);
+  ASSERT_STREQ("undefined symbol: private_taxicab_number", dlerror());
+
+  uint32_t (*f)(void);
+  f = reinterpret_cast<uint32_t (*)(void)>(dlsym(handle, "dlsym_local_symbol_get_taxicab_number_using_dlsym"));
+  ASSERT_TRUE(f != NULL);
+  ASSERT_EQ(1729, f());
+}
+
 TEST(dlfcn, dlopen_noload) {
   void* handle = dlopen("libtest_simple.so", RTLD_NOW | RTLD_NOLOAD);
   ASSERT_TRUE(handle == NULL);
diff --git a/tests/libs/Android.mk b/tests/libs/Android.mk
index 67ea562..efce5b5 100644
--- a/tests/libs/Android.mk
+++ b/tests/libs/Android.mk
@@ -88,6 +88,20 @@
 build_target := SHARED_LIBRARY
 include $(TEST_PATH)/Android.build.mk
 
+# -----------------------------------------------------------------------------
+# Library used to test local symbol lookup
+# -----------------------------------------------------------------------------
+libtest_local_symbol_src_files := \
+    dlsym_local_symbol_private.cpp \
+    dlsym_local_symbol_public.cpp
+
+module := libtest_local_symbol
+build_target := SHARED_LIBRARY
+libtest_local_symbol_ldflags := -Wl,--version-script=$(LOCAL_PATH)/dlsym_local_symbol.map
+libtest_local_symbol_cppflags := -std=gnu++11
+libtest_local_symbol_shared_libraries_target := libdl
+build_type := target
+include $(TEST_PATH)/Android.build.mk
 
 # -----------------------------------------------------------------------------
 # Library used by atexit tests
diff --git a/tests/libs/dlsym_local_symbol.map b/tests/libs/dlsym_local_symbol.map
new file mode 100644
index 0000000..58a2299
--- /dev/null
+++ b/tests/libs/dlsym_local_symbol.map
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+LIBTEST_LOCAL_SYMBOL_1.0 {
+  global:
+    dlsym_local_symbol_get_taxicab_number;
+    dlsym_local_symbol_get_taxicab_number_using_dlsym;
+  local:
+    *;
+};
diff --git a/tests/libs/dlsym_local_symbol_private.cpp b/tests/libs/dlsym_local_symbol_private.cpp
new file mode 100644
index 0000000..2587508
--- /dev/null
+++ b/tests/libs/dlsym_local_symbol_private.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <stdio.h>
+
+// This symbol is declared local in
+// the linker version map: libdlsym_local_symbol.map.
+// It should not be visible from the outside.
+extern "C" const uint32_t __attribute__ ((visibility ("protected"))) private_taxicab_number = 1729;
diff --git a/tests/libs/dlsym_local_symbol_public.cpp b/tests/libs/dlsym_local_symbol_public.cpp
new file mode 100644
index 0000000..d9da32a
--- /dev/null
+++ b/tests/libs/dlsym_local_symbol_public.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <stdio.h>
+
+extern const uint32_t private_taxicab_number;
+
+extern "C" {
+uint32_t dlsym_local_symbol_get_taxicab_number();
+uint32_t dlsym_local_symbol_get_taxicab_number_using_dlsym();
+}
+
+uint32_t dlsym_local_symbol_get_taxicab_number() {
+  return private_taxicab_number;
+}
+
+// Let's make sure that dlsym works correctly for local symbol
+uint32_t dlsym_local_symbol_get_taxicab_number_using_dlsym() {
+  dlerror();
+  uint32_t* ptr = reinterpret_cast<uint32_t*>(dlsym(RTLD_DEFAULT, "private_taxicab_number"));
+  if (ptr == nullptr) {
+    const char* dlerr = dlerror();
+    if (dlerr != nullptr) {
+      fprintf(stderr, "dlsym error: %s\n", dlerr);
+    } else {
+      fprintf(stderr, "dlsym returned NULL with no dlerror.\n");
+    }
+    return 0;
+  }
+
+  return *ptr;
+}