rtld: Sync 2/7 - Remove potential map leakage
authorJohn Marino <draco@marino.st>
Wed, 12 Jun 2013 11:15:29 +0000 (13:15 +0200)
committerJohn Marino <draco@marino.st>
Thu, 13 Jun 2013 11:12:07 +0000 (13:12 +0200)
Eliminate the static buffer used to read the first page of the mapped
object, and eliminate the pread(2) call as well.  Mmap the first page
of the object temporarily and unmap it on error or last use.  Potentially
this leaves a one page gap between succeeding dlopen(3), but there are
other mmap(2) consumers as well.

This fixes several cases where the whole mapping of the object leaked
upon error.  The MAP_PREFAULT_READ code had to be skipped because the
mmap on DragonFly doesn't support this flag.

----

Map libraries linked with -Ttext-segment=base_addr at base_addr.
Normal libraries have a base address of zero and are unaffected by this
change.

Taken from:
FreeBSD SVN 237058 (14 JUN 2012)
FreeBSD SVN 247396 (27 FEB 2013)

libexec/rtld-elf/map_object.c

index 14cedfc..84fcf68 100644 (file)
@@ -38,7 +38,7 @@
 #include "debug.h"
 #include "rtld.h"
 
-static Elf_Ehdr *get_elf_header (int, const char *);
+static Elf_Ehdr *get_elf_header(int, const char *);
 static int convert_prot(int);  /* Elf flags -> mmap protection */
 static int convert_flags(int); /* Elf flags -> mmap flags */
 
@@ -121,7 +121,7 @@ map_object(int fd, const char *path, const struct stat *sb)
            if ((segs[nsegs]->p_align & (PAGE_SIZE - 1)) != 0) {
                _rtld_error("%s: PT_LOAD segment %d not page-aligned",
                    path, nsegs);
-               return NULL;
+               goto error;
            }
            break;
 
@@ -160,12 +160,12 @@ map_object(int fd, const char *path, const struct stat *sb)
     }
     if (phdyn == NULL) {
        _rtld_error("%s: object is not dynamically-linked", path);
-       return NULL;
+       goto error;
     }
 
     if (nsegs < 0) {
        _rtld_error("%s: too few PT_LOAD segments", path);
-       return NULL;
+       goto error;
     }
 
     /*
@@ -175,20 +175,19 @@ map_object(int fd, const char *path, const struct stat *sb)
     base_vaddr = trunc_page(segs[0]->p_vaddr);
     base_vlimit = round_page(segs[nsegs]->p_vaddr + segs[nsegs]->p_memsz);
     mapsize = base_vlimit - base_vaddr;
-    base_addr = hdr->e_type == ET_EXEC ? (caddr_t) base_vaddr : NULL;
+    base_addr = (caddr_t) base_vaddr;
 
     mapbase = mmap(base_addr, mapsize, PROT_NONE, MAP_ANON | MAP_PRIVATE |
       MAP_NOCORE, -1, 0);
     if (mapbase == (caddr_t) -1) {
        _rtld_error("%s: mmap of entire address space failed: %s",
          path, rtld_strerror(errno));
-       return NULL;
+       goto error;
     }
     if (base_addr != NULL && mapbase != base_addr) {
        _rtld_error("%s: mmap returned wrong address: wanted %p, got %p",
          path, base_addr, mapbase);
-       munmap(mapbase, mapsize);
-       return NULL;
+       goto error1;
     }
 
     for (i = 0; i <= nsegs; i++) {
@@ -203,7 +202,7 @@ map_object(int fd, const char *path, const struct stat *sb)
          data_flags, fd, data_offset) == (caddr_t) -1) {
            _rtld_error("%s: mmap of data failed: %s", path,
                rtld_strerror(errno));
-           return NULL;
+           goto error1;
        }
 
        /* Do BSS setup */
@@ -220,7 +219,7 @@ map_object(int fd, const char *path, const struct stat *sb)
                     mprotect(clear_page, PAGE_SIZE, data_prot|PROT_WRITE)) {
                        _rtld_error("%s: mprotect failed: %s", path,
                            rtld_strerror(errno));
-                       return NULL;
+                       goto error1;
                }
 
                memset(clear_addr, 0, nclear);
@@ -244,7 +243,7 @@ map_object(int fd, const char *path, const struct stat *sb)
                    data_flags | MAP_ANON, -1, 0) == (caddr_t)-1) {
                    _rtld_error("%s: mmap of bss failed: %s", path,
                        rtld_strerror(errno));
-                   return NULL;
+                   goto error1;
                }
            }
        }
@@ -277,7 +276,7 @@ map_object(int fd, const char *path, const struct stat *sb)
        if (obj->phdr == NULL) {
            obj_free(obj);
            _rtld_error("%s: cannot allocate program header", path);
-            return NULL;
+           goto error1;
        }
        memcpy((char *)obj->phdr, (char *)hdr + hdr->e_phoff, phsize);
        obj->phdr_alloc = true;
