FROMLIST: drm/bridge: analogix: protect power when get_modes or detect
[firefly-linux-kernel-4.4.55.git] / kernel / ptrace.c
index 335a7ae697f5986269d94a445fd27142fc75c09f..3189e51db7e896c9596d607dd1ef7926367509c5 100644 (file)
 #include <linux/compat.h>
 
 
-static int ptrace_trapping_sleep_fn(void *flags)
-{
-       schedule();
-       return 0;
-}
-
 /*
  * ptrace a task: make the debugger its new parent and
  * move it to the ptrace list.
@@ -225,6 +219,14 @@ static int ptrace_has_cap(struct user_namespace *ns, unsigned int mode)
 static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
 {
        const struct cred *cred = current_cred(), *tcred;
+       int dumpable = 0;
+       kuid_t caller_uid;
+       kgid_t caller_gid;
+
+       if (!(mode & PTRACE_MODE_FSCREDS) == !(mode & PTRACE_MODE_REALCREDS)) {
+               WARN(1, "denying ptrace access check without PTRACE_MODE_*CREDS\n");
+               return -EPERM;
+       }
 
        /* May we inspect the given task?
         * This check is used both for attaching with ptrace
@@ -234,18 +236,33 @@ static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
         * because setting up the necessary parent/child relationship
         * or halting the specified task is impossible.
         */
-       int dumpable = 0;
+
        /* Don't let security modules deny introspection */
-       if (task == current)
+       if (same_thread_group(task, current))
                return 0;
        rcu_read_lock();
+       if (mode & PTRACE_MODE_FSCREDS) {
+               caller_uid = cred->fsuid;
+               caller_gid = cred->fsgid;
+       } else {
+               /*
+                * Using the euid would make more sense here, but something
+                * in userland might rely on the old behavior, and this
+                * shouldn't be a security problem since
+                * PTRACE_MODE_REALCREDS implies that the caller explicitly
+                * used a syscall that requests access to another process
+                * (and not a filesystem syscall to procfs).
+                */
+               caller_uid = cred->uid;
+               caller_gid = cred->gid;
+       }
        tcred = __task_cred(task);
-       if (uid_eq(cred->uid, tcred->euid) &&
-           uid_eq(cred->uid, tcred->suid) &&
-           uid_eq(cred->uid, tcred->uid)  &&
-           gid_eq(cred->gid, tcred->egid) &&
-           gid_eq(cred->gid, tcred->sgid) &&
-           gid_eq(cred->gid, tcred->gid))
+       if (uid_eq(caller_uid, tcred->euid) &&
+           uid_eq(caller_uid, tcred->suid) &&
+           uid_eq(caller_uid, tcred->uid)  &&
+           gid_eq(caller_gid, tcred->egid) &&
+           gid_eq(caller_gid, tcred->sgid) &&
+           gid_eq(caller_gid, tcred->gid))
                goto ok;
        if (ptrace_has_cap(tcred->user_ns, mode))
                goto ok;
@@ -257,7 +274,8 @@ ok:
        if (task->mm)
                dumpable = get_dumpable(task->mm);
        rcu_read_lock();
-       if (!dumpable && !ptrace_has_cap(__task_cred(task)->user_ns, mode)) {
+       if (dumpable != SUID_DUMP_USER &&
+           !ptrace_has_cap(__task_cred(task)->user_ns, mode)) {
                rcu_read_unlock();
                return -EPERM;
        }
@@ -311,7 +329,7 @@ static int ptrace_attach(struct task_struct *task, long request,
                goto out;
 
        task_lock(task);
-       retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH);
+       retval = __ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS);
        task_unlock(task);
        if (retval)
                goto unlock_creds;
@@ -370,7 +388,7 @@ unlock_creds:
 out:
        if (!retval) {
                wait_on_bit(&task->jobctl, JOBCTL_TRAPPING_BIT,
-                           ptrace_trapping_sleep_fn, TASK_UNINTERRUPTIBLE);
+                           TASK_UNINTERRUPTIBLE);
                proc_ptrace_connector(task, PTRACE_ATTACH);
        }
 
