Commit a0eb9abd authored by Eugene Syromiatnikov's avatar Eugene Syromiatnikov Committed by Christian Brauner

fork: block invalid exit signals with clone3()

Previously, higher 32 bits of exit_signal fields were lost when copied
to the kernel args structure (that uses int as a type for the respective
field). Moreover, as Oleg has noted, exit_signal is used unchecked, so
it has to be checked for sanity before use; for the legacy syscalls,
applying CSIGNAL mask guarantees that it is at least non-negative;
however, there's no such thing is done in clone3() code path, and that
can break at least thread_group_leader.

This commit adds a check to copy_clone_args_from_user() to verify that
the exit signal is limited by CSIGNAL as with legacy clone() and that
the signal is valid. With this we don't get the legacy clone behavior
were an invalid signal could be handed down and would only be detected
and ignored in do_notify_parent(). Users of clone3() will now get a
proper error when they pass an invalid exit signal. Note, that this is
not user-visible behavior since no kernel with clone3() has been
released yet.

The following program will cause a splat on a non-fixed clone3() version
and will fail correctly on a fixed version:

 #define _GNU_SOURCE
 #include <linux/sched.h>
 #include <linux/types.h>
 #include <sched.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/syscall.h>
 #include <sys/wait.h>
 #include <unistd.h>

 int main(int argc, char *argv[])
 {
        pid_t pid = -1;
        struct clone_args args = {0};
        args.exit_signal = -1;

        pid = syscall(__NR_clone3, &args, sizeof(struct clone_args));
        if (pid < 0)
                exit(EXIT_FAILURE);

        if (pid == 0)
                exit(EXIT_SUCCESS);

        wait(NULL);

        exit(EXIT_SUCCESS);
 }

Fixes: 7f192e3c ("fork: add clone3")
Reported-by: default avatarOleg Nesterov <oleg@redhat.com>
Suggested-by: default avatarOleg Nesterov <oleg@redhat.com>
Suggested-by: default avatarDmitry V. Levin <ldv@altlinux.org>
Signed-off-by: default avatarEugene Syromiatnikov <esyr@redhat.com>
Link: https://lore.kernel.org/r/4b38fa4ce420b119a4c6345f42fe3cec2de9b0b5.1568223594.git.esyr@redhat.com
[christian.brauner@ubuntu.com: simplify check and rework commit message]
Signed-off-by: default avatarChristian Brauner <christian.brauner@ubuntu.com>
parent f74c2bb9
...@@ -2338,6 +2338,8 @@ struct mm_struct *copy_init_mm(void) ...@@ -2338,6 +2338,8 @@ struct mm_struct *copy_init_mm(void)
* *
* It copies the process, and if successful kick-starts * It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required. * it and waits for it to finish using the VM if required.
*
* args->exit_signal is expected to be checked for sanity by the caller.
*/ */
long _do_fork(struct kernel_clone_args *args) long _do_fork(struct kernel_clone_args *args)
{ {
...@@ -2562,6 +2564,14 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs, ...@@ -2562,6 +2564,14 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
if (copy_from_user(&args, uargs, size)) if (copy_from_user(&args, uargs, size))
return -EFAULT; return -EFAULT;
/*
* Verify that higher 32bits of exit_signal are unset and that
* it is a valid signal
*/
if (unlikely((args.exit_signal & ~((u64)CSIGNAL)) ||
!valid_signal(args.exit_signal)))
return -EINVAL;
*kargs = (struct kernel_clone_args){ *kargs = (struct kernel_clone_args){
.flags = args.flags, .flags = args.flags,
.pidfd = u64_to_user_ptr(args.pidfd), .pidfd = u64_to_user_ptr(args.pidfd),
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment