-
Notifications
You must be signed in to change notification settings - Fork 100
Description
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.
- Link mysqld with libhugetlbfs.a and ld.gold (-fuse-ld=gold)
- hugeedit --text --data ./mysqld
- 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 # worksBug
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;
}