Skip to content

Position independent binaries caught SIGSEGV after remapping data section (patch applied) #49

@dmitriy-philimonov

Description

@dmitriy-philimonov

How to reproduce
We tried to remap .text and .data segments in mysqld binary (https://github.com/mysql/mysql-server). Finally, we detected that using ld.gold (v1.16, --pie is specified) and remapping .data segment gives us SIGSEGV. Specifying --no-pie heals the problem.

  1. Link mysqld with libhugetlbfs.a and ld.gold (-fuse-ld=gold)
  2. hugeedit --text --data ./mysqld
  3. sudo ./mysqld --version

Main problem
The elflink.c :: get_extracopy doesn't copy partially initialized .bss segment at all, proof:

HUGETLB_DEBUG=1 ./mysqld --version # doesn't work
HUGETLB_DEBUG=1 HUGETLB_MINIMAL_COPY=no ./mysqld --version # works

Bug
elflink.c::keep_symbol filters symbols while .bss is copied, meanwhile "start/end" function's input variables have the address calculated as virtual address from ELF file plus the virtual address given by kernel (non-zero for PIE binaries, typical value for kernel 5.4 is 0x555...554000), while (Elf_Sym*)s->st_value is the virtual address from ELF file only. That's why s->st_value is always less than start in keep_symbol function, and .bss segment is totally skipped.

static inline int keep_symbol(char *strtab, Elf_Sym *s, void *start, void *end)
{
	if ((void *)s->st_value < start) // always true for PIE binaries
		return 0;
....

Since .bss segment is totally skipped, all the partially initialized variables are lost, after .data remapping we get 'rubbish' variables and finally SIGSEGV.

Patch to fix

```diff
diff --git a/elflink.c b/elflink.c
index ce2ed24..153df81 100644
--- a/elflink.c
+++ b/elflink.c
@@ -440,11 +440,12 @@ static int find_numsyms(Elf_Sym *symtab, char *strtab)
  * - Object type (variable)
  * - Non-zero size (zero size means the symbol is just a marker with no data)
  */
-static inline int keep_symbol(char *strtab, Elf_Sym *s, void *start, void *end)
+static inline int keep_symbol(const ElfW(Addr) addr, char *strtab, Elf_Sym *s, void *start, void *end)
 {
-       if ((void *)s->st_value < start)
+       const void* sym_addr = (void*)(s->st_value + addr);
+       if (sym_addr < start)
                return 0;
-       if ((void *)s->st_value > end)
+       if (sym_addr > end)
                return 0;
        if ((ELF_ST_BIND(s->st_info) != STB_GLOBAL) &&
            (ELF_ST_BIND(s->st_info) != STB_WEAK))
@@ -455,7 +456,7 @@ static inline int keep_symbol(char *strtab, Elf_Sym *s, void *start, void *end)
                return 0;

        if (__hugetlbfs_debug)
-               DEBUG("symbol to copy at %p: %s\n", (void *)s->st_value,
+               DEBUG("symbol to copy at %p: %s\n", sym_addr,
                                                strtab + s->st_name);

        return 1;
@@ -514,12 +515,12 @@ static void get_extracopy(struct seg_info *seg, const ElfW(Addr) addr,
        end = start;

        for (sym = symtab; sym < symtab + numsyms; sym++) {
-               if (!keep_symbol(strtab, sym, start, end_orig))
+               if (!keep_symbol(addr, strtab, sym, start, end_orig))
                        continue;

                /* These are the droids we are looking for */
                found_sym = 1;
-               sym_end = (void *)(sym->st_value + sym->st_size);
+               sym_end = (void *)(sym->st_value + addr + sym->st_size);
                if (sym_end > end)
                        end = sym_end;
        }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions