It's Bugs All the Way Down

Security Research by Dan Rosenberg

Integer overflow in ld.so (CVE-2010-0830)

A month or so ago, I reported an integer overflow vulnerability in ld.so (the linker/loader included with glibc) that could be exploited to achieve arbitrary code execution. The catch is, the vulnerability is triggered in the processing of a maliciously crafted ELF binary, so the actual potential for exploitation in real life is minimal. In many cases, invoking ld.so on an untrusted binary results in the execution of that binary, so in practice this should never be done. However, there are some options that can be used with ld.so that should not result in code execution, such as the case where a user invokes ld.so to verify the dynamic dependencies of an unknown binary, for example with the “–verify” flag.

This vulnerability was assigned CVE-2010-0830 by the good folks at Ubuntu Security. The CRD (coordinated release date) of this bug has already passed, so it’s officially public and I’m not going to upset anyone by writing about it here. Fixes have been slowly trickling out, most likely because of the low impact of the bug. In fact, there was a bit of a debate as to whether this bug constitutes a security risk at all. But just because a bug is low impact doesn’t mean it isn’t fun. :)

The relevant code can be found in the glibc source at elf/dynamic-link.h in the elf_get_dynamic_info() function. This function is called from elf/rtld.c (the entry point for ld.so), and iterates through dynamic linking information as follows:

ElfW(Dyn) *dyn = l->l_ld;
ElfW(Dyn) **info;

info = l->l_info;

while (dyn->d_tag != DT_NULL)
{
  if (dyn->d_tag < DT_NUM)
    info[dyn->d_tag] = dyn;
  ...

  dyn++;
}

The dyn struct is read straight from the ELF header of the binary being processed. The important thing to notice is that the d_tag member is a signed integer, so the dyn->d_tag < DT_NUM comparison is also signed. Therefore, if an ELF binary was constructed to contain a carefully chosen negative index for this tag, the check will pass and an arbitrary memory location will be written with the address of the current dyn struct.

To produce a proof-of-concept exploit for this bug, I chose a machine without NX and ASLR support for simplicity. I crafted a dyn struct such that the first few bytes contained first-stage shellcode that jumped into a second-stage shellcode stored elsewhere in the malicious binary. The d_tag member was chosen to overwrite a saved return address, so that when the function returns, execution jumps into the dyn struct, running the first-stage shellcode. With more effort I'm sure this could be ported to a machine with ASLR and/or NX.

This bug goes all the way back to glibc 2.0.1, so it's been included since at least 1997. Interestingly, all of the other tests within this code path properly cast the d_tag to an unsigned type before comparisons, preventing out-of-bounds writing and exploitation.

This entry was posted on Sunday, May 2nd, 2010 at 12:28 pm and is filed under Exploitation, Linux. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.