<<< Date Index >>>     <<< Thread Index >>>

Re: Linux ELF loader vulnerabilities



On Wed, 10 Nov 2004, Paul Starzetz wrote:

> 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.

BTW: a.out loader appears to be still full of integer overflow bugs.

> 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:

It can happen when the end of file is encountered. One might exploit this
to create an "oracle" giving quizzical answers about unused kernel memory:
You run your own malformed ELF binary that makes the kernel allocate N
bytes, read M < N bytes, and interpret all N bytes including unintialized
N-M bytes as ELF phdr entries. The fact the kernel ignores mmap() errors
could make it interesting.

On a NFS volume mounted with "intr" option, it can happen when the process
receives a signal in the middle of read(). I don't dare to say whether it
can happen in the middle of a page.

> - -  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.

I'd say that binaries with essential headers (phdr, interp) not fitting
into the first page of the executable file are extremely rare on any
platform. Afaik all standard tools put phdr right after ehdr, and interp
right after phdr. Standard ehdr size is 52 bytes (64 for 64-bit arch), one
standard phdr entry is 32 bytes (56? for 64-bit), and there is only a
handful of entries (<=10) in an ordinary phdr. Interp is quite short,
say <50 bytes. This makes <1000 bytes total.

Have you find any "naturally occuring" binary with big headers?

> 2) This bug can lead to a incorrectly mmaped binary image in the memory.
> There are various reasons why a mmap() call can fail:
[...]
> 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).

ET_EXEC files (ordinary binaries) have fixed mapping. ET_DYN (ld.so or
relocatable binaries; and dynamic libraries but they are irrelevant in
this context) get MAP_FIXED after the first segment has been mapped
successfully.

But there's a catch: ld.so is loaded by load_elf_interp() that stop after
the first mmap() failure. Its return value is wrong but the best thing we
can get with ET_EXEC binaries (both with and without a dynamic linker) is
an unmapped segment. A missing segment is likely to kill the program
before it can do any harm.

ET_DYN binaries may, on the other hand, be more exploitable if their
memory layout is messed up the right way. (Isn't it ironic some people
use ET_DYN binaries in order to be able to randomize process address
space and make their systems more resistant?)

> 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.

As far as I can tell the kernel puts the result of kernel_read(), i.e. the
interpreter's phdr size (<= page size), into elf_entry and initializes the
process' instruction pointer to elf_entry. The inevitable consequence is
that the process jumps into the large black hole at the begining of its
address space (assuming standard Linux memory layout) and dies before it
can do anything harmful. Do I miss anything?

> 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.

This is funny. There used to be
   elf_interpreter[elf_ppnt->p_filesz - 1] = 0;
there but it was "optimized out" between 2.2 and 2.4.


--Pavel Kankovsky aka Peak  [ Boycott Microsoft--http://www.vcnet.com/bms ]
"Resistance is futile. Open your source code and prepare for assimilation."