Until we kill ->ptrace, attach must ensure ->ptrace == 0 to avoid the
races with detach. We can move these checks to ptrace_attach_task()
from the callers, and we can do this check lockless.

>From now ptrace_abort_attach() is only called when the tracee had no
chance to execute any callback, a simple UTRACE_DETACH is enough.

ptrace_attach() doesn't need ptrace_abort_attach() at all, if the
tracee is killed we don't care about attached engine.

---

 kernel/ptrace.c |   67 ++++++++++++++++++++++++++++++--------------------------
 1 file changed, 36 insertions(+), 31 deletions(-)

--- PU/kernel/ptrace.c~09_MV_PTRACE_CK  2009-08-19 16:49:25.000000000 +0200
+++ PU/kernel/ptrace.c  2009-08-19 17:41:18.000000000 +0200
@@ -471,35 +471,45 @@ static int ptrace_attach_task(struct tas
 {
        struct utrace_engine *engine;
        unsigned long events;
+       int err = -EPERM;
 
        engine = utrace_attach_task(tracee, UTRACE_ATTACH_CREATE |
                                                UTRACE_ATTACH_EXCLUSIVE |
                                                UTRACE_ATTACH_MATCH_OPS,
                                                &ptrace_utrace_ops, NULL);
        if (unlikely(IS_ERR(engine))) {
-               int err = PTR_ERR(engine);
-               if (err != -ESRCH && err != -ERESTARTNOINTR)
-                       err = -EPERM ;
+               if (PTR_ERR(engine) == -ESRCH ||
+                   PTR_ERR(engine) == -ERESTARTNOINTR)
+                       err = PTR_ERR(engine);
                return err;
        }
+
        /*
-        * We need QUIESCE for resume handling, CLONE to check
-        * for CLONE_PTRACE, other events are always reported.
-        */
-       events = UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(CLONE) |
-                UTRACE_EVENT(EXEC) | UTRACE_EVENT_SIGNAL_ALL;
-       /*
-        * It can fail only if the tracee is dead, the caller
-        * must notice this before setting PT_PTRACED.
+        * Check ->ptrace to make sure we don't race with the previous
+        * tracer which didn't finish detach. If it is clear, nobody
+        * can change it except us due to UTRACE_ATTACH_EXCLUSIVE.
         */
-       utrace_set_events(tracee, engine, events);
+       if (likely(!tracee->ptrace)) {
+               /*
+                * We need QUIESCE for resume handling, CLONE to check
+                * for CLONE_PTRACE, other events are always reported.
+                */
+               events = UTRACE_EVENT(QUIESCE) | UTRACE_EVENT(CLONE) |
+                        UTRACE_EVENT(EXEC) | UTRACE_EVENT_SIGNAL_ALL;
+               /*
+                * It can fail only if the tracee is dead, the caller
+                * must notice this before setting PT_PTRACED.
+                */
+               utrace_set_events(tracee, engine, events);
+               err = 0;
+       }
+
        utrace_engine_put(engine);
-       return 0;
+       return err;
 }
 
 static void ptrace_abort_attach(struct task_struct *tracee)
 {
-       /* XXX we raced with detach. Double check UTRACE_DETACH is enough */
        ptrace_detach_task(tracee, 0);
 }
 
@@ -613,9 +623,8 @@ int ptrace_attach(struct task_struct *ta
        retval = -EPERM;
        if (unlikely(task->exit_state))
                goto unlock_tasklist;
-       if (task->ptrace)
-               goto unlock_tasklist;
 
+       BUG_ON(task->ptrace);
        task->ptrace = PT_PTRACED;
        if (capable(CAP_SYS_PTRACE))
                task->ptrace |= PT_PTRACE_CAP;
@@ -626,8 +635,6 @@ int ptrace_attach(struct task_struct *ta
        retval = 0;
 unlock_tasklist:
        write_unlock_irq(&tasklist_lock);
-       if (retval)
-               ptrace_abort_attach(task);
 unlock_creds:
        mutex_unlock(&task->cred_guard_mutex);
 out:
@@ -650,19 +657,17 @@ int ptrace_traceme(void)
 
        ret = -EPERM;
        write_lock_irq(&tasklist_lock);
-       /* Are we already being traced? */
-       if (!current->ptrace) {
-               ret = security_ptrace_traceme(current->parent);
-               /*
-                * Check PF_EXITING to ensure ->real_parent has not passed
-                * exit_ptrace(). Otherwise we don't report the error but
-                * pretend ->real_parent untraces us right after return.
-                */
-               if (!ret && !(current->real_parent->flags & PF_EXITING)) {
-                       current->ptrace = PT_PTRACED;
-                       __ptrace_link(current, current->real_parent);
-                       detach = false;
-               }
+       BUG_ON(current->ptrace);
+       ret = security_ptrace_traceme(current->parent);
+       /*
+        * Check PF_EXITING to ensure ->real_parent has not passed
+        * exit_ptrace(). Otherwise we don't report the error but
+        * pretend ->real_parent untraces us right after return.
+        */
+       if (!ret && !(current->real_parent->flags & PF_EXITING)) {
+               current->ptrace = PT_PTRACED;
+               __ptrace_link(current, current->real_parent);
+               detach = false;
        }
        write_unlock_irq(&tasklist_lock);
 

Reply via email to