[Full-Disclosure] Exploit for CVS double free() for Linux pserver

From: Igor Dobrovitski (noident@mad.scientist.com)
Date: 02/02/03

  • Next message: Daniel Ahlberg: "[Full-Disclosure] GLSA: slocate"
    From: Igor Dobrovitski <noident@mad.scientist.com>
    To: bugtraq@securityfocus.com, full-disclosure@lists.netsys.com
    Date: Sun, 2 Feb 2003 22:27:23 +1100 (EST)

       A bug in cvs versions up to and including 1.11.4 was recently found
    where, under certain conditions,
    a pointer is free()'d, and then free()'d again without being
    The reports with regards to the exploitability of the condition in
    question range from -
    "it is a classical exploitable double-free()" to "may possibly be
    I have written an exploit for Linux for pserver, and contrary to my usual
    practice, decided to make it public.
    First, I couldn't find any papers on the internet that would explain the
    exploitation techniques of
    double-free(), and I believe we don't have many publically available
    exploits or in-depth discussion on the matter.
    I hope that this little explanation that I've put together, and the
    exploit itself may be somewhat useful
    to the hacker/security community (we can't exist without each other, can
    we? :)
    The impact of a successful exploitation is not that great: an unprivileged
    access to the system,
    where your calls to getuid() will return a number that's far from 0 (cvs
    drops provileges, and does it right).
    The audience is expected to be familiar with D.L. malloc implementation.
    The explanation of how D.L. malloc works can be found in two articles in
    phrack 57.

    If a request for a memory chunk is made, and if chunks that are kept in
    linked lists, or the last remaindered chunk
    cannot satisfy the requirement, the top memory chunk is split off, and a
    chunk of the right size is returned
    to malloc(). When this chunk is later free()'d, it may be coalesced with
    other adjacent chunks if any of
    the adjacent chunks are free. If not, the chunk is placed in a linked
    list. After being processed by
    the frontlink() macro, the linked list looks like this: we have two items
    in the list, the bin and the chunk,
    both BK and FD pointers of the bin point to the chunk, and both BK and FD
    pointers of the chunk point to the bin.
    Now, should this chunk be free()'d again, while on the linked list, the
    picture changes. After the second free()
    is called and the chunk is processed by the frontlink() again, we have
    both BK and FD pointers of the bin still
    pointed at the chunk, but both BK and FD pointers of the chunk will point
    to itself !!!
    Take a look now at the unlink() macro. This macro is called when taking a
    chunk off the list:
    #define unlink( P, BK, FD ) { \
                                 BK = P->bk; \
                                 FD = P->fd; \
                                 FD->bk = BK; \
                                 BK->fd = FD; \
    Remember that we have now P = P->bk = P->fd. What changes when this chunk
    is passed though unlink()? Nothing!
    This means that ALL subsequent calls to malloc of the size our chunk will
    be returning the same chunk, the one that was double-free()'d.
    The rest is easy. After the chunk was double-free()'d, we make a request
    to the program that will have to allocate
    the double-free()'d chunk back to us, and copy the data we supply into the
    memory returned to us.
    Well, since the chunk is allocated, the backward and forward pointers are
    not used, and user data gets straight there.
    We will copy 2 addresses into the first 8 bytes of the chunk.
    Now, we make another request to the program that will have to allocate to
    us the same chunk. It will be passed through
    the unlink() again, but this time, since the chunk is considered free, its
    BK and FD pointers are used,
    and lo and behold! We can overwrite any address in the memory with 4 bytes
    of our choosing.

    Now, how this particular exploit works:
       1. First we allocate a chunk of some size and make sure this chunk
    comes from the top memory chunk.
    Also make sure that this chunk stays allocated while we're
    exploiting. This will keep our directory chunk
    from being coalesced with the previous chunk.
       2. Allocate the Directory chunk, make sure it comes from the top memory
       3. Allocate a chunk the same size as in step 1, for the same reason,
    except that it will keep our Directory chunk
    from being coalesced with the next chunk.
       4. Now that our exploitable chunk is secure, allocate a big chunk for
    us to put shellcode, jumps and noops,
    4K in this exploit.
       5. free() our directory chunk twice.
       6. Ask the server to malloc() a chunk of the size that was
    double-free()'d, it will give us the very same
    double-free()'d chunk without actually taking it off its linked list;
       7. the server will strcpy() our 2 addresses we provide into the first 8
    bytes of our double-free()'d once
    malloc()'ed chunk.
       8. Ask the server to again malloc() a chunk of the size that was
    double-free()'d, upon which again our chunk is
    malloc()'ed, passed through unlink(), overwriting memory.


    Full-Disclosure - We believe in it.
    Charter: http://lists.netsys.com/full-disclosure-charter.html