Various fixes and improvements
This commit is contained in:
20
src/dlhook.c
20
src/dlhook.c
@@ -55,20 +55,19 @@ void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out
|
||||
case DT_PLTREL: dyn.plt_is_rela = lm->l_ld[i].d_un.d_val == DT_RELA; break; // .rel.plt - is Rel or Rela
|
||||
case DT_JMPREL: dyn.plt._ = lm->l_ld[i].d_un.d_ptr; break; // .rel.plt - offset from base
|
||||
case DT_PLTRELSZ: dyn.plt_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - size in bytes
|
||||
case DT_RELENT: dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rel
|
||||
case DT_RELAENT: dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rela
|
||||
case DT_RELENT: if (!dyn.plt_ent_sz) dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rel
|
||||
case DT_RELAENT: if (!dyn.plt_ent_sz) dyn.plt_ent_sz = lm->l_ld[i].d_un.d_val; break; // .rel.plt - entry size if Rela
|
||||
case DT_SYMTAB: dyn.sym = (ElfW(Sym)*)(lm->l_ld[i].d_un.d_val); break; // .dynsym - offset
|
||||
case DT_STRTAB: dyn.str = (const char*)(lm->l_ld[i].d_un.d_val); break; // .dynstr - offset
|
||||
}
|
||||
}
|
||||
NMI_LOG("DT_DYNAMIC: plt_is_rela=%d plt=%p plt_sz=%lu plt_ent_sz=%lu sym=%p str=%p", dyn.plt_is_rela, (void*)(dyn.plt)._, dyn.plt_sz, dyn.plt_ent_sz, dyn.sym, dyn.str);
|
||||
|
||||
ElfW(Xword) plt_ent_n = dyn.plt_sz / dyn.plt_ent_sz;
|
||||
NMI_ASSERT(dyn.plt_ent_sz, "plt_ent_sz is zero");
|
||||
NMI_ASSERT(dyn.plt_sz%dyn.plt_ent_sz == 0, ".rel.plt length is not a multiple of plt_ent_sz");
|
||||
NMI_ASSERT((dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel)) == dyn.plt_ent_sz, "size mismatch (%lu != %lu)", dyn.plt_is_rela ? sizeof(*dyn.plt.rela) : sizeof(*dyn.plt.rel), dyn.plt_ent_sz);
|
||||
|
||||
// parse the dynamic symbol table, resolve symbols to relocations, then GOT entries
|
||||
for (size_t i = 0; i < plt_ent_n; i++) {
|
||||
for (size_t i = 0; i < dyn.plt_sz/dyn.plt_ent_sz; i++) {
|
||||
// note: Rela is a superset of Rel
|
||||
ElfW(Rel) *rel = dyn.plt_is_rela
|
||||
? (ElfW(Rel)*)(&dyn.plt.rela[i])
|
||||
@@ -76,10 +75,6 @@ void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out
|
||||
NMI_ASSERT(ELFW(R_TYPE)(rel->r_info) == R_JUMP_SLOT, "not a jump slot relocation (R_TYPE=%lu)", ELFW(R_TYPE)(rel->r_info));
|
||||
|
||||
ElfW(Sym) *sym = &dyn.sym[ELFW(R_SYM)(rel->r_info)];
|
||||
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented");
|
||||
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) == STT_FUNC, "not a function symbol (ST_TYPE=%d)", ELFW(ST_TYPE)(sym->st_info));
|
||||
NMI_ASSERT(ELFW(ST_BIND)(sym->st_info) == STB_GLOBAL, "not a globally bound symbol (ST_BIND=%d)", ELFW(ST_BIND)(sym->st_info));
|
||||
|
||||
const char *str = &dyn.str[sym->st_name];
|
||||
if (strcmp(str, symname))
|
||||
continue;
|
||||
@@ -87,18 +82,25 @@ void *nmi_dlhook(void *handle, const char *symname, void *target, char **err_out
|
||||
void **gotoff = (void**)(lm->l_addr + rel->r_offset);
|
||||
NMI_LOG("found symbol %s (gotoff=%p [mapped=%p])", str, (void*)(rel->r_offset), gotoff);
|
||||
|
||||
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) != STT_GNU_IFUNC, "STT_GNU_IFUNC not implemented (gotoff=%p)", (void*)(rel->r_offset));
|
||||
NMI_ASSERT(ELFW(ST_TYPE)(sym->st_info) == STT_FUNC, "not a function symbol (ST_TYPE=%d) (gotoff=%p)", ELFW(ST_TYPE)(sym->st_info), (void*)(rel->r_offset));
|
||||
NMI_ASSERT(ELFW(ST_BIND)(sym->st_info) == STB_GLOBAL, "not a globally bound symbol (ST_BIND=%d) (gotoff=%p)", ELFW(ST_BIND)(sym->st_info), (void*)(rel->r_offset));
|
||||
|
||||
// remove memory protection (to bypass RELRO if it is enabled)
|
||||
// note: this doesn't seem to be used on the Kobo, but we might as well stay on the safe side (plus, I test this on my local machine too)
|
||||
// note: the only way to read the current memory protection is to parse /proc/maps, but there's no harm in unprotecting it again if it's not protected
|
||||
// note: we won't put it back afterwards, as if full RELRO (i.e. RTLD_NOW) wasn't enabled, it would cause segfaults when resolving symbols later on
|
||||
NMI_LOG("removing memory protection");
|
||||
long pagesize = sysconf(_SC_PAGESIZE);
|
||||
NMI_ASSERT(pagesize != -1, "could not get memory page size");
|
||||
void *gotpage = (void*)((size_t)(gotoff) & ~(pagesize-1));
|
||||
NMI_ASSERT(!mprotect(gotpage, pagesize, PROT_READ|PROT_WRITE), "could not set memory protection of page %p containing %p to PROT_READ|PROT_WRITE", gotpage, gotoff);
|
||||
|
||||
// replace the target offset
|
||||
NMI_LOG("patching symbol");
|
||||
void *orig = *gotoff;
|
||||
*gotoff = target;
|
||||
NMI_LOG("successfully patched symbol %s (orig=%p, new=%p)", str, orig, target);
|
||||
NMI_RETURN_OK(orig);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ static void *_nmi_failsafe_destroy(void* _fs) {
|
||||
sleep(fs->delay);
|
||||
|
||||
NMI_LOG("failsafe: renaming %s to %s", fs->tmp, fs->orig);
|
||||
if (!rename(fs->tmp, fs->orig))
|
||||
if (rename(fs->tmp, fs->orig))
|
||||
NMI_LOG("error: could not rename lib");
|
||||
|
||||
NMI_LOG("failsafe: freeing memory");
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#define _GNU_SOURCE
|
||||
#define _GNU_SOURCE // program_invocation_short_name
|
||||
#include <link.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h> // program_invocation_short_name
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "failsafe.h"
|
||||
@@ -10,6 +12,11 @@
|
||||
#include "util.h"
|
||||
|
||||
__attribute__((constructor)) void nmi_init() {
|
||||
// for if it's been loaded with LD_PRELOAD rather than as a Qt plugin
|
||||
if (strcmp(program_invocation_short_name, "nickel"))
|
||||
if (!(getenv("LIBNMI_FORCE") && !strcmp(getenv("LIBNMI_FORCE"), "true")))
|
||||
return;
|
||||
|
||||
char *err;
|
||||
|
||||
NMI_LOG("init: creating failsafe");
|
||||
|
||||
Reference in New Issue
Block a user