improve error handling in map_library and support long phdrs
authorRich Felker <dalias@aerifal.cx>
Fri, 2 Aug 2013 13:56:49 +0000 (09:56 -0400)
committerRich Felker <dalias@aerifal.cx>
Fri, 2 Aug 2013 13:56:49 +0000 (09:56 -0400)
previously, errno could be meaningless when the caller wrote it to the
dlerror string or stderr. try to make it meaningful. also, fix
incorrect check for over-long program headers and instead actually
support them by allocating memory if needed.

src/ldso/dynlink.c

index 63a2554..0ae7177 100644 (file)
@@ -305,6 +305,7 @@ static void reclaim_gaps(unsigned char *base, Phdr *ph, size_t phent, size_t phc
 static void *map_library(int fd, struct dso *dso)
 {
        Ehdr buf[(896+sizeof(Ehdr))/sizeof(Ehdr)];
+       void *allocated_buf=0;
        size_t phsize;
        size_t addr_min=SIZE_MAX, addr_max=0, map_len;
        size_t this_min, this_max;
@@ -312,23 +313,28 @@ static void *map_library(int fd, struct dso *dso)
        Ehdr *eh;
        Phdr *ph, *ph0;
        unsigned prot;
-       unsigned char *map, *base;
+       unsigned char *map=MAP_FAILED, *base;
        size_t dyn=0;
        size_t tls_image=0;
        size_t i;
 
        ssize_t l = read(fd, buf, sizeof buf);
-       if (l<(int)sizeof *eh) return 0;
        eh = buf;
-       if (eh->e_type != ET_DYN && eh->e_type != ET_EXEC) {
-               errno = ENOEXEC;
-               return 0;
-       }
+       if (l<0) return 0;
+       if (l<sizeof *eh || (eh->e_type != ET_DYN && eh->e_type != ET_EXEC))
+               goto noexec;
        phsize = eh->e_phentsize * eh->e_phnum;
-       if (phsize + sizeof *eh > l) return 0;
-       if (eh->e_phoff + phsize > l) {
+       if (phsize > sizeof buf - sizeof *eh) {
+               allocated_buf = malloc(phsize);
+               if (!allocated_buf) return 0;
+               l = pread(fd, allocated_buf, phsize, eh->e_phoff);
+               if (l < 0) goto error;
+               if (l != phsize) goto noexec;
+               ph = ph0 = allocated_buf;
+       } else if (eh->e_phoff + phsize > l) {
                l = pread(fd, buf+1, phsize, eh->e_phoff);
-               if (l != phsize) return 0;
+               if (l < 0) goto error;
+               if (l != phsize) goto noexec;
                ph = ph0 = (void *)(buf + 1);
        } else {
                ph = ph0 = (void *)((char *)buf + eh->e_phoff);
@@ -354,7 +360,7 @@ static void *map_library(int fd, struct dso *dso)
                        addr_max = ph->p_vaddr+ph->p_memsz;
                }
        }
-       if (!dyn) return 0;
+       if (!dyn) goto noexec;
        addr_max += PAGE_SIZE-1;
        addr_max &= -PAGE_SIZE;
        addr_min &= -PAGE_SIZE;
@@ -365,7 +371,7 @@ static void *map_library(int fd, struct dso *dso)
         * use the invalid part; we just need to reserve the right
         * amount of virtual address space to map over later. */
        map = mmap((void *)addr_min, map_len, prot, MAP_PRIVATE, fd, off_start);
-       if (map==MAP_FAILED) return 0;
+       if (map==MAP_FAILED) goto error;
        /* If the loaded file is not relocatable and the requested address is
         * not available, then the load operation must fail. */
        if (eh->e_type != ET_DYN && addr_min && map!=(void *)addr_min) {
@@ -416,8 +422,11 @@ static void *map_library(int fd, struct dso *dso)
        dso->dynv = (void *)(base+dyn);
        if (dso->tls_size) dso->tls_image = (void *)(base+tls_image);
        return map;
+noexec:
+       errno = ENOEXEC;
 error:
-       munmap(map, map_len);
+       if (map!=MAP_FAILED) munmap(map, map_len);
+       free(allocated_buf);
        return 0;
 }