ptrace(2) race testcase + proposed patch
Tijl Coosemans
tijl at ulyssis.org
Thu Aug 2 22:29:04 UTC 2007
While working on Wine I hit on a race between a ptrace PT_DETACH and
subsequent PT_ATTACH which causes the SIGSTOP of this attach to get
lost and never delivered. Attached are a test program and a proposed
patch.
The test program forks a child and loops attaching and detaching to it.
It can hang during the wait4 call.
The problem occurs when the stopped child process returns from the
ptracestop() call in sys/kern/kern_sig.c:issignal() after the parent
has already returned from the next PT_ATTACH. Then the signal that
caused the process to stop is taken off the sigqueue, but this may
already be a new SIGSTOP from the next PT_ATTACH. The solution is to
take the signal off the queue before calling ptracestop().
A second cause for the problem is in sys/kern/sys_process.c:kern_ptrace()
where PT_ATTACH sets td_xsig of a thread to SIGSTOP. If this thread
then returns from ptracestop() (returning td_xsig) and it was previously
stopped by a SIGSTOP, the new SIGSTOP is ignored and deleted at the end
of issignal(). The solution is to deliver the signal via td_xsig only
when the process is currently stopped (and now continuing) and
otherwise (PT_ATTACH) using psignal(). This is similar to equivalent
code in NetBSD.
I was hoping if someone could review this to make sure I did the right
thing, because I'm not entirely familiar with this code.
-------------- next part --------------
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main( void ) {
pid_t pid;
int status;
/* fork dummy child process */
pid = fork();
if( pid == 0 ) {
/* child does nothing */
for( ; ; ) {
sleep( 1 );
}
} else {
/* parent */
sleep( 1 );
for( ; ; ) {
/* loop: attach, wait, detach */
printf( "attach " );
fflush( stdout );
ptrace( PT_ATTACH, pid, ( caddr_t ) 0, 0 );
printf( "wait " );
fflush( stdout );
wait4( pid, &status, 0, NULL );
printf( "detach " );
fflush( stdout );
ptrace( PT_DETACH, pid, ( caddr_t ) 1, 0 );
printf( "\n" );
fflush( stdout );
}
}
return 0;
}
-------------- next part --------------
--- sys/kern/kern_sig.c.orig 2007-07-19 10:49:16.000000000 +0200
+++ sys/kern/kern_sig.c 2007-07-26 01:37:22.000000000 +0200
@@ -2543,6 +2543,20 @@
continue;
}
if (p->p_flag & P_TRACED && (p->p_flag & P_PPWAIT) == 0) {
+ ksiginfo_t ksi;
+
+ /*
+ * clear old signal.
+ * XXX shrug off debugger, it causes siginfo to
+ * be thrown away.
+ */
+ ksiginfo_init( &ksi );
+ sigqueue_get(&td->td_sigqueue, sig, &ksi);
+#ifdef KSE
+ if (td->td_pflags & TDP_SA)
+ SIGADDSET(td->td_sigmask, sig);
+#endif
+
/*
* If traced, always stop.
*/
@@ -2550,20 +2564,7 @@
newsig = ptracestop(td, sig);
mtx_lock(&ps->ps_mtx);
-#ifdef KSE
- if (td->td_pflags & TDP_SA)
- SIGADDSET(td->td_sigmask, sig);
-
-#endif
if (sig != newsig) {
- ksiginfo_t ksi;
- /*
- * clear old signal.
- * XXX shrug off debugger, it causes siginfo to
- * be thrown away.
- */
- sigqueue_get(&td->td_sigqueue, sig, &ksi);
-
/*
* If parent wants us to take the signal,
* then it will leave it in p->p_xstat;
@@ -2585,6 +2586,9 @@
if (SIGISMEMBER(td->td_sigmask, sig))
continue;
signotify(td);
+ } else {
+ /* Add old signal back. */
+ sigqueue_add(&td->td_sigqueue, sig, &ksi);
}
/*
--- sys/kern/sys_process.c.orig 2007-08-02 15:53:10.000000000 +0200
+++ sys/kern/sys_process.c 2007-08-02 19:49:56.000000000 +0200
@@ -779,14 +779,15 @@
sx_xunlock(&proctree_lock);
proctree_locked = 0;
}
- /* deliver or queue signal */
- thread_lock(td2);
- td2->td_flags &= ~TDF_XSIG;
- thread_unlock(td2);
- td2->td_xsig = data;
p->p_xstat = data;
p->p_xthread = NULL;
if ((p->p_flag & (P_STOPPED_SIG | P_STOPPED_TRACE)) != 0) {
+ /* deliver or queue signal */
+ thread_lock(td2);
+ td2->td_flags &= ~TDF_XSIG;
+ thread_unlock(td2);
+ td2->td_xsig = data;
+
PROC_SLOCK(p);
if (req == PT_DETACH) {
struct thread *td3;
@@ -809,11 +810,10 @@
p->p_flag &= ~(P_STOPPED_TRACE|P_STOPPED_SIG|P_WAITED);
thread_unsuspend(p);
PROC_SUNLOCK(p);
+ } else {
+ if (data)
+ psignal(p, data);
}
-
- if (data)
- psignal(p, data);
-
break;
case PT_WRITE_I:
More information about the freebsd-hackers
mailing list