@@ -461,8 +479,6 @@ static bool __ptrace_detach(struct task_struct *tracer, struct task_struct *p)
 
 static int ptrace_detach(struct task_struct *child, unsigned int data)
 {
-       bool dead = false;
-
        if (!valid_signal(data))
                return -EIO;
 
@@ -472,54 +488,38 @@ static int ptrace_detach(struct task_struct *child, unsigned int data)
 
        write_lock_irq(&tasklist_lock);
        /*
-        * This child can be already killed. Make sure de_thread() or
-        * our sub-thread doing do_wait() didn't do release_task() yet.
+        * We rely on ptrace_freeze_traced(). It can't be killed and
+        * untraced by another thread, it can't be a zombie.
         */
-       if (child->ptrace) {
-               child->exit_code = data;
-               dead = __ptrace_detach(current, child);
-       }
+       WARN_ON(!child->ptrace || child->exit_state);
+       /*
+        * tasklist_lock avoids the race with wait_task_stopped(), see
+        * the comment in ptrace_resume().
+        */
+       child->exit_code = data;
+       __ptrace_detach(current, child);
        write_unlock_irq(&tasklist_lock);
 
        proc_ptrace_connector(child, PTRACE_DETACH);
-       if (unlikely(dead))
-               release_task(child);
 
        return 0;
 }
 
 /*
  * Detach all tasks we were using ptrace on. Called with tasklist held
- * for writing, and returns with it held too. But note it can release
- * and reacquire the lock.
+ * for writing.
  */
-void exit_ptrace(struct task_struct *tracer)
-       __releases(&tasklist_lock)
-       __acquires(&tasklist_lock)
+void exit_ptrace(struct task_struct *tracer, struct list_head *dead)
 {
        struct task_struct *p, *n;
-       LIST_HEAD(ptrace_dead);
-
-       if (likely(list_empty(&tracer->ptraced)))
-               return;
 
        list_for_each_entry_safe(p, n, &tracer->ptraced, ptrace_entry) {
                if (unlikely(p->ptrace & PT_EXITKILL))
                        send_sig_info(SIGKILL, SEND_SIG_FORCED, p);
 
                if (__ptrace_detach(tracer, p))
-                       list_add(&p->ptrace_entry, &ptrace_dead);
-       }
-
-       write_unlock_irq(&tasklist_lock);
-       BUG_ON(!list_empty(&tracer->ptraced));
-
-       list_for_each_entry_safe(p, n, &ptrace_dead, ptrace_entry) {
-               list_del_init(&p->ptrace_entry);
-               release_task(p);
+                       list_add(&p->ptrace_entry, dead);
        }
-
-       write_lock_irq(&tasklist_lock);
 }
 
 int ptrace_readdata(struct task_struct *tsk, unsigned long src, char __user *dst, int len)
@@ -579,6 +579,19 @@ static int ptrace_setoptions(struct task_struct *child, unsigned long data)
        if (data & ~(unsigned long)PTRACE_O_MASK)
                return -EINVAL;
 
+       if (unlikely(data & PTRACE_O_SUSPEND_SECCOMP)) {
+               if (!config_enabled(CONFIG_CHECKPOINT_RESTORE) ||
+                   !config_enabled(CONFIG_SECCOMP))
+                       return -EINVAL;
+
+               if (!capable(CAP_SYS_ADMIN))
+                       return -EPERM;
+
+               if (seccomp_mode(&current->seccomp) != SECCOMP_MODE_DISABLED ||
+                   current->ptrace & PT_SUSPEND_SECCOMP)
+                       return -EPERM;
+       }
+
        /* Avoid intermediate state when all opts are cleared */
        flags = child->ptrace;
        flags &= ~(PTRACE_O_MASK << PT_OPT_FLAG_SHIFT);
