xorl %eax, %eax

CVE-2011-2702: eglibc and glibc Signedness Issue

with one comment

This is an awesome vulnerability reported by c0ntex after having it as a 0day for 1.5 year. You can read his very detailed analysis here.

Although a very well written and complete analysis is already written by the original author of this vulnerability, I will write a blog post mostly for future references.

The vulnerability was added when the 32-bit memset(3) and memcpy(3) routines were optimized with SSE2/SSSE3 features.
Although there are more vulnerable routines here I will be focusing on memcpy(3) from eglibc library. The exact code is located in sysdeps/i386/i686/multiarch/memcpy-ssse3.S file.

Since the code is in x86 assembly I will go from the beginning to the end to most of the memcpy(3) code.

ENTRY (MEMCPY)
        ENTRANCE
        movl    LEN(%esp), %ecx
        movl    SRC(%esp), %eax
        movl    DEST(%esp), %edx

So, the user arguments are stored in the above registers. To exploit this vulnerability the attacker must have control of the length argument which is stored in ECX general purpose register as you can see from the above code snippet.

#ifdef USE_AS_MEMMOVE
        cmp     %eax, %edx
        jb      L(copy_forward)
        je      L(fwd_write_0bytes)
        cmp     $32, %ecx
        jge     L(memmove_bwd)
        jmp     L(bk_write_less32bytes_2)

It checks that the source and destination pointers do not match and executes the equivalent routine. The most important part for us is the next one, it compares the value of ECX (our length argument) with the static value 32. If ECX is greater than that, it will jump to memmove_bwd(), otherwise it will jump to bk_write_less32bytes_2(). Since ECX could store signed integers this check could be bypassed easily leading to the latter routine which is shown below.

L(fwd_write_less32bytes):
#ifndef USE_AS_MEMMOVE
        cmp     %dl, %al
        jl      L(bk_write)
#endif
         add     %ecx, %edx
         add     %ecx, %eax
         BRANCH_TO_JMPTBL_ENTRY (L(table_48bytes_fwd), %ecx, 4)
#ifndef USE_AS_MEMMOVE
L(bk_write):
         BRANCH_TO_JMPTBL_ENTRY (L(table_48_bytes_bwd), %ecx, 4)
#endif

So, it will initially compare DL with AL to ensure that the lower Byte of EDX (destination pointer) is not less than the lower part of EAX (source pointer) register. This check can also bypassed so we skip the bk_write() call and move to last part.
Here it increments the length with the value of destination and source addresses and uses a macro to find an entry in a jump table.

Here is the aforementioned macro…

# define BRANCH_TO_JMPTBL_ENTRY_TAIL(TABLE, INDEX, SCALE)       \
    addl        (%ebx,INDEX,SCALE), %ebx;                       \
    /* We loaded the jump table.  Go.  */                       \
    jmp         *%ebx

As you can see, the user controlled ECX value is used as an index to the ‘table_48_bytes_bwd’ table. However, since there was no check for negative length values an attacker could control the result value stored in EBX and make it point to arbitrary memory locations leading to code execution when ‘JMP *%EBX’ is executed.

As c0ntex showed in his very detailed analysis, there is at least one mplayer exploit in the wild using this vulnerability.

(gdb) r ~/Desktop/expl.wmv 
Starting program: /usr/bin/mplayer ~/Desktop/expl.wmv
[Thread debugging using libthread_db enabled]

MPlayer SVN-r1.0~rc3+svn20090426-4.4.3 (C) 2000-2009 MPlayer Team
mplayer: could not connect to socket
mplayer: No such file or directory
Failed to open LIRC support. You will not be able to use your remote control.

Playing /home/user/Desktop/expl.wmv.
ASF file format detected.
[asfheader] Audio stream found, -aid 97
process 17760 is executing new program: /bin/dash
$
Program exited normally.
(gdb) q

However, any application linked to a vulnerable version of eglibc/glibc that allows user controlled length parameter on a memcpy(3) or memset(3) calls it could possibly lead to code execution.

Furthermore, c0ntex also provided a PoC trigger code that you see below.

#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
        char * buf = NULL;
        puts("Usage: ./test A 3492348247\n");
        memcpy(buf, argv[1], atoi(argv[2]));
        return 0;
}

So, I wrote this post because this is certainly a really cool 0day even though it’s now dead. :(

Written by xorl

August 6, 2011 at 11:06

Posted in vulnerabilities

One Response

Subscribe to comments with RSS.

  1. As usual a nice explanation.

    I agree this is a pretty nasty bug, but still shouldn’t places where the length parameter is completely user controlled and unchecked be rare?

    Afterall that could be a threat on its own: if nothing else memory exhaustion is possible.

    Of course exploiting this is easier than a possible signedness issue in the original application.

    fiction

    August 8, 2011 at 11:05


Leave a comment