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
This commit is contained in:
parent
60bc90909a
commit
df1a3c6d21
@ -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 @@ enum {
|
||||
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 @@ void __libc_init_vdso() {
|
||||
|
||||
// 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 @@ void __libc_init_vdso() {
|
||||
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 @@ void __libc_init_vdso() {
|
||||
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() {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user