Linux module registry crashes when loading an ELF shared object whose offset-zero PT_LOAD has a non-zero virtual address
Description
Frida Gum 17.9.6 may crash when dlopen() loads a valid ELF shared object whose PT_LOAD segment covering file offset zero has a non-zero p_vaddr.
During a dynamic-loader notification, gum_enumerate_modules_using_r_debug() treats link_map::l_addr as the address of the mapped ELF header:
gum_compute_elf_range_from_ehdr (
(const ElfW(Ehdr) *) lm->l_addr, &range);
However, link_map::l_addr is the ELF load bias. It is only equal to the ELF-header address when the offset-zero PT_LOAD has p_vaddr == 0.
For the layout used by this reproducer:
offset-zero PT_LOAD p_vaddr = 0x400000
mapped ELF header = l_addr + 0x400000
Gum therefore dereferences an unmapped load-bias address and receives SIGSEGV before emitting the module-added signal.
Environment
- Frida Gum: 17.9.6
- OS: glibc-based Linux
- Reproduced on: x86-64 and AArch64
Reproduction
1. Create a shared library with the relevant ELF layout
Save as fixture.c:
__attribute__ ((visibility ("default")))
int
fixture_add_one (int value)
{
return value + 1;
}
Build it:
cc -g -fPIC -shared fixture.c \
-Wl,-Ttext-segment=0x400000 \
-o libnonzero-vaddr.so
Verify its program headers:
readelf -W -l libnonzero-vaddr.so | grep -m1 LOAD
The output should show file offset zero and virtual address 0x400000, similar to:
LOAD 0x000000 0x0000000000400000 ...
2. Create the Gum reproducer
Save as repro.c:
#define _GNU_SOURCE
#include "frida-gum.h"
#include <dlfcn.h>
#include <link.h>
static gboolean saw_test_module = FALSE;
static GumAddress observed_base = 0;
static void
on_module_added (GumModuleRegistry * registry,
GumModule * module,
gpointer user_data)
{
const gchar * path = gum_module_get_path (module);
if (g_str_has_suffix (path, "libnonzero-vaddr.so"))
{
const GumMemoryRange * range = gum_module_get_range (module);
saw_test_module = TRUE;
observed_base = range->base_address;
g_print ("module-added: %s at 0x%" G_GINT64_MODIFIER "x\n",
path, (guint64) observed_base);
}
}
int
main (void)
{
GumModuleRegistry * registry;
gulong handler;
void * handle;
struct link_map * lm = NULL;
GumAddress expected_base;
int result;
gum_init_embedded ();
registry = gum_module_registry_obtain ();
handler = g_signal_connect (registry, "module-added",
G_CALLBACK (on_module_added), NULL);
/*
* An affected Gum build receives SIGSEGV during this call, before
* on_module_added() is invoked.
*/
handle = dlopen ("./libnonzero-vaddr.so", RTLD_NOW | RTLD_LOCAL);
if (handle == NULL)
{
g_printerr ("dlopen failed: %s\n", dlerror ());
return 1;
}
if (dlinfo (handle, RTLD_DI_LINKMAP, &lm) != 0 || lm == NULL)
{
g_printerr ("dlinfo failed: %s\n", dlerror ());
return 1;
}
expected_base = (GumAddress) lm->l_addr + 0x400000;
g_print ("load bias: 0x%" G_GINT64_MODIFIER "x\n",
(guint64) lm->l_addr);
g_print ("expected base: 0x%" G_GINT64_MODIFIER "x\n",
(guint64) expected_base);
g_print ("observed base: 0x%" G_GINT64_MODIFIER "x\n",
(guint64) observed_base);
result = saw_test_module && observed_base == expected_base ? 0 : 2;
g_signal_handler_disconnect (registry, handler);
dlclose (handle);
gum_deinit_embedded ();
return result;
}
3. Build and run
cc -O0 -g repro.c -o repro \
$(pkg-config --cflags --libs frida-gum-1.0) \
-ldl
./repro
Actual result
The process receives SIGSEGV during dlopen(), before the module-added callback is dispatched.
A symbolized backtrace includes:
gum_compute_elf_range_from_ehdr
gum_enumerate_modules_using_r_debug
Expected result
dlopen() succeeds.
- Gum emits the
module-added signal.
- The reported module base is the mapped offset-zero segment:
link_map::l_addr + 0x400000
Linux module registry crashes when loading an ELF shared object whose offset-zero PT_LOAD has a non-zero virtual address
Description
Frida Gum 17.9.6 may crash when
dlopen()loads a valid ELF shared object whosePT_LOADsegment covering file offset zero has a non-zerop_vaddr.During a dynamic-loader notification,
gum_enumerate_modules_using_r_debug()treatslink_map::l_addras the address of the mapped ELF header:However,
link_map::l_addris the ELF load bias. It is only equal to the ELF-header address when the offset-zeroPT_LOADhasp_vaddr == 0.For the layout used by this reproducer:
Gum therefore dereferences an unmapped load-bias address and receives
SIGSEGVbefore emitting themodule-addedsignal.Environment
Reproduction
1. Create a shared library with the relevant ELF layout
Save as
fixture.c:Build it:
Verify its program headers:
readelf -W -l libnonzero-vaddr.so | grep -m1 LOADThe output should show file offset zero and virtual address
0x400000, similar to:2. Create the Gum reproducer
Save as
repro.c:3. Build and run
cc -O0 -g repro.c -o repro \ $(pkg-config --cflags --libs frida-gum-1.0) \ -ldl ./reproActual result
The process receives
SIGSEGVduringdlopen(), before themodule-addedcallback is dispatched.A symbolized backtrace includes:
Expected result
dlopen()succeeds.module-addedsignal.