@@ -300,63 +299,72 @@ map_object(int fd, const char *path, const struct stat *sb)
     }
     if (note_start < note_end)
        digest_notes(obj, note_start, note_end);
-    return obj;
+    munmap(hdr, PAGE_SIZE);
+    return (obj);
+
+error1:
+    munmap(mapbase, mapsize);
+error:
+    munmap(hdr, PAGE_SIZE);
+    return (NULL);
 }
 
 static Elf_Ehdr *
-get_elf_header (int fd, const char *path)
+get_elf_header(int fd, const char *path)
 {
-    static union {
-       Elf_Ehdr hdr;
-       char buf[PAGE_SIZE];
-    } u;
-    ssize_t nbytes;
-
-    if ((nbytes = pread(fd, u.buf, PAGE_SIZE, 0)) == -1) {
-       _rtld_error("%s: read error: %s", path, rtld_strerror(errno));
-       return NULL;
-    }
+       Elf_Ehdr *hdr;
 
-    /* Make sure the file is valid */
-    if (nbytes < (ssize_t)sizeof(Elf_Ehdr) || !IS_ELF(u.hdr)) {
-       _rtld_error("%s: invalid file format", path);
-       return NULL;
-    }
-    if (u.hdr.e_ident[EI_CLASS] != ELF_TARG_CLASS
-      || u.hdr.e_ident[EI_DATA] != ELF_TARG_DATA) {
-       _rtld_error("%s: unsupported file layout", path);
-       return NULL;
-    }
-    if (u.hdr.e_ident[EI_VERSION] != EV_CURRENT
-      || u.hdr.e_version != EV_CURRENT) {
-       _rtld_error("%s: unsupported file version", path);
-       return NULL;
-    }
-    if (u.hdr.e_type != ET_EXEC && u.hdr.e_type != ET_DYN) {
-       _rtld_error("%s: unsupported file type", path);
-       return NULL;
-    }
-    if (u.hdr.e_machine != ELF_TARG_MACH) {
-       _rtld_error("%s: unsupported machine", path);
-       return NULL;
-    }
+       /* DragonFly mmap does not have MAP_PREFAULT_READ */
+       hdr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
+       if (hdr == (Elf_Ehdr *)MAP_FAILED) {
+               _rtld_error("%s: read error: %s", path, rtld_strerror(errno));
+               return (NULL);
+       }
 
-    /*
-     * We rely on the program header being in the first page.  This is
-     * not strictly required by the ABI specification, but it seems to
-     * always true in practice.  And, it simplifies things considerably.
-     */
-    if (u.hdr.e_phentsize != sizeof(Elf_Phdr)) {
-       _rtld_error(
-         "%s: invalid shared object: e_phentsize != sizeof(Elf_Phdr)", path);
-       return NULL;
-    }
-    if (u.hdr.e_phoff + u.hdr.e_phnum * sizeof(Elf_Phdr) > (size_t)nbytes) {
-       _rtld_error("%s: program header too large", path);
-       return NULL;
-    }
+       /* Make sure the file is valid */
+       if (!IS_ELF(*hdr)) {
+               _rtld_error("%s: invalid file format", path);
+               goto error;
+       }
+       if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS ||
+           hdr->e_ident[EI_DATA] != ELF_TARG_DATA) {
+               _rtld_error("%s: unsupported file layout", path);
+               goto error;
+       }
+       if (hdr->e_ident[EI_VERSION] != EV_CURRENT ||
+           hdr->e_version != EV_CURRENT) {
+               _rtld_error("%s: unsupported file version", path);
+               goto error;
+       }
+       if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN) {
+               _rtld_error("%s: unsupported file type", path);
+               goto error;
+       }
+       if (hdr->e_machine != ELF_TARG_MACH) {
+               _rtld_error("%s: unsupported machine", path);
+               goto error;
+       }
 
-    return (&u.hdr);
+       /*
+        * We rely on the program header being in the first page.  This is
+        * not strictly required by the ABI specification, but it seems to
+        * always true in practice.  And, it simplifies things considerably.
+        */
+       if (hdr->e_phentsize != sizeof(Elf_Phdr)) {
+               _rtld_error(
+           "%s: invalid shared object: e_phentsize != sizeof(Elf_Phdr)", path);
+               goto error;
+       }
+       if (hdr->e_phoff + hdr->e_phnum * sizeof(Elf_Phdr) >
+           (size_t)PAGE_SIZE) {
+               _rtld_error("%s: program header too large", path);
+               goto error;
+       }
+       return (hdr);
+
+error:
+       munmap(hdr, PAGE_SIZE);
+       return (NULL);
 }
 
 void