Merge tag 'pidfd-v5.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/brauner...
[sfrench/cifs-2.6.git] / kernel / signal.c
index 5d53183e270576a630aae70650e153c0889da8dd..b7953934aa994e7993254aa6b04438815ed37f1f 100644 (file)
@@ -19,7 +19,9 @@
 #include <linux/sched/task.h>
 #include <linux/sched/task_stack.h>
 #include <linux/sched/cputime.h>
+#include <linux/file.h>
 #include <linux/fs.h>
+#include <linux/proc_fs.h>
 #include <linux/tty.h>
 #include <linux/binfmts.h>
 #include <linux/coredump.h>
@@ -3487,6 +3489,16 @@ COMPAT_SYSCALL_DEFINE4(rt_sigtimedwait_time32, compat_sigset_t __user *, uthese,
 #endif
 #endif
 
+static inline void prepare_kill_siginfo(int sig, struct kernel_siginfo *info)
+{
+       clear_siginfo(info);
+       info->si_signo = sig;
+       info->si_errno = 0;
+       info->si_code = SI_USER;
+       info->si_pid = task_tgid_vnr(current);
+       info->si_uid = from_kuid_munged(current_user_ns(), current_uid());
+}
+
 /**
  *  sys_kill - send a signal to a process
  *  @pid: the PID of the process
@@ -3496,16 +3508,125 @@ SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
 {
        struct kernel_siginfo info;
 
-       clear_siginfo(&info);
-       info.si_signo = sig;
-       info.si_errno = 0;
-       info.si_code = SI_USER;
-       info.si_pid = task_tgid_vnr(current);
-       info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
+       prepare_kill_siginfo(sig, &info);
 
        return kill_something_info(sig, &info, pid);
 }
 
+#ifdef CONFIG_PROC_FS
+/*
+ * Verify that the signaler and signalee either are in the same pid namespace
+ * or that the signaler's pid namespace is an ancestor of the signalee's pid
+ * namespace.
+ */
+static bool access_pidfd_pidns(struct pid *pid)
+{
+       struct pid_namespace *active = task_active_pid_ns(current);
+       struct pid_namespace *p = ns_of_pid(pid);
+
+       for (;;) {
+               if (!p)
+                       return false;
+               if (p == active)
+                       break;
+               p = p->parent;
+       }
+
+       return true;
+}
+
+static int copy_siginfo_from_user_any(kernel_siginfo_t *kinfo, siginfo_t *info)
+{
+#ifdef CONFIG_COMPAT
+       /*
+        * Avoid hooking up compat syscalls and instead handle necessary
+        * conversions here. Note, this is a stop-gap measure and should not be
+        * considered a generic solution.
+        */
+       if (in_compat_syscall())
+               return copy_siginfo_from_user32(
+                       kinfo, (struct compat_siginfo __user *)info);
+#endif
+       return copy_siginfo_from_user(kinfo, info);
+}
+
+/**
+ * sys_pidfd_send_signal - send a signal to a process through a task file
+ *                          descriptor
+ * @pidfd:  the file descriptor of the process
+ * @sig:    signal to be sent
+ * @info:   the signal info
+ * @flags:  future flags to be passed
+ *
+ * The syscall currently only signals via PIDTYPE_PID which covers
+ * kill(<positive-pid>, <signal>. It does not signal threads or process
+ * groups.
+ * In order to extend the syscall to threads and process groups the @flags
+ * argument should be used. In essence, the @flags argument will determine
+ * what is signaled and not the file descriptor itself. Put in other words,
+ * grouping is a property of the flags argument not a property of the file
+ * descriptor.
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+SYSCALL_DEFINE4(pidfd_send_signal, int, pidfd, int, sig,
+               siginfo_t __user *, info, unsigned int, flags)
+{
+       int ret;
+       struct fd f;
+       struct pid *pid;
+       kernel_siginfo_t kinfo;
+
+       /* Enforce flags be set to 0 until we add an extension. */
+       if (flags)
+               return -EINVAL;
+
+       f = fdget_raw(pidfd);
+       if (!f.file)
+               return -EBADF;
+
+       /* Is this a pidfd? */
+       pid = tgid_pidfd_to_pid(f.file);
+       if (IS_ERR(pid)) {
+               ret = PTR_ERR(pid);
+               goto err;
+       }
+
+       ret = -EINVAL;
+       if (!access_pidfd_pidns(pid))
+               goto err;
+
+       if (info) {
+               ret = copy_siginfo_from_user_any(&kinfo, info);
+               if (unlikely(ret))
+                       goto err;
+
+               ret = -EINVAL;
+               if (unlikely(sig != kinfo.si_signo))
+                       goto err;
+
+               if ((task_pid(current) != pid) &&
+                   (kinfo.si_code >= 0 || kinfo.si_code == SI_TKILL)) {
+                       /* Only allow sending arbitrary signals to yourself. */
+                       ret = -EPERM;
+                       if (kinfo.si_code != SI_USER)
+                               goto err;
+
+                       /* Turn this into a regular kill signal. */
+                       prepare_kill_siginfo(sig, &kinfo);
+               }
+       } else {
+               prepare_kill_siginfo(sig, &kinfo);
+       }
+
+       ret = kill_pid_info(sig, &kinfo, pid);
+
+err:
+       fdput(f);
+       return ret;
+}
+#endif /* CONFIG_PROC_FS */
+
 static int
 do_send_specific(pid_t tgid, pid_t pid, int sig, struct kernel_siginfo *info)
 {