Memory corruption in ld.so

Bug #542197 reported by Dan Rosenberg on 2010-03-19
264
This bug affects 1 person
Affects Status Importance Assigned to Milestone
eglibc (Ubuntu)
Low
Unassigned
Dapper
Undecided
Unassigned
Hardy
Undecided
Unassigned
Intrepid
Undecided
Unassigned
Jaunty
Undecided
Unassigned
Karmic
Low
Unassigned
Lucid
Low
Unassigned
glibc (Ubuntu)
Undecided
Unassigned
Dapper
Low
Unassigned
Hardy
Low
Unassigned
Intrepid
Low
Unassigned
Jaunty
Low
Unassigned
Karmic
Undecided
Unassigned
Lucid
Undecided
Unassigned

Bug Description

I have discovered a memory corruption vulnerability in ld.so. When processing maliciously crafted ELF binaries using ld.so, regardless of whether execution of those binaries is intended (for example, I tested using the "--verify" flag, which should not lead to any code execution), arbitrary code execution can be achieved.

The relevant code is in elf/dynamic-link.h, in the elf_get_dynamic_info() function, which is called from rtld.c (the entry point for ld.so). The code iterates through dynamic linking information, using the d_tag member as an index into the info[] array. d_tag is declared as an Elf32_Sword. According to the Linux man pages, Elf32_Sword is an unsigned type. However, the elf.h header declares this as a signed type. By crafting an ELF binary such that a d_tag entry has a carefully calculated negative index, the address of the user-controlled dyn struct can be written to an arbitrary location.

I have confirmed that this can lead to arbitrary code execution. For simplicity, I tested on Ubuntu Jaunty 9.04 on a 32-bit x86 machine, with ASLR disabled (/proc/sys/kernel/randomize_vaspace set to 0). My libc6 package is version 2.9-4-ubuntu6. My exploit was a simple test case with a crafted header that can be invoked as:

/lib/ld-2.9.so --verify ./test

The exploit overwrites a return address on the stack, pointing it into the dyn struct. This struct contains first-stage shellcode that jumps execution into the .data segment and spawns a shell. This is not a robust exploit - it is mitigated by NX support on later distributions (since code execution is taking place in .data), and ASLR would make it impossible to reliably overwrite a return address on the stack. It may not even work reliably on other machines - since exact stack offsets are used, it may be subject to changes in environment variables, etc. However, I'm sure that a more talented exploit writer could produce a more robust exploit, and my example should prove the point that this is a code execution issue. If the exploit does not spawn a shell, I would expect it to cause a segfault.

This problem can be mitigated by defining Elf32_Sword as an unsigned int, to be consistent with its documentation. If this may have other unintended effects, then more careful checking of array bounds in the vulnerable function should be done, to prevent writing to arbitrary memory locations.

Kees Cook (kees) wrote :

I haven't dug fully into this yet, but it seems slightly related to similar issues with ld in general?

http://www.catonmat.net/blog/ldd-arbitrary-code-execution/

Dan Rosenberg (dan-j-rosenberg) wrote :

I suppose the exploitation scenarios are similar, but this seems to be a case of "feature vs. bug". The ldd bash script has documented behavior that results in the execution of the binary that is passed to it as an argument if it can't be loaded by /lib/ld.so. This bug, on the other hand, is a memory corruption issue in ld.so itself, and can result in arbitrary code execution in situations where no code should be executed at all.

Kees Cook (kees) wrote :

