Finally, the actual fix.

ptrace_detach_task(sig => -1) is very buggy. Somehow I completely forgot
that implicit detach can race with the running tracee. Depending on how
exactly it races with ptrace_report_signal() we can have the following
problems:

        1) If the tracer exits right after ptrace(PTRACE_CONT, SIGXXX)
           the tracee can miss this signal.

        2) the tracee can report a signal and stop after it was already
           detached.

Change ptrace_detach_task(sig => -1) to set ctx->resume = UTRACE_DETACH
before anything else, change ptrace_report_signal() to check ctx->resume
before reporting a signal. This fixes the 2nd problem.

To fix the 1st problem, change !explicit case to check ->siginfo != NULL
instead of relying on get_stop_event() which can't work with the running
tracee.

Reported-by: Glen Johnson <bugpr...@us.ibm.com>
Signed-off-by: Oleg Nesterov <o...@redhat.com>
---

 kernel/ptrace-utrace.c |   41 ++++++++++++++++++++++++++++++++++-------
 1 file changed, 34 insertions(+), 7 deletions(-)

--- kstub/kernel/ptrace-utrace.c~5_implicit_detach_races        2010-09-20 
03:53:32.000000000 +0200
+++ kstub/kernel/ptrace-utrace.c        2010-09-20 03:53:32.000000000 +0200
@@ -262,7 +262,7 @@ static void ptrace_detach_task(struct ta
         * If true, the caller is PTRACE_DETACH, otherwise
         * the tracer detaches implicitly during exit.
         */
-       bool voluntary = (sig >= 0);
+       bool explicit = (sig >= 0);
        struct utrace_engine *engine = ptrace_lookup_engine(tracee);
        enum utrace_resume_action action = UTRACE_DETACH;
        struct ptrace_context *ctx;
@@ -272,24 +272,46 @@ static void ptrace_detach_task(struct ta
 
        ctx = ptrace_context(engine);
 
-       if (sig) {
+       if (!explicit) {
+               int err;
 
+               /*
+                * We are going to detach, the tracee can be running.
+                * Ensure ptrace_report_signal() won't report a signal.
+                */
+               ctx->resume = UTRACE_DETACH;
+               err = utrace_barrier_uninterruptible(tracee, engine);
+
+               if (!err && ctx->siginfo) {
+                       /*
+                        * The tracee has already reported a signal
+                        * before utrace_barrier().
+                        *
+                        * Resume it like we do in PTRACE_EVENT_SIGNAL
+                        * case below. The difference is that we can race
+                        * with ptrace_report_signal() if the tracee is
+                        * running but this doesn't matter. In any case
+                        * UTRACE_SIGNAL_REPORT must be pending and it
+                        * can return nothing but UTRACE_DETACH.
+                        */
+                       action = UTRACE_RESUME;
+               }
+
+       } else if (sig) {
                switch (get_stop_event(ctx)) {
                case PTRACE_EVENT_SYSCALL:
-                       if (voluntary)
-                               send_sig_info(sig, SEND_SIG_PRIV, tracee);
+                       send_sig_info(sig, SEND_SIG_PRIV, tracee);
                        break;
 
                case PTRACE_EVENT_SIGNAL:
-                       if (voluntary)
-                               ctx->signr = sig;
+                       ctx->signr = sig;
                        ctx->resume = UTRACE_DETACH;
                        action = UTRACE_RESUME;
                        break;
                }
        }
 
-       ptrace_wake_up(tracee, engine, action, voluntary);
+       ptrace_wake_up(tracee, engine, action, explicit);
 
        if (action != UTRACE_DETACH)
                ctx->options = PTRACE_O_DETACHED;
@@ -553,6 +575,11 @@ static u32 ptrace_report_signal(u32 acti
        }
 
        WARN_ON(ctx->siginfo);
+
+       /* Raced with the exiting tracer ? */
+       if (resume == UTRACE_DETACH)
+               return action;
+
        ctx->siginfo = info;
        /*
         * ctx->siginfo points to the caller's stack.

Reply via email to