Kirill Smelkov
a92c3148f6
doc/04_elf_hooking: Fix segmentation fault on systems where libm.so is built with GNU_IFUNC symbols
...
Hello up there. First of all thanks for LIEF - it looks to be very
handy. I've discovered it recently and started to go through the
tutorial, but quickly hit Segmentation Fault instead of successful
result in 04_elf_hooking, and in https://github.com/lief-project/tutorials/tree/master/04_ELF_hooking
(1.venv) kirr@deca:~/src/tools/exe/LIEF-tutorials/04_ELF_hooking$ make run
unset LD_LIBRARY_PATH
./do_math.bin 2
cos(2) = -0.416147
python insert_hook.py
Hook inserted at VA: 0x344000
Change cos!33640 -> cos!3440d0
LD_LIBRARY_PATH=. ./do_math.bin 2
make: *** [Makefile:17: run] Segmentation fault (core dumped)
Inspecting with gdb showed that this segmentation fault happens
because cos@got.plt entry is being set with 0x0000000a address even
though everything looks more or less ok in readelf output of updated
libm.so . Further running the example in gdb with setting hardware
watchpoint on cos@got.plt lead to seeing it is being set, expectedly, by
_dl_fixup in ld.so . To investigate I've built glibc with ld.so from
sources, added debugging prints and saw the following:
...
219551: XXX DL_FIXUP 70
219551: symbol=cos; lookup in file=./do_math.bin [0]
219551: symbol=cos; lookup in file=./libm.so.6 [0]
219551: -> found it:
219551: sym @7f1020de6e88:
219551: .st_shndx: 15
219551: .st_value: 3440d0
219551: .st_size: 4b
219551: (weak)
219551: (global)
219551: map @7f10211260c0:
219551: .l_name ./libm.so.6:
219551: .l_addr 7f1020de1000:
219551: .l_ld:
219551: .d_tag 1:
219551: .d_val 1:
219551: .d_ptr 1:
219551: binding file ./do_math.bin [0] to ./libm.so.6 [0]: normal symbol `cos' [GLIBC_2.2.5]
219551: .ref @7f1020de6e88:
219551: .map @7f10211260c0:
219551: (after lookup)
219551: value: 7f10211250d0 (= addr) <-- NOTE
219551: value': 7f10211250d0 <-- NOTE
219551: ivalue: a <-- NOTE
219551: (before fixup_plt)
219551: (after fixup_plt)
Segmentation fault (core dumped)
Here we see that address of updated `cos` symbol is first correctly computed,
but after seeing that cos.st_type is GNU_IFUNC, ld.so invokes its code and uses
return value as the address to actually put into got.plt:
https://sourceware.org/git/?p=glibc.git;a=blob;f=elf/dl-runtime.c;h=d35a725415554337746f562c3f9b16ae8295613c#l123
Since the code we built in hook uses regular FUNC - not GNU_IFUNC - binding
protocol for dynamic linking, this leads to crashes, as `hook.cos` code is used
as ifunc resolver which it did not intended to be.
The fix is simple: explicitly update hooked symbol type to be normal FUNC. Then
ld.so will use regular binding procedure for injected code irregardless of
whether libm.cos was FUNC or GNU_IFUNC.
Fixes: https://github.com/lief-project/LIEF/issues/380
Updates: https://github.com/lief-project/tutorials/issues/4
Appendix I. ld.so debug print patch
-----------------------------------
```diff
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
index 05f36a2507..0cb3695bdc 100644
--- a/elf/dl-lookup.c
+++ b/elf/dl-lookup.c
@@ -456,22 +456,34 @@ do_lookup_x (const char *undef_name, unsigned int new_hash,
if (sym != NULL)
{
found_it:
+ _dl_debug_printf(" -> found it:\n");
+ _dl_debug_printf(" sym @%lx:\n", (unsigned long)sym);
+ _dl_debug_printf(" .st_shndx: %d\n", sym->st_shndx);
+ _dl_debug_printf(" .st_value: %lx\n", sym->st_value);
+ _dl_debug_printf(" .st_size: %lx\n", sym->st_size);
+
/* Hidden and internal symbols are local, ignore them. */
- if (__glibc_unlikely (dl_symbol_visibility_binds_local_p (sym)))
+ if (__glibc_unlikely (dl_symbol_visibility_binds_local_p (sym))) {
+ _dl_debug_printf(" (local -> skip)\n");
goto skip;
+ }
- if (ELFW(ST_VISIBILITY) (sym->st_other) == STV_PROTECTED)
+ if (ELFW(ST_VISIBILITY) (sym->st_other) == STV_PROTECTED) {
+ _dl_debug_printf(" (protected)\n");
_dl_check_protected_symbol (undef_name, undef_map, ref, map,
type_class);
+ }
switch (ELFW(ST_BIND) (sym->st_info))
{
case STB_WEAK:
+ _dl_debug_printf(" (weak)\n");
/* Weak definition. Use this value if we don't find another. */
if (__glibc_unlikely (GLRO(dl_dynamic_weak)))
{
if (! result->s)
{
+ _dl_debug_printf(" (weak -> already)\n");
result->s = sym;
result->m = (struct link_map *) map;
}
@@ -479,18 +491,30 @@ do_lookup_x (const char *undef_name, unsigned int new_hash,
}
/* FALLTHROUGH */
case STB_GLOBAL:
+ _dl_debug_printf(" (global)\n");
/* Global definition. Just what we need. */
result->s = sym;
result->m = (struct link_map *) map;
+
+ _dl_debug_printf(" map @%lx:\n", (unsigned long)map);
+ _dl_debug_printf(" .l_name %s:\n", map->l_name);
+ _dl_debug_printf(" .l_addr %lx:\n", map->l_addr);
+ _dl_debug_printf(" .l_ld:\n");
+ _dl_debug_printf(" .d_tag %lx:\n", map->l_ld->d_tag);
+ _dl_debug_printf(" .d_val %lx:\n", map->l_ld->d_un.d_val);
+ _dl_debug_printf(" .d_ptr %lx:\n", map->l_ld->d_un.d_ptr);
+
return 1;
case STB_GNU_UNIQUE:;
+ _dl_debug_printf(" (unique)\n");
do_lookup_unique (undef_name, new_hash, (struct link_map *) map,
result, type_class, sym, strtab, ref,
undef_map, flags);
return 1;
default:
+ _dl_debug_printf(" (default)\n");
/* Local symbols are ignored. */
break;
}
@@ -871,6 +895,9 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
}
+ _dl_debug_printf(".ref @%lx:\n", (unsigned long)current_value.s);
+ _dl_debug_printf(".map @%lx:\n", (unsigned long)current_value.m);
+
*ref = current_value.s;
return LOOKUP_VALUE (current_value.m);
}
diff --git a/elf/dl-runtime.c b/elf/dl-runtime.c
index d35a725415..3f2771c5de 100644
--- a/elf/dl-runtime.c
+++ b/elf/dl-runtime.c
@@ -59,6 +59,8 @@ _dl_fixup (
lookup_t result;
DL_FIXUP_VALUE_TYPE value;
+ _dl_debug_printf("XXX DL_FIXUP %d\n", sym->st_name);
+
/* Sanity check that we're really looking at a PLT relocation. */
assert (ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
@@ -95,6 +97,8 @@ _dl_fixup (
result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,
version, ELF_RTYPE_CLASS_PLT, flags, NULL);
+ _dl_debug_printf("(after lookup)\n");
+
/* We are done with the global scope. */
if (!RTLD_SINGLE_THREAD_P)
THREAD_GSCOPE_RESET_FLAG ();
@@ -108,9 +112,11 @@ _dl_fixup (
offset. */
value = DL_FIXUP_MAKE_VALUE (result,
SYMBOL_ADDRESS (result, sym, false));
+ _dl_debug_printf("value: %lx (= addr)\n", value);
}
else
{
+ _dl_debug_printf("(make value branch 2)\n");
/* We already found the symbol. The module (and therefore its load
address) is also known. */
value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));
@@ -119,10 +125,14 @@ _dl_fixup (
/* And now perhaps the relocation addend. */
value = elf_machine_plt_value (l, reloc, value);
+ _dl_debug_printf("value': %lx\n", value);
+ // XXX here
if (sym != NULL
- && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0))
+ && __builtin_expect (ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC, 0)) {
value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));
+ _dl_debug_printf("ivalue: %lx\n", value);
+ }
#ifdef SHARED
/* Auditing checkpoint: we have a new binding. Provide the auditing
@@ -159,7 +169,12 @@ _dl_fixup (
if (__glibc_unlikely (GLRO(dl_bind_not)))
return value;
- return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
+ _dl_debug_printf("(before fixup_plt)\n");
+ //return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
+ DL_FIXUP_VALUE_TYPE xxx;
+ xxx = elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);
+ _dl_debug_printf("(after fixup_plt)\n");
+ return xxx;
}
#ifndef PROF
@@ -175,6 +190,8 @@ _dl_profile_fixup (
{
void (*mcount_fct) (ElfW(Addr), ElfW(Addr)) = _dl_mcount;
+ _dl_debug_printf("XXX DL_PROFILE_FIXUP ...\n");
+
if (l->l_reloc_result == NULL)
{
/* BZ #14843 : ELF_DYNAMIC_RELOCATE is called before l_reloc_result
```
2023-04-06 16:38:04 +03:00