Commit 0363994f authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] fix ELF exec with huge bss

From: Roland McGrath <roland@redhat.com>

The following test program will crash every time if dynamically linked.
I think this bites all 32-bit platforms, including 32-bit executables on
64-bit platforms that support them (and could in theory bite 64-bit
platforms with bss sizes beyond the bounds of comprehension).

	volatile char hugebss[1080000000];
	main() { printf("%p..%p\n", &hugebss[0], &hugebss[sizeof hugebss]);
	 system("cat /proc/$PPID/maps");
	 hugebss[sizeof hugebss - 1] = 1;
	 return 23;
	}

The problem is that the kernel maps ld.so at 0x40000000 or some such place,
before it maps the bss.  Here the bss is so large that it overlaps and
clobbers that mapping.  I've changed it to map the bss before it loads the
interpreter, so that part of the address space is reserved before ld.so's
mapping (which doesn't really care where it goes) is done.

This patch also adds error checking to the bss setup (and interpreter's bss
setup).  With the aforementioned change but no error checking, "ulimit -v
65536; ./hugebss" will crash in the store after the `system' call, because
the kernel will have failed to allocate the bss and ignored the error, so
the program runs without those pages being mapped at all.  With this change
it dies with a SIGKILL as for a failure to set up stack pages.  It might be
even better to try to detect the case earlier so that execve can return an
error before it has wiped out the address space.  But that seems like it
would always be fragile and miss some corner cases, so I did not try to add
such complexity.
parent 709087ca
...@@ -82,13 +82,17 @@ static struct linux_binfmt elf_format = { ...@@ -82,13 +82,17 @@ static struct linux_binfmt elf_format = {
#define BAD_ADDR(x) ((unsigned long)(x) > TASK_SIZE) #define BAD_ADDR(x) ((unsigned long)(x) > TASK_SIZE)
static void set_brk(unsigned long start, unsigned long end) static int set_brk(unsigned long start, unsigned long end)
{ {
start = ELF_PAGEALIGN(start); start = ELF_PAGEALIGN(start);
end = ELF_PAGEALIGN(end); end = ELF_PAGEALIGN(end);
if (end > start) if (end > start) {
do_brk(start, end - start); unsigned long addr = do_brk(start, end - start);
if (BAD_ADDR(addr))
return addr;
}
current->mm->start_brk = current->mm->brk = end; current->mm->start_brk = current->mm->brk = end;
return 0;
} }
...@@ -381,8 +385,11 @@ static unsigned long load_elf_interp(struct elfhdr * interp_elf_ex, ...@@ -381,8 +385,11 @@ static unsigned long load_elf_interp(struct elfhdr * interp_elf_ex,
elf_bss = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1); /* What we have mapped so far */ elf_bss = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1); /* What we have mapped so far */
/* Map the last of the bss segment */ /* Map the last of the bss segment */
if (last_bss > elf_bss) if (last_bss > elf_bss) {
do_brk(elf_bss, last_bss - elf_bss); error = do_brk(elf_bss, last_bss - elf_bss);
if (BAD_ADDR(error))
goto out_close;
}
*interp_load_addr = load_addr; *interp_load_addr = load_addr;
error = ((unsigned long) interp_elf_ex->e_entry) + load_addr; error = ((unsigned long) interp_elf_ex->e_entry) + load_addr;
...@@ -691,7 +698,12 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs) ...@@ -691,7 +698,12 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
/* There was a PT_LOAD segment with p_memsz > p_filesz /* There was a PT_LOAD segment with p_memsz > p_filesz
before this one. Map anonymous pages, if needed, before this one. Map anonymous pages, if needed,
and clear the area. */ and clear the area. */
set_brk (elf_bss + load_bias, elf_brk + load_bias); retval = set_brk (elf_bss + load_bias,
elf_brk + load_bias);
if (retval) {
send_sig(SIGKILL, current, 0);
goto out_free_dentry;
}
nbyte = ELF_PAGEOFFSET(elf_bss); nbyte = ELF_PAGEOFFSET(elf_bss);
if (nbyte) { if (nbyte) {
nbyte = ELF_MIN_ALIGN - nbyte; nbyte = ELF_MIN_ALIGN - nbyte;
...@@ -756,6 +768,18 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs) ...@@ -756,6 +768,18 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
start_data += load_bias; start_data += load_bias;
end_data += load_bias; end_data += load_bias;
/* Calling set_brk effectively mmaps the pages that we need
* for the bss and break sections. We must do this before
* mapping in the interpreter, to make sure it doesn't wind
* up getting placed where the bss needs to go.
*/
retval = set_brk(elf_bss, elf_brk);
if (retval) {
send_sig(SIGKILL, current, 0);
goto out_free_dentry;
}
padzero(elf_bss);
if (elf_interpreter) { if (elf_interpreter) {
if (interpreter_type == INTERPRETER_AOUT) if (interpreter_type == INTERPRETER_AOUT)
elf_entry = load_aout_interp(&interp_ex, elf_entry = load_aout_interp(&interp_ex,
...@@ -799,13 +823,6 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs) ...@@ -799,13 +823,6 @@ static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
current->mm->end_data = end_data; current->mm->end_data = end_data;
current->mm->start_stack = bprm->p; current->mm->start_stack = bprm->p;
/* Calling set_brk effectively mmaps the pages that we need
* for the bss and break sections
*/
set_brk(elf_bss, elf_brk);
padzero(elf_bss);
if (current->personality & MMAP_PAGE_ZERO) { if (current->personality & MMAP_PAGE_ZERO) {
/* Why this, you ask??? Well SVr4 maps page 0 as read-only, /* Why this, you ask??? Well SVr4 maps page 0 as read-only,
and some applications "depend" upon this behavior. and some applications "depend" upon this behavior.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment