]> git.hungrycats.org Git - linux/commitdiff
[PATCH] fix PTRACE_ATTACH race with real parent's wait calls
authorRoland McGrath <roland@redhat.com>
Mon, 18 Oct 2004 15:53:35 +0000 (08:53 -0700)
committerLinus Torvalds <torvalds@ppc970.osdl.org>
Mon, 18 Oct 2004 15:53:35 +0000 (08:53 -0700)
There is a race between PTRACE_ATTACH and the real parent calling wait.
For a moment, the task is put in PT_PTRACED but with its parent still
pointing to its real_parent.  In this circumstance, if the real parent
calls wait without the WUNTRACED flag, he can see a stopped child status,
which wait should never return without WUNTRACED when the caller is not
using ptrace.  Here it is not the caller that is using ptrace, but some
third party.

This patch avoids this race condition by adding the PT_ATTACHED flag to
distinguish a real parent from a ptrace_attach parent when PT_PTRACED is
set, and then having wait use this flag to confirm that things are in order
and not consider the child ptraced when its ->ptrace flags are set but its
parent links have not yet been switched.  (ptrace_check_attach also uses it
similarly to rule out a possible race with a bogus ptrace call by the real
parent during ptrace_attach.)

While looking into this, I noticed that every arch's sys_execve has:

current->ptrace &= ~PT_DTRACE;

with no locking at all.  So, if an exec happens in a race with
PTRACE_ATTACH, you could wind up with ->ptrace not having PT_PTRACED set
because this store clobbered it.  That will cause later BUG hits because
the parent links indicate ptracedness but the flag is not set.  The patch
corrects all the places I found to use task_lock around diddling ->ptrace
when it's possible to be racing with ptrace_attach.  (The ptrace operation
code itself doesn't have this issue because it already excludes anyone else
being in ptrace_attach.)

Signed-off-by: Roland McGrath <roland@redhat.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
21 files changed:
arch/i386/kernel/process.c
arch/m32r/kernel/process.c
arch/parisc/hpux/fs.c
arch/parisc/kernel/process.c
arch/parisc/kernel/sys_parisc32.c
arch/ppc/kernel/process.c
arch/ppc64/kernel/process.c
arch/ppc64/kernel/sys_ppc32.c
arch/s390/kernel/compat_linux.c
arch/s390/kernel/process.c
arch/sh/kernel/process.c
arch/sh64/kernel/process.c
arch/sparc/kernel/process.c
arch/sparc64/kernel/process.c
arch/sparc64/kernel/sys_sparc32.c
arch/um/kernel/exec_kern.c
arch/x86_64/ia32/sys_ia32.c
arch/x86_64/kernel/process.c
include/linux/ptrace.h
kernel/exit.c
kernel/ptrace.c

index 0095fa1dda6f6452f7a51fa08333458f0f75a6d0..45df20c2e77d0f24940274b63c45a6009c0b749f 100644 (file)
@@ -656,7 +656,9 @@ asmlinkage int sys_execve(struct pt_regs regs)
                        (char __user * __user *) regs.edx,
                        &regs);
        if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
                /* Make sure we don't return using sysenter.. */
                set_thread_flag(TIF_IRET);
        }
