RE: [PATCH] ptrace RSE bug

Petr Tesarik
2007-11-13T11:07:25Z

[-- Attachment #1: Type: text/plain, Size: 1403 bytes --]

On Mon, 2007-11-12 at 16:30 -0800, Roland McGrath wrote:
> [...]
> If you do the artificial test using a long sleep in arch_ptrace_stop,
> then you can probably produce this by hand with gdb. Have the process
> doing raise(SIGCHLD) or some other harmless signal. The traced
> process will stop to report the signal to gdb, and then gdb will sit
> at the prompt before resuming it (given "handle SIGFOO stop" if not default).
> If your sleep is long enough, it won't be hard to get your SIGKILL in there.
> Then when gdb is sitting, the traced process may still be sitting too.
> But it should have gone away instantly from SIGKILL.

I found it extremely difficult to trigger the race condition without the
articifial test - arch_ptrace_stop() only sleeps if the user page is not
present, but in the common case the register stack backing store will
have been quite recently accessed by the process.

It should be possible to create a large file, flush the page cache, put
the RSE into lazy mode, flush it and map the register stack from that
file, so that no memory accesses to the backing store are done before
ptrace_stop(), but for the time being I placed an msleep(100) after
arch_ptrace_stop().

Anyway, I produced a test case which succeeds when the call to
sigkill_pending() is in and fails when it's commented out. I'm attaching
it here (the kernel patch to follow).

Regards,
Petr Tesarik

[-- Attachment #2: kill-race.c --]
[-- Type: text/x-csrc, Size: 1899 bytes --]

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <time.h>

#define TEST_OK 0
#define TEST_FAILED 1
#define TEST_ABORTED 2

#define INTERACTIVE 1

static int run_debugger(pid_t pid)
{
struct timespec tm;
int stat;
int res;

printf("Running debugger on pid %ld\n", (long) pid);

if (waitpid(pid, &stat, 0) == -1) {
perror("waitpid attach");
goto error_out;
}

if (ptrace(PTRACE_CONT, pid, 0, 0) == -1) {
perror("PTRACE_CONT");
goto error_out;
}

tm.tv_sec = 0;
tm.tv_nsec = 10000;
nanosleep(&tm, NULL);

if (kill(pid, SIGKILL) == -1) {
perror("kill SIGKILL");
goto error_out;
}

#if INTERACTIVE
puts("Sent SIGKILL. Press Enter to continue.");
getchar();
#endif

if (waitpid(pid, &stat, 0) == -1) {
perror("waitpid exit");
res = TEST_FAILED;
} else if (WIFSIGNALED(stat)) {
if (WTERMSIG(stat) != SIGKILL) {
fprintf(stderr, "Child terminated by signal %d\n",
WTERMSIG(stat));
res = TEST_FAILED;
}
} else if (WIFSTOPPED(stat)) {
fprintf(stderr, "Child notified us about signal %d - FAILED\n",
WSTOPSIG(stat));
res = TEST_FAILED;
} else if (!WIFEXITED(stat)) {
fprintf(stderr, "Child terminated abnormally, stat=0x%x\n",
stat);
res = TEST_FAILED;
} else {
fprintf(stderr, "Child exited with code %d\n",
WEXITSTATUS(stat));
res = TEST_FAILED;
if (res < WEXITSTATUS(stat))
res = WEXITSTATUS(stat);
}

return res;

error_out:
ptrace(PTRACE_KILL, pid, 0, 0);

return TEST_ABORTED;
}

static int run_child()
{
/* Attach ourselves */
if (ptrace(PTRACE_TRACEME, 0, 0, 0) == -1) {
perror("TRACE_ME");
return TEST_ABORTED;
}

for (;;)
raise(SIGCHLD);
}

int main()
{
pid_t pid;

if ((pid = fork()))
return run_debugger(pid);
else
return run_child();
}