diff --git a/pkg/abi/linux/clone.go b/pkg/abi/linux/clone.go index 322a4ef5a..ae1276d1e 100644 --- a/pkg/abi/linux/clone.go +++ b/pkg/abi/linux/clone.go @@ -46,9 +46,16 @@ const ( // Only passable via clone3(2). CLONE_CLEAR_SIGHAND = 0x100000000 CLONE_INTO_CGROUP = 0x200000000 + + // Sizeof first published struct. + CLONE_ARGS_SIZE_VER0 = 64 + // Sizeof third published struct. + CLONE_ARGS_SIZE_VER2 = 88 ) // CloneArgs is struct clone_args, from include/uapi/linux/sched.h. +// +// +marshal type CloneArgs struct { Flags uint64 Pidfd uint64 diff --git a/pkg/sentry/kernel/task_clone.go b/pkg/sentry/kernel/task_clone.go index 5885cfeb9..c83eb22e4 100644 --- a/pkg/sentry/kernel/task_clone.go +++ b/pkg/sentry/kernel/task_clone.go @@ -30,6 +30,16 @@ import ( "gvisor.dev/gvisor/pkg/usermem" ) +// SupportedFlags is the bitwise OR of all the supported flags for clone. +// TODO(b/290826530): Implement CLONE_INTO_CGROUP when cgroups v2 is +// implemented. +const SupportedFlags = linux.CLONE_VM | linux.CLONE_FS | linux.CLONE_FILES | linux.CLONE_SYSVSEM | + linux.CLONE_THREAD | linux.CLONE_SIGHAND | linux.CLONE_CHILD_SETTID | linux.CLONE_NEWPID | + linux.CLONE_CHILD_CLEARTID | linux.CLONE_CHILD_SETTID | linux.CLONE_PARENT | + linux.CLONE_PARENT_SETTID | linux.CLONE_SETTLS | linux.CLONE_NEWUSER | linux.CLONE_NEWUTS | + linux.CLONE_NEWIPC | linux.CLONE_NEWNET | linux.CLONE_PTRACE | linux.CLONE_UNTRACED | + linux.CLONE_IO | linux.CLONE_VFORK + // Clone implements the clone(2) syscall and returns the thread ID of the new // task in t's PID namespace. Clone may return both a non-zero thread ID and a // non-nil error. @@ -37,12 +47,18 @@ import ( // Preconditions: The caller must be running Task.doSyscallInvoke on the task // goroutine. func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { + if args.Flags&^SupportedFlags != 0 { + return 0, nil, linuxerr.EINVAL + } // Since signal actions may refer to application signal handlers by virtual // address, any set of signal handlers must refer to the same address // space. if args.Flags&(linux.CLONE_SIGHAND|linux.CLONE_VM) == linux.CLONE_SIGHAND { return 0, nil, linuxerr.EINVAL } + if args.SetTID != 0 { + return 0, nil, linuxerr.ENOTSUP + } // In order for the behavior of thread-group-directed signals to be sane, // all tasks in a thread group must share signal handlers. if args.Flags&(linux.CLONE_THREAD|linux.CLONE_SIGHAND) == linux.CLONE_THREAD { @@ -160,7 +176,7 @@ func (t *Task) Clone(args *linux.CloneArgs) (ThreadID, *SyscallControl, error) { // clone() returns 0 in the child. image.Arch.SetReturn(0) if args.Stack != 0 { - image.Arch.SetStack(uintptr(args.Stack)) + image.Arch.SetStack(uintptr(args.Stack + args.StackSize)) } if args.Flags&linux.CLONE_SETTLS != 0 { if !image.Arch.SetTLS(uintptr(args.TLS)) { diff --git a/pkg/sentry/syscalls/linux/linux64.go b/pkg/sentry/syscalls/linux/linux64.go index 17bb0e1fd..f40f7fe69 100644 --- a/pkg/sentry/syscalls/linux/linux64.go +++ b/pkg/sentry/syscalls/linux/linux64.go @@ -108,7 +108,7 @@ var AMD64 = &kernel.SyscallTable{ 53: syscalls.SupportedPoint("socketpair", SocketPair, PointSocketpair), 54: syscalls.Supported("setsockopt", SetSockOpt), 55: syscalls.Supported("getsockopt", GetSockOpt), - 56: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Mount namespace (CLONE_NEWNS) not supported. Options CLONE_PARENT, CLONE_SYSVSEM not supported.", nil), + 56: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Options CLONE_PIDFD, CLONE_NEWNS, CLONE_PARENT, CLONE_NEWTIME, CLONE_CLEAR_SIGHAND, and CLONE_SYSVSEM not supported.", nil), 57: syscalls.SupportedPoint("fork", Fork, PointFork), 58: syscalls.SupportedPoint("vfork", Vfork, PointVfork), 59: syscalls.SupportedPoint("execve", Execve, PointExecve), @@ -403,7 +403,7 @@ var AMD64 = &kernel.SyscallTable{ 432: syscalls.ErrorWithEvent("fsmount", linuxerr.ENOSYS, "", nil), 433: syscalls.ErrorWithEvent("fspick", linuxerr.ENOSYS, "", nil), 434: syscalls.ErrorWithEvent("pidfd_open", linuxerr.ENOSYS, "", nil), - 435: syscalls.ErrorWithEvent("clone3", linuxerr.ENOSYS, "", nil), + 435: syscalls.PartiallySupported("clone3", Clone3, "Options CLONE_PIDFD, CLONE_NEWNS, CLONE_INTO_CGROUP, CLONE_NEWTIME, CLONE_CLEAR_SIGHAND, CLONE_PARENT, CLONE_SYSVSEM and, SetTid are not supported.", nil), 436: syscalls.Supported("close_range", CloseRange), 439: syscalls.Supported("faccessat2", Faccessat2), 441: syscalls.Supported("epoll_pwait2", EpollPwait2), @@ -651,7 +651,7 @@ var ARM64 = &kernel.SyscallTable{ 217: syscalls.Error("add_key", linuxerr.EACCES, "Not available to user.", nil), 218: syscalls.Error("request_key", linuxerr.EACCES, "Not available to user.", nil), 219: syscalls.Error("keyctl", linuxerr.EACCES, "Not available to user.", nil), - 220: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Mount namespace (CLONE_NEWNS) not supported. Options CLONE_PARENT, CLONE_SYSVSEM not supported.", nil), + 220: syscalls.PartiallySupportedPoint("clone", Clone, PointClone, "Options CLONE_PIDFD, CLONE_NEWNS, CLONE_PARENT, CLONE_NEWTIME, CLONE_CLEAR_SIGHAND, and CLONE_SYSVSEM not supported.", nil), 221: syscalls.SupportedPoint("execve", Execve, PointExecve), 222: syscalls.Supported("mmap", Mmap), 223: syscalls.PartiallySupported("fadvise64", Fadvise64, "Not all options are supported.", nil), @@ -724,7 +724,7 @@ var ARM64 = &kernel.SyscallTable{ 432: syscalls.ErrorWithEvent("fsmount", linuxerr.ENOSYS, "", nil), 433: syscalls.ErrorWithEvent("fspick", linuxerr.ENOSYS, "", nil), 434: syscalls.ErrorWithEvent("pidfd_open", linuxerr.ENOSYS, "", nil), - 435: syscalls.ErrorWithEvent("clone3", linuxerr.ENOSYS, "", nil), + 435: syscalls.PartiallySupported("clone3", Clone3, "Options CLONE_PIDFD, CLONE_NEWNS, CLONE_INTO_CGROUP, CLONE_NEWTIME, CLONE_CLEAR_SIGHAND, CLONE_PARENT, CLONE_SYSVSEM and clone_args.set_tid are not supported.", nil), 436: syscalls.Supported("close_range", CloseRange), 439: syscalls.Supported("faccessat2", Faccessat2), 441: syscalls.Supported("epoll_pwait2", EpollPwait2), diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go index ddae15cd3..6aa73290c 100644 --- a/pkg/sentry/syscalls/linux/sys_thread.go +++ b/pkg/sentry/syscalls/linux/sys_thread.go @@ -206,7 +206,6 @@ func ExitGroup(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintp func clone(t *kernel.Task, flags int, stack hostarch.Addr, parentTID hostarch.Addr, childTID hostarch.Addr, tls hostarch.Addr) (uintptr, *kernel.SyscallControl, error) { args := linux.CloneArgs{ Flags: uint64(uint32(flags) &^ linux.CSIGNAL), - Pidfd: uint64(parentTID), ChildTID: uint64(childTID), ParentTID: uint64(parentTID), ExitSignal: uint64(flags & linux.CSIGNAL), @@ -234,6 +233,29 @@ func Vfork(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, return clone(t, linux.CLONE_VM|linux.CLONE_VFORK|int(linux.SIGCHLD), 0, 0, 0, 0) } +// Clone3 implements linux syscall clone3(2). +func Clone3(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + cloneArgsPointer := args[0].Pointer() + size := args[1].SizeT() + + if int(size) < linux.CLONE_ARGS_SIZE_VER0 || int(size) > linux.CLONE_ARGS_SIZE_VER2 { + return 0, nil, linuxerr.EINVAL + } + + var cloneArgs linux.CloneArgs + if cloneArgsPointer != 0 { + if _, err := cloneArgs.CopyInN(t, cloneArgsPointer, int(size)); err != nil { + return 0, nil, err + } + } + + ntid, ctrl, err := t.Clone(&cloneArgs) + if err != nil { + return 0, nil, err + } + return uintptr(ntid), ctrl, err +} + // parseCommonWaitOptions applies the options common to wait4 and waitid to // wopts. func parseCommonWaitOptions(wopts *kernel.WaitOptions, options int) error { diff --git a/test/syscalls/linux/fork.cc b/test/syscalls/linux/fork.cc index e3180d309..7e8092bbe 100644 --- a/test/syscalls/linux/fork.cc +++ b/test/syscalls/linux/fork.cc @@ -18,12 +18,16 @@ #include #include #include +#include #include +#include #include #include +#include #include +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -459,6 +463,79 @@ TEST(CloneTest, NonCanonicalTLS) { #endif } +#ifndef SYS_clone3 +#define SYS_clone3 435 +#endif // SYS_clone3 + +// struct clone_args is a Linux clone struct. Old versions of glibc do not +// expose it. See include/uapi/linux/sched.h +struct clone_args { + uint64_t flags; + uint64_t pidfd; + uint64_t child_tid; + uint64_t parent_tid; + uint64_t exit_signal; + uint64_t stack; + uint64_t stack_size; + uint64_t tls; + uint64_t set_tid; + uint64_t set_tid_size; + uint64_t cgroup; +}; + +int clone3(struct clone_args* ca, size_t size) { + return syscall(SYS_clone3, ca, size); +} + +// Checks that clone fails for any unsupported flag. +TEST(CloneTest, Clone3UnknownFlag) { + clone_args ca = {}; + ca.flags = (1ULL << 63); + ca.exit_signal = SIGCHLD; + EXPECT_THAT(clone3(&ca, sizeof(ca)), SyscallFailsWithErrno(EINVAL)); +} + +// Clone3 works as Clone. +TEST(CloneTest, Clone3AsClone) { + clone_args ca = {}; + ca.exit_signal = SIGCHLD; + + int child_pid; + EXPECT_THAT(child_pid = clone3(&ca, sizeof(ca)), SyscallSucceeds()); + + if (child_pid == 0) { + exit(0); + } + + int status; + EXPECT_THAT(waitpid(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} + +// Clone3 works with basic Clone3 values for args like exit_signal & parent_tid. +TEST(CloneTest, Clone3Basic) { + clone_args ca = {}; + + ca.flags = CLONE_PARENT_SETTID; + ca.exit_signal = SIGCHLD; + + pid_t store_child_tid = 0; + ca.parent_tid = reinterpret_cast(&store_child_tid); + int child_pid; + EXPECT_THAT(child_pid = clone3(&ca, sizeof(ca)), SyscallSucceeds()); + EXPECT_EQ(store_child_tid, child_pid); + + if (child_pid == 0) { + exit(0); + } + + int status; + EXPECT_THAT(waitpid(child_pid, &status, 0), + SyscallSucceedsWithValue(child_pid)); + EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} + } // namespace } // namespace testing } // namespace gvisor