index 9e7de27a8e0d74fc3a04bf7c907179348d2d1c32..c301e0e61173c73117163d36af9a3549186174c3 100644 (file)
@@ -335,8 +335,11 @@ asmlinkage int sys_execve(char __user *ufilename, char __user * __user *uargv, c
                goto out;
 
        error = do_execve(filename, uargv, uenvp, &regs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 out:
        return error;
index 0800eb3eade6b93821b444fbd09e7deaa29c8408..d7c80edf44899323f7401c59fd8e1c64be5648f6 100644 (file)
@@ -43,8 +43,11 @@ int hpux_execve(struct pt_regs *regs)
        error = do_execve(filename, (char **) regs->gr[25],
                (char **)regs->gr[24], regs);
 
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 
 out:
index d7365b958f7eb7040539a70a73c5ac3769e6a3f4..320fca55fa1a4c4c49812ecbaa55161093888714 100644 (file)
@@ -363,8 +363,11 @@ asmlinkage int sys_execve(struct pt_regs *regs)
                goto out;
        error = do_execve(filename, (char **) regs->gr[25],
                (char **) regs->gr[24], regs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 out:
 
index e78332541b180592131948fb23ab1953230fa78f..2b42313c1017a397b46a350f44115b61ed070bc1 100644 (file)
@@ -80,8 +80,11 @@ asmlinkage int sys32_execve(struct pt_regs *regs)
                goto out;
        error = compat_do_execve(filename, compat_ptr(regs->gr[25]),
                                 compat_ptr(regs->gr[24]), regs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 out:
 
index d9ab6a7de95c85f6976c182d0a2c05e6c09b81ac..94298ebecabe16f66f1fbb2c7e6706bdb35314b8 100644 (file)
@@ -598,8 +598,11 @@ int sys_execve(unsigned long a0, unsigned long a1, unsigned long a2,
        preempt_enable();
        error = do_execve(filename, (char __user *__user *) a1,
                          (char __user *__user *) a2, regs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 out:
        return error;
index 8211337074cb5200755c82868a35ff81a876507a..1529ba6db632b4006f38932261ba30a6339da06f 100644 (file)
@@ -512,8 +512,11 @@ int sys_execve(unsigned long a0, unsigned long a1, unsigned long a2,
        error = do_execve(filename, (char __user * __user *) a1,
                                    (char __user * __user *) a2, regs);
   
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 
 out:
index 710b7cd9ec470e5985f57141ff60777b89239219..6a514b3d977c5531aaf09574a2967b55a1c49297 100644 (file)
@@ -621,8 +621,11 @@ long sys32_execve(unsigned long a0, unsigned long a1, unsigned long a2,
 
        error = compat_do_execve(filename, compat_ptr(a1), compat_ptr(a2), regs);
 
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 
 out:
index 5c0a63aff9393a70f5153d4aef7ce64517fca900..01f57183d9ba29fbb1aa9f2fb4853754ba4436b7 100644 (file)
@@ -751,7 +751,9 @@ sys32_execve(struct pt_regs regs)
                                 compat_ptr(regs.gprs[4]), &regs);
        if (error == 0)
        {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
                current->thread.fp_regs.fpc=0;
                __asm__ __volatile__
                        ("sr  0,0\n\t"
index 5d56e77c74eee3ee086377599ff622d8bf40d394..566b34f4c3947df1a9dd42e984583c84a8d8ea77 100644 (file)
@@ -340,7 +340,9 @@ asmlinkage long sys_execve(struct pt_regs regs)
         error = do_execve(filename, (char __user * __user *) regs.gprs[3],
                          (char __user * __user *) regs.gprs[4], &regs);
        if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
                current->thread.fp_regs.fpc = 0;
                if (MACHINE_HAS_IEEE)
                        asm volatile("sfpc %0,%0" : : "d" (0));
index c9a43c8df32fb461cb3281238064283bbc2f676e..11019fa5ab86d24dc14491e2f78e46b356d9ce99 100644 (file)
@@ -481,8 +481,11 @@ asmlinkage int sys_execve(char *ufilename, char **uargv,
                          (char __user * __user *)uargv,
                          (char __user * __user *)uenvp,
                          &regs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 out:
        return error;
index 13cec35796ab9910408f53c64b43dc43d7087145..68f764389781d5452bb8dc7b63df7d2ad97ea89c 100644 (file)
@@ -862,8 +862,11 @@ asmlinkage int sys_execve(char *ufilename, char **uargv,
                          (char __user * __user *)uargv,
                          (char __user * __user *)uenvp,
                          pregs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
 out:
        unlock_kernel();
index 1dc918135eb333ee3053b490fc11e9984af51461..f32eaf5b6256e4b7186d4b9ca9fc7b176263abf5 100644 (file)
@@ -670,8 +670,11 @@ asmlinkage int sparc_execve(struct pt_regs *regs)
                          (char __user * __user *)regs->u_regs[base + UREG_I2],
                          regs);
        putname(filename);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
 out:
        return error;
 }
index f3e3c657e9cb87c5c77d052f8ff3580de1faed1a..56fecbdae8bc1ee00abdafb902306ab3fd2fa007 100644 (file)
@@ -829,7 +829,9 @@ asmlinkage int sparc_execve(struct pt_regs *regs)
                current_thread_info()->xfsr[0] = 0;
                current_thread_info()->fpsaved[0] = 0;
                regs->tstate &= ~TSTATE_PEF;
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
        }
 out:
        return error;
index b81f15521e86bb428a52419c981a8e4556038a79..28014dc7419453515d551b9119b90749899b4556 100644 (file)
@@ -1274,7 +1274,9 @@ asmlinkage long sparc32_execve(struct pt_regs *regs)
                current_thread_info()->xfsr[0] = 0;
                current_thread_info()->fpsaved[0] = 0;
                regs->tstate &= ~TSTATE_PEF;
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
        }
 out:
        return error;
index a5f21283d2cd840af2448fe23d1993189099155f..f6c84475bb836e952567773bac70d50187a4954e 100644 (file)
@@ -43,7 +43,9 @@ static int execve1(char *file, char **argv, char **env)
 #endif
         error = do_execve(file, argv, env, &current->thread.regs);
         if (error == 0){
+               task_lock(current);
                 current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
                 set_cmdline(current_cmd());
         }
         return(error);
index 50465345140add226e376839046c4dfdd8436a81..a854fb32963ab1d877a329f72d0cd531f7ccb8e4 100644 (file)
@@ -1135,8 +1135,11 @@ asmlinkage long sys32_execve(char __user *name, compat_uptr_t __user *argv,
        if (IS_ERR(filename))
                return error;
        error = compat_do_execve(filename, argv, envp, regs);
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
        return error;
 }
index 6e835be5f26a6aba9da3ffc878e3419599927513..73dfb27a53520d5689a5b97dc435d7ec4c3a56a1 100644 (file)
@@ -542,8 +542,11 @@ long sys_execve(char __user *name, char __user * __user *argv,
        if (IS_ERR(filename)) 
                return error;
        error = do_execve(filename, argv, envp, &regs); 
-       if (error == 0)
+       if (error == 0) {
+               task_lock(current);
                current->ptrace &= ~PT_DTRACE;
+               task_unlock(current);
+       }
        putname(filename);
        return error;
 }
index 53132cd80429e69acec2c7c9f284ce21e0be9f41..f182471250912824f7ec7872c284780b71f1aa04 100644 (file)
@@ -63,6 +63,7 @@
 #define PT_TRACE_EXEC  0x00000080
 #define PT_TRACE_VFORK_DONE    0x00000100
 #define PT_TRACE_EXIT  0x00000200
+#define PT_ATTACHED    0x00000400      /* parent != real_parent */
 
 #define PT_TRACE_MASK  0x000003f4
 
index 426d3ae722ba1815294dd755417b0c895ccd40d9..ca9a9e21c4444eef2d9d365634235f14880d931f 100644 (file)
@@ -1280,6 +1280,22 @@ static int wait_task_continued(task_t *p, int noreap,
 }
 
 
+static inline int my_ptrace_child(struct task_struct *p)
+{
+       if (!(p->ptrace & PT_PTRACED))
+               return 0;
+       if (!(p->ptrace & PT_ATTACHED))
+               return 1;
+       /*
+        * This child was PTRACE_ATTACH'd.  We should be seeing it only if
+        * we are the attacher.  If we are the real parent, this is a race
+        * inside ptrace_attach.  It is waiting for the tasklist_lock,
+        * which we have to switch the parent links, but has already set
+        * the flags in p->ptrace.
+        */
+       return (p->parent != p->real_parent);
+}
+
 static long do_wait(pid_t pid, int options, struct siginfo __user *infop,
                    int __user *stat_addr, struct rusage __user *ru)
 {
@@ -1308,12 +1324,12 @@ repeat:
 
                        switch (p->state) {
                        case TASK_TRACED:
-                               if (!(p->ptrace & PT_PTRACED))
+                               if (!my_ptrace_child(p))
                                        continue;
                                /*FALLTHROUGH*/
                        case TASK_STOPPED:
                                if (!(options & WUNTRACED) &&
-                                   !(p->ptrace & PT_PTRACED))
+                                   !my_ptrace_child(p))
                                        continue;
                                retval = wait_task_stopped(p, ret == 2,
                                                           (options & WNOWAIT),
index b14b4a4677296a2e9fc2c4c961b314ffd64e623d..09ba057222c3322b250688af256dc4f03cef5488 100644 (file)
@@ -82,7 +82,8 @@ int ptrace_check_attach(struct task_struct *child, int kill)
         */
        read_lock(&tasklist_lock);
        if ((child->ptrace & PT_PTRACED) && child->parent == current &&
-           child->signal != NULL) {
+           (!(child->ptrace & PT_ATTACHED) || child->real_parent != current)
+           && child->signal != NULL) {
                ret = 0;
                spin_lock_irq(&child->sighand->siglock);
                if (child->state == TASK_STOPPED) {
@@ -131,7 +132,7 @@ int ptrace_attach(struct task_struct *task)
                goto bad;
 
        /* Go */
-       task->ptrace |= PT_PTRACED;
+       task->ptrace |= PT_PTRACED | PT_ATTACHED;
        if (capable(CAP_SYS_PTRACE))
                task->ptrace |= PT_PTRACE_CAP;
        task_unlock(task);