Eliminate the static buffer used to read the first page of the mapped

object, and eliminate the pread(2) call as well [1]. Mmap the first
page of the object temporaly, and unmap it on error or last use.
Potentially, this leaves one-page gap between succeeding dlopen(3),
but there are other mmap(2) consumers as well.

Fix several cases were the whole mapping of the object leaked on error.

Use MAP_PREFAULT_READ for mmap(2) calls which map real object pages [2].

Insipired by the patch by:	Ian Lepore <freebsd damnhippie dyndns org> [1]
Suggested by:	alc [2]
MFC after:	2 weeks
This commit is contained in:
Konstantin Belousov 2012-06-14 11:20:22 +00:00
parent d4746e107f
commit e474e51e07

View 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;
@ -161,12 +161,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;
}
/*
@ -183,13 +183,12 @@ map_object(int fd, const char *path, const struct stat *sb)
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++) {
@ -201,10 +200,10 @@ map_object(int fd, const char *path, const struct stat *sb)
data_prot = convert_prot(segs[i]->p_flags);
data_flags = convert_flags(segs[i]->p_flags) | MAP_FIXED;
if (mmap(data_addr, data_vlimit - data_vaddr, data_prot,
data_flags, fd, data_offset) == (caddr_t) -1) {
data_flags | MAP_PREFAULT_READ, 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 */
@ -221,7 +220,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);
@ -240,7 +239,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;
}
}
}
@ -273,7 +272,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;
@ -293,63 +292,72 @@ map_object(int fd, const char *path, const struct stat *sb)
obj->relro_page = obj->relocbase + trunc_page(relro_page);
obj->relro_size = round_page(relro_size);
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;
Elf_Ehdr *hdr;
if ((nbytes = pread(fd, u.buf, PAGE_SIZE, 0)) == -1) {
_rtld_error("%s: read error: %s", path, rtld_strerror(errno));
return NULL;
}
hdr = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_PRIVATE | MAP_PREFAULT_READ,
fd, 0);
if (hdr == (Elf_Ehdr *)MAP_FAILED) {
_rtld_error("%s: read error: %s", path, rtld_strerror(errno));
return (NULL);
}
/* 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;
}
/* 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;
}
/*
* 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;
}
/*
* 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);
return (&u.hdr);
error:
munmap(hdr, PAGE_SIZE);
return (NULL);
}
void