How Can Linux Ptrace Be Unsafe or Contain a Race Condition

How can Linux ptrace be unsafe or contain a race condition?

The major problem is that many syscall arguments, like filenames, are passed to the kernel as userspace pointers. Any task that is allowed to run simultaneously and has write access to the memory that the pointer points to can effectively modify these arguments after they are inspected by your supervisor and before the kernel acts on them. By the time the kernel follows the pointer, the pointed-to contents may have been deliberately changed by another schedulable task (process or thread) with access to that memory. For example:

Thread 1                           Supervisor             Thread 2
-----------------------------------------------------------------------------------------------------
strcpy(filename, "/dev/null");
open(filename, O_RDONLY);
Check filename - OK
strcpy(filename, "/home/user/.ssh/id_rsa");
(in kernel) opens "/home/user/.ssh/id_rsa"

One way to stop this is to disallow calling clone() with the CLONE_VM flag, and in addition prevent any creation of writeable MAP_SHARED memory mappings (or at least keep track of them such that you deny any syscall that tries to directly reference data from such a mapping). You could also copy any such argument into a non-shared bounce-buffer before allowing the syscall to proceed. This will effectively prevent any threaded application from running in the sandbox.

The alternative is to SIGSTOP every other process in the traced group around every potentially dangerous syscall, wait for them to actually stop, then allow the syscall to proceed. After it returns, you then SIGCONT them (unless they were already stopped). Needless to say, this may have a significant performance impact.

(There are also analogous problems with syscall arguments that are passed on the stack, and with shared open file tables).

calling ptrace inside a ptraced Linux process

You are indeed seeing a race condition. You can cause it to happen repeatably by putting sleep(1); immediately before the second fork() call.

The race condition is caused because process A is not correctly passing signals on to process B. That means that if process B starts tracing process C after process A has started tracing process B, process B never gets the SIGCHLD signal indicating that process C has stopped, so it can never continue it.

To fix the problem, you just need to fix your SIGCHLD handler:

static void sigchld_handler(int sig){
int result, status;
pid_t child_pid = wait(&status); // find who send us this SIGCHLD

printf("%d received SIGCHLD on %d\n", getpid(), child_pid);
if (WIFSTOPPED(status))
{
result=ptrace(PTRACE_CONT, child_pid, 0, WSTOPSIG(status));
if(result) {
perror("continuing after SIGCHLD");
}
}
}

Preventing processes to execute certain system calls

If you want to do it the ptrace way, you have some options (and some are really simple). First of all, I recommend you to follow the tutorial explained here. With it you can learn how to know what system calls are being called, and also the basic ptrace knowledge (don't worry, it's a very short tutorial). The options (that I know) you have are the following:

  • The easiest one would be to kill the child, that is this exact code here.
  • Secondly you could make the child fail, just by changing the registers with PTRACE_SETREGS, putting wrong values in them, and you can also change the return value of the system call if you want (again, with PTRACE_SETREGS).
  • Finally you could skip the system call. But for that you should know the address after the system call call, make the intruction register point there and set it (again, with PTRACE_SETREGS).

Using ptrace to write a program supervisor in userspace

This looks like a good place to start.
http://www.linuxjournal.com/article/6100



Related Topics



Leave a reply



Submit