This patch should fix it, though I haven't tested yet. I can't find the docs that says Sword should be unsigned (looking at "man elf"). Oddly, the rest of the code in this function is correctly typed to unsigned, just not the first check. :(

Dan Rosenberg (dan-j-rosenberg) wrote :

Fix looks good. Just for the record, my local man pages on Ubuntu correctly document Elf32_Sword as a signed value (hence the "S"), but the online copy at http://linux.die.net has it backwards. I guess the lesson is that documentation may vary, but the code usually reveals the truth.

Kees Cook (kees) wrote :

CVE-2010-0830

Kees Cook (kees) on 2010-04-01
Changed in glibc (Ubuntu):
status: New → Triaged
importance: Undecided → Low
Changed in glibc (Ubuntu Karmic):
status: New → Invalid
Changed in glibc (Ubuntu Lucid):
status: Triaged → Invalid
importance: Low → Undecided
Changed in glibc (Ubuntu Jaunty):
importance: Undecided → Low
Changed in glibc (Ubuntu Intrepid):
importance: Undecided → Low
Changed in glibc (Ubuntu Hardy):
importance: Undecided → Low
Changed in glibc (Ubuntu Dapper):
importance: Undecided → Low
Changed in eglibc (Ubuntu Jaunty):
status: New → Invalid
Changed in eglibc (Ubuntu Intrepid):
status: New → Invalid
Changed in eglibc (Ubuntu Hardy):
status: New → Invalid
Changed in eglibc (Ubuntu Dapper):
status: New → Invalid
Kees Cook (kees) on 2010-04-01
Changed in glibc (Ubuntu Dapper):
status: New → Triaged
Changed in glibc (Ubuntu Hardy):
status: New → Triaged
Changed in glibc (Ubuntu Intrepid):
status: New → Triaged
Changed in glibc (Ubuntu Jaunty):
status: New → Triaged
Changed in eglibc (Ubuntu Lucid):
status: New → Triaged
importance: Undecided → Low
Changed in eglibc (Ubuntu Karmic):
status: New → Triaged
importance: Undecided → Low
Launchpad Janitor (janitor) wrote :

This bug was fixed in the package eglibc - 2.11.1-0ubuntu7.1

---------------
eglibc (2.11.1-0ubuntu7.1) lucid-security; urgency=low

  * SECURITY UPDATE: newlines not escaped in /etc/mtab.
    - debian/patches/any/git-mntent-newline-escape.diff: upstream fixes.
    - CVE-2010-0296
  * SECURITY UPDATE: arbitrary code execution from ELF headers (LP: #542197).
    - debian/patches/any/git-fix-dtag-cast.diff: upstream fixes.
    - CVE-2010-0830
 -- Kees Cook <email address hidden> Wed, 19 May 2010 16:56:07 -0700

Launchpad Janitor (janitor) wrote :

This bug was fixed in the package eglibc - 2.10.1-0ubuntu17

---------------
eglibc (2.10.1-0ubuntu17) karmic-security; urgency=low

  * SECURITY UPDATE: integer overflow in strfmon() might lead to arbitrary
    code execution.
    - debian/patches/any/git-strfmon-overflow.diff: backport from upstream.
    - CVE-2008-1391
  * SECURITY UPDATE: newlines not escaped in /etc/mtab.
    - debian/patches/any/git-mntent-newline-escape.diff: upstream fixes.
    - CVE-2010-0296
  * SECURITY UPDATE: arbitrary code execution from ELF headers (LP: #542197).
    - debian/patches/any/git-fix-dtag-cast.diff: upstream fixes.
    - CVE-2010-0830
  * debian/patches/any/git-readdir-padding.diff: fix readdir padding when
    processing getdents64() in a 32-bit execution environment (LP: #392501).
 -- Kees Cook <email address hidden> Wed, 19 May 2010 16:57:47 -0700

Launchpad Janitor (janitor) wrote :

This bug was fixed in the package glibc - 2.9-4ubuntu6.2

---------------
glibc (2.9-4ubuntu6.2) jaunty-security; urgency=low

  * SECURITY UPDATE: integer overflow in strfmon() might lead to arbitrary
    code execution.
    - debian/patches/any/git-strfmon-overflow.diff: backport from upstream.
    - CVE-2008-1391
  * SECURITY UPDATE: newlines not escaped in /etc/mtab.
    - debian/patches/any/git-mntent-newline-escape.diff: upstream fixes.
    - CVE-2010-0296
  * SECURITY UPDATE: arbitrary code execution from ELF headers (LP: #542197).
    - debian/patches/any/git-fix-dtag-cast.diff: upstream fixes.
    - CVE-2010-0830
  * debian/patches/any/git-readdir-padding.diff: fix readdir padding when
    processing getdents64() in a 32-bit execution environment (LP: #392501).
 -- Kees Cook <email address hidden> Wed, 19 May 2010 16:58:40 -0700

Launchpad Janitor (janitor) wrote :

This bug was fixed in the package glibc - 2.7-10ubuntu6

---------------
glibc (2.7-10ubuntu6) hardy-security; urgency=low

  * SECURITY UPDATE: integer overflow in strfmon() might lead to arbitrary
    code execution.
    - debian/patches/any/git-strfmon-overflow.diff: backport from upstream.
    - CVE-2008-1391
  * SECURITY UPDATE: newlines not escaped in /etc/mtab.
    - debian/patches/any/git-mntent-newline-escape.diff: upstream fixes.
    - CVE-2010-0296
  * SECURITY UPDATE: arbitrary code execution from ELF headers (LP: #542197).
    - debian/patches/any/git-fix-dtag-cast.diff: upstream fixes.
    - CVE-2010-0830
  * debian/patches/any/git-readdir-padding.diff: fix readdir padding when
    processing getdents64() in a 32-bit execution environment (LP: #392501).
 -- Kees Cook <email address hidden> Wed, 19 May 2010 16:59:18 -0700

Changed in eglibc (Ubuntu Karmic):
status: Triaged → Fix Released
Changed in eglibc (Ubuntu Lucid):
status: Triaged → Fix Released
Changed in glibc (Ubuntu Hardy):
status: Triaged → Fix Released
Changed in glibc (Ubuntu Jaunty):
status: Triaged → Fix Released
Kees Cook (kees) on 2010-05-25
visibility: private → public
Changed in glibc (Ubuntu Dapper):
status: Triaged → Fix Released
Changed in glibc (Ubuntu Intrepid):
status: Triaged → Won't Fix
Changed in eglibc (Ubuntu):
assignee: nobody → dave weston (davidweezer)
status: Triaged → Fix Released
Kees Cook (kees) on 2010-05-26
Changed in eglibc (Ubuntu):
assignee: dave weston (davidweezer) → nobody
status: Fix Released → Triaged
tags: added: patch
Matthias Klose (doko) wrote :

this is fixed in eglibc-2.12

Changed in eglibc (Ubuntu):
status: Triaged → Fix Released
To post a comment you must log in.
This report contains Public Security information  Edit
Everyone can see this security related information.

Other bug subscribers