make vdso function pointers read-only at runtime

Global, writable function pointers are low-hanging fruit for hijacking
control flow with an overflow from a global buffer or an arbitrary write
vulnerability. This moves the function pointer table into a dedicated
page and makes it read-only at runtime, similar to RELRO.

This increases the memory usage of the library by just under one page.
This could be avoided by having the linker load the vdso by replacing
weak symbols. It's not significant within the Zygote spawning model
though because it's read-only after early init.

Change-Id: Id7a49c96c1b15c2e1926528304b3c54a81e78caf
diff --git a/libc/bionic/vdso.cpp b/libc/bionic/vdso.cpp
index a240663..d4aea98 100644
--- a/libc/bionic/vdso.cpp
+++ b/libc/bionic/vdso.cpp
@@ -30,8 +30,14 @@
 #define VDSO_GETTIMEOFDAY_SYMBOL  "__vdso_gettimeofday"
 #endif
 
+#include <errno.h>
+#include <limits.h>
+#include <sys/mman.h>
 #include <time.h>
 
+#include "private/bionic_prctl.h"
+#include "private/libc_logging.h"
+
 extern "C" int __clock_gettime(int, timespec*);
 extern "C" int __gettimeofday(timeval*, struct timezone*);
 
@@ -46,28 +52,33 @@
   VDSO_END
 };
 
-static vdso_entry vdso_entries[] = {
+static const vdso_entry vdso_entries_template[] = {
   [VDSO_CLOCK_GETTIME] = { VDSO_CLOCK_GETTIME_SYMBOL, reinterpret_cast<void*>(__clock_gettime) },
   [VDSO_GETTIMEOFDAY] = { VDSO_GETTIMEOFDAY_SYMBOL, reinterpret_cast<void*>(__gettimeofday) },
 };
 
+static vdso_entry* vdso_entries;
+
 int clock_gettime(int clock_id, timespec* tp) {
-  static int (*vdso_clock_gettime)(int, timespec*) =
+  int (*vdso_clock_gettime)(int, timespec*) =
       reinterpret_cast<int (*)(int, timespec*)>(vdso_entries[VDSO_CLOCK_GETTIME].fn);
   return vdso_clock_gettime(clock_id, tp);
 }
 
 int gettimeofday(timeval* tv, struct timezone* tz) {
-  static int (*vdso_gettimeofday)(timeval*, struct timezone*) =
+  int (*vdso_gettimeofday)(timeval*, struct timezone*) =
       reinterpret_cast<int (*)(timeval*, struct timezone*)>(vdso_entries[VDSO_GETTIMEOFDAY].fn);
   return vdso_gettimeofday(tv, tz);
 }
 
-void __libc_init_vdso() {
+static void __libc_init_vdso_entries() {
+  // Set up the defaults in case we don't have a vdso or can't find everything we're looking for.
+  memcpy(vdso_entries, vdso_entries_template, sizeof(vdso_entries_template));
+
   // Do we have a vdso?
   uintptr_t vdso_ehdr_addr = getauxval(AT_SYSINFO_EHDR);
   ElfW(Ehdr)* vdso_ehdr = reinterpret_cast<ElfW(Ehdr)*>(vdso_ehdr_addr);
-  if (vdso_ehdr == NULL) {
+  if (vdso_ehdr == nullptr) {
     return;
   }
 
@@ -85,7 +96,7 @@
 
   // Where's the dynamic table?
   ElfW(Addr) vdso_addr = 0;
-  ElfW(Dyn)* vdso_dyn = NULL;
+  ElfW(Dyn)* vdso_dyn = nullptr;
   ElfW(Phdr)* vdso_phdr = reinterpret_cast<ElfW(Phdr)*>(vdso_ehdr_addr + vdso_ehdr->e_phoff);
   for (size_t i = 0; i < vdso_ehdr->e_phnum; ++i) {
     if (vdso_phdr[i].p_type == PT_DYNAMIC) {
@@ -94,13 +105,13 @@
       vdso_addr = vdso_ehdr_addr + vdso_phdr[i].p_offset - vdso_phdr[i].p_vaddr;
     }
   }
-  if (vdso_addr == 0 || vdso_dyn == NULL) {
+  if (vdso_addr == 0 || vdso_dyn == nullptr) {
     return;
   }
 
   // Where are the string and symbol tables?
-  const char* strtab = NULL;
-  ElfW(Sym)* symtab = NULL;
+  const char* strtab = nullptr;
+  ElfW(Sym)* symtab = nullptr;
   for (ElfW(Dyn)* d = vdso_dyn; d->d_tag != DT_NULL; ++d) {
     if (d->d_tag == DT_STRTAB) {
       strtab = reinterpret_cast<const char*>(vdso_addr + d->d_un.d_ptr);
@@ -108,7 +119,7 @@
       symtab = reinterpret_cast<ElfW(Sym)*>(vdso_addr + d->d_un.d_ptr);
     }
   }
-  if (strtab == NULL || symtab == NULL) {
+  if (strtab == nullptr || symtab == nullptr) {
     return;
   }
 
@@ -122,6 +133,20 @@
   }
 }
 
+void __libc_init_vdso() {
+  static_assert(PAGE_SIZE >= sizeof(vdso_entries_template), "vdso_entries_template too large");
+  vdso_entries = reinterpret_cast<vdso_entry*>(mmap(nullptr, sizeof(vdso_entries_template), PROT_READ|PROT_WRITE,
+                                                    MAP_ANONYMOUS|MAP_PRIVATE, -1, 0));
+  if (vdso_entries == MAP_FAILED) {
+    __libc_fatal("failed to allocate vdso function pointer table: %s", strerror(errno));
+  }
+  __libc_init_vdso_entries();
+  if (mprotect(vdso_entries, sizeof(vdso_entries_template), PROT_READ) == -1) {
+    __libc_fatal("failed to mprotect PROT_READ vdso function pointer table: %s", strerror(errno));
+  }
+  prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, vdso_entries, sizeof(vdso_entries_template), "vdso function pointer table");
+}
+
 #else
 
 void __libc_init_vdso() {