fix dynamic linker mapping/clearing bss in first/only LOAD segment
authorRich Felker <dalias@aerifal.cx>
Tue, 26 Jun 2018 16:15:13 +0000 (12:15 -0400)
committerRich Felker <dalias@aerifal.cx>
Tue, 26 Jun 2018 16:22:29 +0000 (12:22 -0400)
writable load segments can have size-in-memory larger than their size
in the ELF file, representing bss or equivalent. the initial partial
page has to be zero-filled, and additional anonymous pages have to be
mapped such that accesses don't failt with SIGBUS.

map_library skips redundant MAP_FIXED mapping of the initial
(lowest-address) segment when processing LOAD segments since it was
already mapped when reserving the virtual address range, but in doing
so, inadvertently also skipped the code to fill/map bss. typical
executable and library files have two or more LOAD segments, and the
first one is text/rodata (non-writable) and thus has no bss, but it is
syntactically valid for an ELF program/library to put its writable
segment first, or to have only one segment (everything writable). the
binutils bfd-based linker has been observed to create such programs in
the presence of unusual sections or linker scripts.

fix by moving only the mmap_fixed operation under the conditional
rather than skipping the remainder of the loop body. add a check to
avoid bss processing in the case where the segment is not writable;
this should not happen, but if it does, the change would be a crashing
regression without this check.

ldso/dynlink.c

index c6216b7c703f1b61b436d90ac77b5ae5f3c457e8..eecbddb5fbff2050be46a6e951e58366a7a0c784 100644 (file)
@@ -706,18 +706,17 @@ static void *map_library(int fd, struct dso *dso)
                        dso->phnum = eh->e_phnum;
                        dso->phentsize = eh->e_phentsize;
                }
-               /* Reuse the existing mapping for the lowest-address LOAD */
-               if ((ph->p_vaddr & -PAGE_SIZE) == addr_min && !DL_NOMMU_SUPPORT)
-                       continue;
                this_min = ph->p_vaddr & -PAGE_SIZE;
                this_max = ph->p_vaddr+ph->p_memsz+PAGE_SIZE-1 & -PAGE_SIZE;
                off_start = ph->p_offset & -PAGE_SIZE;
                prot = (((ph->p_flags&PF_R) ? PROT_READ : 0) |
                        ((ph->p_flags&PF_W) ? PROT_WRITE: 0) |
                        ((ph->p_flags&PF_X) ? PROT_EXEC : 0));
-               if (mmap_fixed(base+this_min, this_max-this_min, prot, MAP_PRIVATE|MAP_FIXED, fd, off_start) == MAP_FAILED)
-                       goto error;
-               if (ph->p_memsz > ph->p_filesz) {
+               /* Reuse the existing mapping for the lowest-address LOAD */
+               if ((ph->p_vaddr & -PAGE_SIZE) != addr_min || DL_NOMMU_SUPPORT)
+                       if (mmap_fixed(base+this_min, this_max-this_min, prot, MAP_PRIVATE|MAP_FIXED, fd, off_start) == MAP_FAILED)
+                               goto error;
+               if (ph->p_memsz > ph->p_filesz && (ph->p_flags&PF_W)) {
                        size_t brk = (size_t)base+ph->p_vaddr+ph->p_filesz;
                        size_t pgbrk = brk+PAGE_SIZE-1 & -PAGE_SIZE;
                        memset((void *)brk, 0, pgbrk-brk & PAGE_SIZE-1);