ptrace: fix possible zombie leak on PTRACE_DETACH
[safe/jmp/linux-2.6] / kernel / ptrace.c
index 8392a9d..ee553b6 100644 (file)
 #include <asm/pgtable.h>
 #include <asm/uaccess.h>
 
+
+/*
+ * Initialize a new task whose father had been ptraced.
+ *
+ * Called from copy_process().
+ */
+void ptrace_fork(struct task_struct *child, unsigned long clone_flags)
+{
+       arch_ptrace_fork(child, clone_flags);
+}
+
 /*
  * ptrace a task: make the debugger its new parent and
  * move it to the ptrace list.
@@ -45,7 +56,7 @@ void __ptrace_link(struct task_struct *child, struct task_struct *new_parent)
  * TASK_TRACED, resume it now.
  * Requires that irqs be disabled.
  */
-void ptrace_untrace(struct task_struct *child)
+static void ptrace_untrace(struct task_struct *child)
 {
        spin_lock(&child->sighand->siglock);
        if (task_is_traced(child)) {
@@ -72,6 +83,7 @@ void __ptrace_unlink(struct task_struct *child)
        child->parent = child->real_parent;
        list_del_init(&child->ptrace_entry);
 
+       arch_ptrace_untrace(child);
        if (task_is_traced(child))
                ptrace_untrace(child);
 }
@@ -107,7 +119,7 @@ int ptrace_check_attach(struct task_struct *child, int kill)
        read_unlock(&tasklist_lock);
 
        if (!ret && !kill)
-               wait_task_inactive(child);
+               ret = wait_task_inactive(child, TASK_TRACED) ? 0 : -ESRCH;
 
        /* All systems go.. */
        return ret;
@@ -115,6 +127,8 @@ int ptrace_check_attach(struct task_struct *child, int kill)
 
 int __ptrace_may_access(struct task_struct *task, unsigned int mode)
 {
+       const struct cred *cred = current_cred(), *tcred;
+
        /* May we inspect the given task?
         * This check is used both for attaching with ptrace
         * and for allowing access to sensitive information in /proc.
@@ -127,20 +141,26 @@ int __ptrace_may_access(struct task_struct *task, unsigned int mode)
        /* Don't let security modules deny introspection */
        if (task == current)
                return 0;
-       if (((current->uid != task->euid) ||
-            (current->uid != task->suid) ||
-            (current->uid != task->uid) ||
-            (current->gid != task->egid) ||
-            (current->gid != task->sgid) ||
-            (current->gid != task->gid)) && !capable(CAP_SYS_PTRACE))
+       rcu_read_lock();
+       tcred = __task_cred(task);
+       if ((cred->uid != tcred->euid ||
+            cred->uid != tcred->suid ||
+            cred->uid != tcred->uid  ||
+            cred->gid != tcred->egid ||
+            cred->gid != tcred->sgid ||
+            cred->gid != tcred->gid) &&
+           !capable(CAP_SYS_PTRACE)) {
+               rcu_read_unlock();
                return -EPERM;
+       }
+       rcu_read_unlock();
        smp_rmb();
        if (task->mm)
                dumpable = get_dumpable(task->mm);
        if (!dumpable && !capable(CAP_SYS_PTRACE))
                return -EPERM;
 
-       return security_ptrace(current, task, mode);
+       return security_ptrace_may_access(task, mode);
 }
 
 bool ptrace_may_access(struct task_struct *task, unsigned int mode)
@@ -163,6 +183,14 @@ int ptrace_attach(struct task_struct *task)
        if (same_thread_group(task, current))
                goto out;
 
+       /* Protect exec's credential calculations against our interference;
+        * SUID, SGID and LSM creds get determined differently under ptrace.
+        */
+       retval = mutex_lock_interruptible(&current->cred_exec_mutex);
+       if (retval  < 0)
+               goto out;
+
+       retval = -EPERM;
 repeat:
        /*
         * Nasty, nasty.
@@ -202,22 +230,15 @@ repeat:
 bad:
        write_unlock_irqrestore(&tasklist_lock, flags);
        task_unlock(task);
+       mutex_unlock(&current->cred_exec_mutex);
 out:
        return retval;
 }
 
-static inline void __ptrace_detach(struct task_struct *child, unsigned int data)
-{
-       child->exit_code = data;
-       /* .. re-parent .. */
-       __ptrace_unlink(child);
-       /* .. and wake it up. */
-       if (child->exit_state != EXIT_ZOMBIE)
-               wake_up_process(child);
-}
-
 int ptrace_detach(struct task_struct *child, unsigned int data)
 {
+       int dead = 0;
+
        if (!valid_signal(data))
                return -EIO;
 
@@ -227,10 +248,19 @@ int ptrace_detach(struct task_struct *child, unsigned int data)
 
        write_lock_irq(&tasklist_lock);
        /* protect against de_thread()->release_task() */
-       if (child->ptrace)
-               __ptrace_detach(child, data);
+       if (child->ptrace) {
+               child->exit_code = data;
+
+               dead = __ptrace_detach(current, child);
+
+               if (!child->exit_state)
+                       wake_up_process(child);
+       }
        write_unlock_irq(&tasklist_lock);
 
+       if (unlikely(dead))
+               release_task(child);
+
        return 0;
 }
 
@@ -499,8 +529,7 @@ repeat:
                        goto repeat;
                }
 
-               ret = security_ptrace(current->parent, current,
-                                     PTRACE_MODE_ATTACH);
+               ret = security_ptrace_traceme(current->parent);
 
                /*
                 * Set the ptrace bit in the process ptrace flags.
@@ -546,7 +575,7 @@ struct task_struct *ptrace_get_task_struct(pid_t pid)
 #define arch_ptrace_attach(child)      do { } while (0)
 #endif
 
-asmlinkage long sys_ptrace(long request, long pid, long addr, long data)
+SYSCALL_DEFINE4(ptrace, long, request, long, pid, long, addr, long, data)
 {
        struct task_struct *child;
        long ret;
@@ -613,7 +642,7 @@ int generic_ptrace_pokedata(struct task_struct *tsk, long addr, long data)
        return (copied == sizeof(data)) ? 0 : -EIO;
 }
 
-#if defined CONFIG_COMPAT && defined __ARCH_WANT_COMPAT_SYS_PTRACE
+#if defined CONFIG_COMPAT
 #include <linux/compat.h>
 
 int compat_ptrace_request(struct task_struct *child, compat_long_t request,
@@ -710,4 +739,4 @@ asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid,
        unlock_kernel();
        return ret;
 }
-#endif /* CONFIG_COMPAT && __ARCH_WANT_COMPAT_SYS_PTRACE */
+#endif /* CONFIG_COMPAT */