The poisoned NUL byte

From: Olaf Kirch (okirat_private)
Date: Wed Oct 14 1998 - 02:42:46 PDT

  • Next message: Marc Heuse: "Re: /tmp race in mc-4.5.0"

    Summary: you can exploit a single-byte buffer overrun to gain root privs.
    
    
    When, half a day after releasing version 2.2beta37 of the Linux nfs server,
    I received a message from Larry Doolittle telling me that it was still
    vulnerable to the root exploit posted to bugtraq, I was ready to quit
    hacking and start as a carpenter...
    
    Tempting as that was, I didn't, and started looking for the bug instead.
    It turned out that an ancient version of libc was at fault (libc-5.3.x),
    which has a buffer overrun in realpath().
    
    So, to make sure your mountd is safe, upgrade your libc to a recent
    version. As far as I can tell, this particular overrun was fixed in
    libc-4.4 (but see below).
    
    The interesting thing about this overrun is that it was by just a single
    byte. And yes, it not just crashed the process, it provided a root shell.
    It took me a while to figure out, but what it boils down to is this:
    
    At the beginning of the function, realpath copies the argument (1024 bytes)
    To a local buffer (sized MAXPATHLEN, i.e. 1024 bytes). Thus, the terminating
    0 byte of the string gets scribbled over the next byte, which happens to be
    the lowest byte of %ebp, the frame pointer of the calling function. At
    function entry, its value was 0xbffff3ec. After the strcpy, it becomes
    0xbffff300.
    
    During the remainder of realpath(), nothing exciting happens, but when the
    function returns, %ebp is restored from stack, which effectively shifts
    down the calling function's stack frame by 0xec bytes.
    
    The calling function now does a few things with local data, dereferences
    some pointers (by sheer dumb luck these pointers contain random but valid
    addresses), and returns, restoring the %esp and %ebp registers from stack.
    With the stack having shifted down 0xec bytes, it picks up the return address
    from the local buffer containing the exploit code...
    
    As a side note, the buffer overrun could be modified easily to work
    on systems using non-execute stacks, because the RPC arguments are
    decoded into a static buffer. Making the exploit point the fake return
    address at the static buffer instead of the stack easily defeats the
    no-exec stack.
    
    Also note that even the most recent libc5 contains another buffer overrun
    in realpath. It is not deadly to mountd unless you start it from
    a directory whose path name is longer than 40-something bytes. A patch to
    libc is appended, and I will release an nfs-server update that checks
    for this bug and replaces the faulty realpath function soon.
    
    Cheers
    Olaf
    ------------------------------------------------------------------
    Concerning the buffer overflow in realpath:
    
    The appended patch illustrates the problem. To trigger the overflow, try
    
    void main(void) /* hullo Dan Popp:-) */
    {
            char    buffer[1024], result[1024];
    
            memset(buffer, 'A', 1021);
            buffer[1021] = '\0';
            chdir("/tmp");
            realpath(buffer, result);
            printf("length = %d\n", strlen(result));
    }
    
    Glibc uses a different realpath implementation which does not have
    this bug.
    
    --- libc-5.4.38/libc/bsd/realpath.c.orig        Sat Oct  3 00:42:48 1998
    +++ libc-5.4.38/libc/bsd/realpath.c     Sat Oct  3 00:43:09 1998
    @@ -76,7 +76,7 @@
            }
            strcpy(copy_path, path);
            path = copy_path;
    -       max_path = copy_path + PATH_MAX - 2;
    +       max_path = resolved_path + PATH_MAX - 2;
            /* If it's a relative pathname use getwd for starters. */
            if (*path != '/') {
                    /* Ohoo... */
    @@ -122,7 +122,7 @@
                    }
                    /* Safely copy the next pathname component. */
                    while (*path != '\0' && *path != '/') {
    -                       if (path > max_path) {
    +                       if (new_path > max_path) {
                                    errno = ENAMETOOLONG;
                                    return NULL;
                            }
    
    ------------------------------------------------------------------
    --
    Olaf Kirch         |  --- o --- Nous sommes du soleil we love when we play
    okirat_private  |    / | \   sol.dhoop.naytheet.ah kin.ir.samse.qurax
    okirat_private    +-------------------- Why Not?! -----------------------
             UNIX, n.: Spanish manufacturer of fire extinguishers.
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:19:42 PDT