@@ -719,6 +732,8 @@ static int ptrace_peek_siginfo(struct task_struct *child,
 static int ptrace_resume(struct task_struct *child, long request,
                         unsigned long data)
 {
+       bool need_siglock;
+
        if (!valid_signal(data))
                return -EIO;
 
@@ -746,8 +761,26 @@ static int ptrace_resume(struct task_struct *child, long request,
                user_disable_single_step(child);
        }
 
+       /*
+        * Change ->exit_code and ->state under siglock to avoid the race
+        * with wait_task_stopped() in between; a non-zero ->exit_code will
+        * wrongly look like another report from tracee.
+        *
+        * Note that we need siglock even if ->exit_code == data and/or this
+        * status was not reported yet, the new status must not be cleared by
+        * wait_task_stopped() after resume.
+        *
+        * If data == 0 we do not care if wait_task_stopped() reports the old
+        * status and clears the code too; this can't race with the tracee, it
+        * takes siglock after resume.
+        */
+       need_siglock = data && !thread_group_empty(current);
+       if (need_siglock)
+               spin_lock_irq(&child->sighand->siglock);
        child->exit_code = data;
        wake_up_state(child, __TASK_TRACED);
+       if (need_siglock)
+               spin_unlock_irq(&child->sighand->siglock);
 
        return 0;
 }
@@ -844,6 +877,47 @@ int ptrace_request(struct task_struct *child, long request,
                        ret = ptrace_setsiginfo(child, &siginfo);
                break;
 
+       case PTRACE_GETSIGMASK:
+               if (addr != sizeof(sigset_t)) {
+                       ret = -EINVAL;
+                       break;
+               }
+
+               if (copy_to_user(datavp, &child->blocked, sizeof(sigset_t)))
+                       ret = -EFAULT;
+               else
+                       ret = 0;
+
+               break;
+
+       case PTRACE_SETSIGMASK: {
+               sigset_t new_set;
+
+               if (addr != sizeof(sigset_t)) {
+                       ret = -EINVAL;
+                       break;
+               }
+
+               if (copy_from_user(&new_set, datavp, sizeof(sigset_t))) {
+                       ret = -EFAULT;
+                       break;
+               }
+
+               sigdelsetmask(&new_set, sigmask(SIGKILL)|sigmask(SIGSTOP));
+
+               /*
+                * Every thread does recalc_sigpending() after resume, so
+                * retarget_shared_pending() and recalc_sigpending() are not
+                * called here.
+                */
+               spin_lock_irq(&child->sighand->siglock);
+               child->blocked = new_set;
+               spin_unlock_irq(&child->sighand->siglock);
+
+               ret = 0;
+               break;
+       }
+
        case PTRACE_INTERRUPT:
                /*
                 * Stop tracee without any side-effect on signal or job
@@ -948,8 +1022,7 @@ int ptrace_request(struct task_struct *child, long request,
 
 #ifdef CONFIG_HAVE_ARCH_TRACEHOOK
        case PTRACE_GETREGSET:
-       case PTRACE_SETREGSET:
-       {
+       case PTRACE_SETREGSET: {
                struct iovec kiov;
                struct iovec __user *uiov = datavp;
 
@@ -966,6 +1039,11 @@ int ptrace_request(struct task_struct *child, long request,
                break;
        }
 #endif
+
+       case PTRACE_SECCOMP_GET_FILTER:
+               ret = seccomp_get_filter(child, addr, datavp);
+               break;
+
        default:
                break;
        }
@@ -1059,7 +1137,6 @@ int generic_ptrace_pokedata(struct task_struct *tsk, unsigned long addr,
 }
 
 #if defined CONFIG_COMPAT
-#include <linux/compat.h>
 
 int compat_ptrace_request(struct task_struct *child, compat_long_t request,
                          compat_ulong_t addr, compat_ulong_t data)
@@ -1139,8 +1216,8 @@ int compat_ptrace_request(struct task_struct *child, compat_long_t request,
        return ret;
 }
 
-asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid,
-                                 compat_long_t addr, compat_long_t data)
+COMPAT_SYSCALL_DEFINE4(ptrace, compat_long_t, request, compat_long_t, pid,
+                      compat_long_t, addr, compat_long_t, data)
 {
        struct task_struct *child;
        long ret;
@@ -1181,19 +1258,3 @@ asmlinkage long compat_sys_ptrace(compat_long_t request, compat_long_t pid,
        return ret;
 }
 #endif /* CONFIG_COMPAT */
-
-#ifdef CONFIG_HAVE_HW_BREAKPOINT
-int ptrace_get_breakpoints(struct task_struct *tsk)
-{
-       if (atomic_inc_not_zero(&tsk->ptrace_bp_refcnt))
-               return 0;
-
-       return -1;
-}
-
-void ptrace_put_breakpoints(struct task_struct *tsk)
-{
-       if (atomic_dec_and_test(&tsk->ptrace_bp_refcnt))
-               flush_ptrace_hw_breakpoint(tsk);
-}
-#endif /* CONFIG_HAVE_HW_BREAKPOINT */