The poisoned NUL byte

Olaf Kirch (okir@MONAD.SWB.DE)
Wed, 14 Oct 1998 11:42:46 +0200

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

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.

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';
        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  |    / | \   sol.dhoop.naytheet.ah    +-------------------- Why Not?! -----------------------
         UNIX, n.: Spanish manufacturer of fire extinguishers.