From 0b569b7caebc2b7785732ed6cb8248cf0568783f Mon Sep 17 00:00:00 2001 From: Dean Deng Date: Mon, 21 Oct 2019 14:53:44 -0700 Subject: [PATCH] Add basic implementation of execveat syscall and associated tests. Allow file descriptors of directories as well as AT_FDCWD. PiperOrigin-RevId: 275929668 --- pkg/sentry/syscalls/linux/linux64_amd64.go | 2 +- pkg/sentry/syscalls/linux/sys_thread.go | 48 ++++- test/syscalls/linux/BUILD | 1 + test/syscalls/linux/exec.cc | 201 +++++++++++++-------- test/util/multiprocess_util.cc | 46 ++++- test/util/multiprocess_util.h | 7 + 6 files changed, 220 insertions(+), 85 deletions(-) diff --git a/pkg/sentry/syscalls/linux/linux64_amd64.go b/pkg/sentry/syscalls/linux/linux64_amd64.go index e215ac049..9c9ab438e 100644 --- a/pkg/sentry/syscalls/linux/linux64_amd64.go +++ b/pkg/sentry/syscalls/linux/linux64_amd64.go @@ -362,7 +362,7 @@ var AMD64 = &kernel.SyscallTable{ 319: syscalls.Supported("memfd_create", MemfdCreate), 320: syscalls.CapError("kexec_file_load", linux.CAP_SYS_BOOT, "", nil), 321: syscalls.CapError("bpf", linux.CAP_SYS_ADMIN, "", nil), - 322: syscalls.ErrorWithEvent("execveat", syserror.ENOSYS, "", []string{"gvisor.dev/issue/265"}), // TODO(b/118901836) + 322: syscalls.PartiallySupported("execveat", Execveat, "No support for AT_EMPTY_PATH, AT_SYMLINK_FOLLOW.", nil), 323: syscalls.ErrorWithEvent("userfaultfd", syserror.ENOSYS, "", []string{"gvisor.dev/issue/266"}), // TODO(b/118906345) 324: syscalls.ErrorWithEvent("membarrier", syserror.ENOSYS, "", []string{"gvisor.dev/issue/267"}), // TODO(b/118904897) 325: syscalls.PartiallySupported("mlock2", Mlock2, "Stub implementation. The sandbox lacks appropriate permissions.", nil), diff --git a/pkg/sentry/syscalls/linux/sys_thread.go b/pkg/sentry/syscalls/linux/sys_thread.go index 8ab7ffa25..6e425f1ec 100644 --- a/pkg/sentry/syscalls/linux/sys_thread.go +++ b/pkg/sentry/syscalls/linux/sys_thread.go @@ -15,10 +15,12 @@ package linux import ( + "path" "syscall" "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/sentry/arch" + "gvisor.dev/gvisor/pkg/sentry/fs" "gvisor.dev/gvisor/pkg/sentry/kernel" "gvisor.dev/gvisor/pkg/sentry/kernel/sched" "gvisor.dev/gvisor/pkg/sentry/usermem" @@ -67,8 +69,22 @@ func Execve(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal argvAddr := args[1].Pointer() envvAddr := args[2].Pointer() - // Extract our arguments. - filename, err := t.CopyInString(filenameAddr, linux.PATH_MAX) + return execveat(t, linux.AT_FDCWD, filenameAddr, argvAddr, envvAddr, 0) +} + +// Execveat implements linux syscall execveat(2). +func Execveat(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) { + dirFD := args[0].Int() + pathnameAddr := args[1].Pointer() + argvAddr := args[2].Pointer() + envvAddr := args[3].Pointer() + flags := args[4].Int() + + return execveat(t, dirFD, pathnameAddr, argvAddr, envvAddr, flags) +} + +func execveat(t *kernel.Task, dirFD int32, pathnameAddr, argvAddr, envvAddr usermem.Addr, flags int32) (uintptr, *kernel.SyscallControl, error) { + pathname, err := t.CopyInString(pathnameAddr, linux.PATH_MAX) if err != nil { return 0, nil, err } @@ -89,14 +105,38 @@ func Execve(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.Syscal } } + if flags != 0 { + // TODO(b/128449944): Handle AT_EMPTY_PATH and AT_SYMLINK_NOFOLLOW. + t.Kernel().EmitUnimplementedEvent(t) + return 0, nil, syserror.ENOSYS + } + root := t.FSContext().RootDirectory() defer root.DecRef() - wd := t.FSContext().WorkingDirectory() + + var wd *fs.Dirent + if dirFD == linux.AT_FDCWD || path.IsAbs(pathname) { + // If pathname is absolute, LoadTaskImage() will ignore the wd. + wd = t.FSContext().WorkingDirectory() + } else { + // Need to extract the given FD. + f := t.GetFile(dirFD) + if f == nil { + return 0, nil, syserror.EBADF + } + defer f.DecRef() + + wd = f.Dirent + wd.IncRef() + if !fs.IsDir(wd.Inode.StableAttr) { + return 0, nil, syserror.ENOTDIR + } + } defer wd.DecRef() // Load the new TaskContext. maxTraversals := uint(linux.MaxSymlinkTraversals) - tc, se := t.Kernel().LoadTaskImage(t, t.MountNamespace(), root, wd, &maxTraversals, filename, nil, argv, envv, t.Arch().FeatureSet()) + tc, se := t.Kernel().LoadTaskImage(t, t.MountNamespace(), root, wd, &maxTraversals, pathname, nil, argv, envv, t.Arch().FeatureSet()) if se != nil { return 0, nil, se.ToError() } diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index cf4c63b40..b869ca6f9 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -670,6 +670,7 @@ cc_binary( "//test/util:thread_util", "@com_google_absl//absl/strings", "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/types:optional", "@com_google_googletest//:gtest", ], ) diff --git a/test/syscalls/linux/exec.cc b/test/syscalls/linux/exec.cc index 4947271ba..85734c290 100644 --- a/test/syscalls/linux/exec.cc +++ b/test/syscalls/linux/exec.cc @@ -33,6 +33,7 @@ #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/multiprocess_util.h" @@ -68,11 +69,12 @@ constexpr char kExit42[] = "--exec_exit_42"; constexpr char kExecWithThread[] = "--exec_exec_with_thread"; constexpr char kExecFromThread[] = "--exec_exec_from_thread"; -// Runs filename with argv and checks that the exit status is expect_status and -// that stderr contains expect_stderr. -void CheckOutput(const std::string& filename, const ExecveArray& argv, - const ExecveArray& envv, int expect_status, - const std::string& expect_stderr) { +// Runs file specified by dirfd and pathname with argv and checks that the exit +// status is expect_status and that stderr contains expect_stderr. +void CheckExecHelper(const absl::optional dirfd, + const std::string& pathname, const ExecveArray& argv, + const ExecveArray& envv, const int flags, + int expect_status, const std::string& expect_stderr) { int pipe_fds[2]; ASSERT_THAT(pipe2(pipe_fds, O_CLOEXEC), SyscallSucceeds()); @@ -110,8 +112,15 @@ void CheckOutput(const std::string& filename, const ExecveArray& argv, // CloexecEventfd depend on that not happening. }; - auto kill = ASSERT_NO_ERRNO_AND_VALUE( - ForkAndExec(filename, argv, envv, remap_stderr, &child, &execve_errno)); + Cleanup kill; + if (dirfd.has_value()) { + kill = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExecveat(*dirfd, pathname, argv, + envv, flags, remap_stderr, + &child, &execve_errno)); + } else { + kill = ASSERT_NO_ERRNO_AND_VALUE( + ForkAndExec(pathname, argv, envv, remap_stderr, &child, &execve_errno)); + } ASSERT_EQ(0, execve_errno); @@ -140,6 +149,21 @@ void CheckOutput(const std::string& filename, const ExecveArray& argv, EXPECT_TRUE(absl::StrContains(output, expect_stderr)) << output; } +void CheckExec(const std::string& filename, const ExecveArray& argv, + const ExecveArray& envv, int expect_status, + const std::string& expect_stderr) { + CheckExecHelper(/*dirfd=*/absl::optional(), filename, argv, envv, + /*flags=*/0, expect_status, expect_stderr); +} + +void CheckExecveat(const int32_t dirfd, const std::string& pathname, + const ExecveArray& argv, const ExecveArray& envv, + const int flags, int expect_status, + const std::string& expect_stderr) { + CheckExecHelper(absl::optional(dirfd), pathname, argv, envv, flags, + expect_status, expect_stderr); +} + TEST(ExecTest, EmptyPath) { int execve_errno; ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec("", {}, {}, nullptr, &execve_errno)); @@ -147,46 +171,45 @@ TEST(ExecTest, EmptyPath) { } TEST(ExecTest, Basic) { - CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, {}, - ArgEnvExitStatus(0, 0), - absl::StrCat(WorkloadPath(kBasicWorkload), "\n")); + CheckExec(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, {}, + ArgEnvExitStatus(0, 0), + absl::StrCat(WorkloadPath(kBasicWorkload), "\n")); } TEST(ExecTest, OneArg) { - CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload), "1"}, - {}, ArgEnvExitStatus(1, 0), - absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n")); + CheckExec(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload), "1"}, + {}, ArgEnvExitStatus(1, 0), + absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n")); } TEST(ExecTest, FiveArg) { - CheckOutput(WorkloadPath(kBasicWorkload), - {WorkloadPath(kBasicWorkload), "1", "2", "3", "4", "5"}, {}, - ArgEnvExitStatus(5, 0), - absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n2\n3\n4\n5\n")); + CheckExec(WorkloadPath(kBasicWorkload), + {WorkloadPath(kBasicWorkload), "1", "2", "3", "4", "5"}, {}, + ArgEnvExitStatus(5, 0), + absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n2\n3\n4\n5\n")); } TEST(ExecTest, OneEnv) { - CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, - {"1"}, ArgEnvExitStatus(0, 1), - absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n")); + CheckExec(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, {"1"}, + ArgEnvExitStatus(0, 1), + absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n")); } TEST(ExecTest, FiveEnv) { - CheckOutput(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, - {"1", "2", "3", "4", "5"}, ArgEnvExitStatus(0, 5), - absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n2\n3\n4\n5\n")); + CheckExec(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload)}, + {"1", "2", "3", "4", "5"}, ArgEnvExitStatus(0, 5), + absl::StrCat(WorkloadPath(kBasicWorkload), "\n1\n2\n3\n4\n5\n")); } TEST(ExecTest, OneArgOneEnv) { - CheckOutput(WorkloadPath(kBasicWorkload), - {WorkloadPath(kBasicWorkload), "arg"}, {"env"}, - ArgEnvExitStatus(1, 1), - absl::StrCat(WorkloadPath(kBasicWorkload), "\narg\nenv\n")); + CheckExec(WorkloadPath(kBasicWorkload), {WorkloadPath(kBasicWorkload), "arg"}, + {"env"}, ArgEnvExitStatus(1, 1), + absl::StrCat(WorkloadPath(kBasicWorkload), "\narg\nenv\n")); } TEST(ExecTest, InterpreterScript) { - CheckOutput(WorkloadPath(kExitScript), {WorkloadPath(kExitScript), "25"}, {}, - ArgEnvExitStatus(25, 0), ""); + CheckExec(WorkloadPath(kExitScript), {WorkloadPath(kExitScript), "25"}, {}, + ArgEnvExitStatus(25, 0), ""); } // Everything after the path in the interpreter script is a single argument. @@ -199,8 +222,8 @@ TEST(ExecTest, InterpreterScriptArgSplit) { GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " foo bar"), 0755)); - CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), - absl::StrCat(link.path(), "\nfoo bar\n", script.path(), "\n")); + CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), + absl::StrCat(link.path(), "\nfoo bar\n", script.path(), "\n")); } // Original argv[0] is replaced with the script path. @@ -212,8 +235,8 @@ TEST(ExecTest, InterpreterScriptArgvZero) { TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755)); - CheckOutput(script.path(), {"REPLACED"}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script.path(), "\n")); + CheckExec(script.path(), {"REPLACED"}, {}, ArgEnvExitStatus(1, 0), + absl::StrCat(link.path(), "\n", script.path(), "\n")); } // Original argv[0] is replaced with the script path, exactly as passed to @@ -230,8 +253,8 @@ TEST(ExecTest, InterpreterScriptArgvZeroRelative) { auto script_relative = ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, script.path())); - CheckOutput(script_relative, {"REPLACED"}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script_relative, "\n")); + CheckExec(script_relative, {"REPLACED"}, {}, ArgEnvExitStatus(1, 0), + absl::StrCat(link.path(), "\n", script_relative, "\n")); } // argv[0] is added as the script path, even if there was none. @@ -243,8 +266,8 @@ TEST(ExecTest, InterpreterScriptArgvZeroAdded) { TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path()), 0755)); - CheckOutput(script.path(), {}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script.path(), "\n")); + CheckExec(script.path(), {}, {}, ArgEnvExitStatus(1, 0), + absl::StrCat(link.path(), "\n", script.path(), "\n")); } // A NUL byte in the script line ends parsing. @@ -258,8 +281,8 @@ TEST(ExecTest, InterpreterScriptArgNUL) { absl::StrCat("#!", link.path(), " foo", std::string(1, '\0'), "bar"), 0755)); - CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), - absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n")); + CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), + absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n")); } // Trailing whitespace following interpreter path is ignored. @@ -271,8 +294,8 @@ TEST(ExecTest, InterpreterScriptTrailingWhitespace) { TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " "), 0755)); - CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(1, 0), - absl::StrCat(link.path(), "\n", script.path(), "\n")); + CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(1, 0), + absl::StrCat(link.path(), "\n", script.path(), "\n")); } // Multiple whitespace characters between interpreter and arg allowed. @@ -284,8 +307,8 @@ TEST(ExecTest, InterpreterScriptArgWhitespace) { TempPath script = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( GetAbsoluteTestTmpdir(), absl::StrCat("#!", link.path(), " foo"), 0755)); - CheckOutput(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), - absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n")); + CheckExec(script.path(), {script.path()}, {}, ArgEnvExitStatus(2, 0), + absl::StrCat(link.path(), "\nfoo\n", script.path(), "\n")); } TEST(ExecTest, InterpreterScriptNoPath) { @@ -314,15 +337,15 @@ TEST(ExecTest, ExecFn) { auto script_relative = ASSERT_NO_ERRNO_AND_VALUE(GetRelativePath(cwd, script.path())); - CheckOutput(script_relative, {script_relative}, {}, ArgEnvExitStatus(0, 0), - absl::StrCat(script_relative, "\n")); + CheckExec(script_relative, {script_relative}, {}, ArgEnvExitStatus(0, 0), + absl::StrCat(script_relative, "\n")); } TEST(ExecTest, ExecName) { std::string path = WorkloadPath(kStateWorkload); - CheckOutput(path, {path, "PrintExecName"}, {}, ArgEnvExitStatus(0, 0), - absl::StrCat(Basename(path).substr(0, 15), "\n")); + CheckExec(path, {path, "PrintExecName"}, {}, ArgEnvExitStatus(0, 0), + absl::StrCat(Basename(path).substr(0, 15), "\n")); } TEST(ExecTest, ExecNameScript) { @@ -336,21 +359,21 @@ TEST(ExecTest, ExecNameScript) { std::string script_path = script.path(); - CheckOutput(script_path, {script_path}, {}, ArgEnvExitStatus(0, 0), - absl::StrCat(Basename(script_path).substr(0, 15), "\n")); + CheckExec(script_path, {script_path}, {}, ArgEnvExitStatus(0, 0), + absl::StrCat(Basename(script_path).substr(0, 15), "\n")); } // execve may be called by a multithreaded process. TEST(ExecTest, WithSiblingThread) { - CheckOutput("/proc/self/exe", {"/proc/self/exe", kExecWithThread}, {}, - W_EXITCODE(42, 0), ""); + CheckExec("/proc/self/exe", {"/proc/self/exe", kExecWithThread}, {}, + W_EXITCODE(42, 0), ""); } // execve may be called from a thread other than the leader of a multithreaded // process. TEST(ExecTest, FromSiblingThread) { - CheckOutput("/proc/self/exe", {"/proc/self/exe", kExecFromThread}, {}, - W_EXITCODE(42, 0), ""); + CheckExec("/proc/self/exe", {"/proc/self/exe", kExecFromThread}, {}, + W_EXITCODE(42, 0), ""); } TEST(ExecTest, NotFound) { @@ -388,7 +411,7 @@ TEST(ExecStateTest, HandlerReset) { absl::StrCat(absl::Hex(reinterpret_cast(SIG_DFL))), }; - CheckOutput(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); + CheckExec(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); } // Ignored signal dispositions are not reset. @@ -404,7 +427,7 @@ TEST(ExecStateTest, IgnorePreserved) { absl::StrCat(absl::Hex(reinterpret_cast(SIG_IGN))), }; - CheckOutput(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); + CheckExec(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); } // Signal masks are not reset on exec @@ -420,7 +443,7 @@ TEST(ExecStateTest, SignalMask) { absl::StrCat(SIGUSR1), }; - CheckOutput(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); + CheckExec(WorkloadPath(kStateWorkload), args, {}, W_EXITCODE(0, 0), ""); } // itimers persist across execve. @@ -472,10 +495,10 @@ TEST(ExecStateTest, ItimerPreserved) { TEST(ProcSelfExe, ChangesAcrossExecve) { // See exec_proc_exe_workload for more details. We simply // assert that the /proc/self/exe link changes across execve. - CheckOutput(WorkloadPath(kProcExeWorkload), - {WorkloadPath(kProcExeWorkload), - ASSERT_NO_ERRNO_AND_VALUE(ProcessExePath(getpid()))}, - {}, W_EXITCODE(0, 0), ""); + CheckExec(WorkloadPath(kProcExeWorkload), + {WorkloadPath(kProcExeWorkload), + ASSERT_NO_ERRNO_AND_VALUE(ProcessExePath(getpid()))}, + {}, W_EXITCODE(0, 0), ""); } TEST(ExecTest, CloexecNormalFile) { @@ -484,20 +507,20 @@ TEST(ExecTest, CloexecNormalFile) { const FileDescriptor fd_closed_on_exec = ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY | O_CLOEXEC)); - CheckOutput(WorkloadPath(kAssertClosedWorkload), - {WorkloadPath(kAssertClosedWorkload), - absl::StrCat(fd_closed_on_exec.get())}, - {}, W_EXITCODE(0, 0), ""); + CheckExec(WorkloadPath(kAssertClosedWorkload), + {WorkloadPath(kAssertClosedWorkload), + absl::StrCat(fd_closed_on_exec.get())}, + {}, W_EXITCODE(0, 0), ""); // The assert closed workload exits with code 2 if the file still exists. We // can use this to do a negative test. const FileDescriptor fd_open_on_exec = ASSERT_NO_ERRNO_AND_VALUE(Open(tempFile.path(), O_RDONLY)); - CheckOutput(WorkloadPath(kAssertClosedWorkload), - {WorkloadPath(kAssertClosedWorkload), - absl::StrCat(fd_open_on_exec.get())}, - {}, W_EXITCODE(2, 0), ""); + CheckExec(WorkloadPath(kAssertClosedWorkload), + {WorkloadPath(kAssertClosedWorkload), + absl::StrCat(fd_open_on_exec.get())}, + {}, W_EXITCODE(2, 0), ""); } TEST(ExecTest, CloexecEventfd) { @@ -505,9 +528,40 @@ TEST(ExecTest, CloexecEventfd) { ASSERT_THAT(efd = eventfd(0, EFD_CLOEXEC), SyscallSucceeds()); FileDescriptor fd(efd); - CheckOutput(WorkloadPath(kAssertClosedWorkload), - {WorkloadPath(kAssertClosedWorkload), absl::StrCat(fd.get())}, {}, - W_EXITCODE(0, 0), ""); + CheckExec(WorkloadPath(kAssertClosedWorkload), + {WorkloadPath(kAssertClosedWorkload), absl::StrCat(fd.get())}, {}, + W_EXITCODE(0, 0), ""); +} + +TEST(ExecveatTest, BasicWithFDCWD) { + std::string path = WorkloadPath(kBasicWorkload); + CheckExecveat(AT_FDCWD, path, {path}, {}, /*flags=*/0, ArgEnvExitStatus(0, 0), + absl::StrCat(path, "\n")); +} + +TEST(ExecveatTest, Basic) { + std::string absolute_path = WorkloadPath(kBasicWorkload); + std::string parent_dir = std::string(Dirname(absolute_path)); + std::string relative_path = std::string(Basename(absolute_path)); + const FileDescriptor dirfd = + ASSERT_NO_ERRNO_AND_VALUE(Open(parent_dir, O_DIRECTORY)); + + CheckExecveat(dirfd.get(), relative_path, {absolute_path}, {}, /*flags=*/0, + ArgEnvExitStatus(0, 0), absl::StrCat(absolute_path, "\n")); +} + +TEST(ExecveatTest, AbsolutePathWithFDCWD) { + std::string path = WorkloadPath(kBasicWorkload); + CheckExecveat(AT_FDCWD, path, {path}, {}, ArgEnvExitStatus(0, 0), 0, + absl::StrCat(path, "\n")); +} + +TEST(ExecveatTest, AbsolutePath) { + std::string path = WorkloadPath(kBasicWorkload); + // File descriptor should be ignored when an absolute path is given. + const int32_t badFD = -1; + CheckExecveat(badFD, path, {path}, {}, ArgEnvExitStatus(0, 0), 0, + absl::StrCat(path, "\n")); } // Priority consistent across calls to execve() @@ -522,9 +576,8 @@ TEST(GetpriorityTest, ExecveMaintainsPriority) { // Program run (priority_execve) will exit(X) where // X=getpriority(PRIO_PROCESS,0). Check that this exit value is prio. - CheckOutput(WorkloadPath(kPriorityWorkload), - {WorkloadPath(kPriorityWorkload)}, {}, - W_EXITCODE(expected_exit_code, 0), ""); + CheckExec(WorkloadPath(kPriorityWorkload), {WorkloadPath(kPriorityWorkload)}, + {}, W_EXITCODE(expected_exit_code, 0), ""); } void ExecWithThread() { diff --git a/test/util/multiprocess_util.cc b/test/util/multiprocess_util.cc index 95f5f3b4f..8b676751b 100644 --- a/test/util/multiprocess_util.cc +++ b/test/util/multiprocess_util.cc @@ -14,6 +14,7 @@ #include "test/util/multiprocess_util.h" +#include #include #include #include @@ -30,11 +31,12 @@ namespace gvisor { namespace testing { -PosixErrorOr ForkAndExec(const std::string& filename, - const ExecveArray& argv, - const ExecveArray& envv, - const std::function& fn, pid_t* child, - int* execve_errno) { +namespace { + +// exec_fn wraps a variant of the exec family, e.g. execve or execveat. +PosixErrorOr ForkAndExecHelper(const std::function& exec_fn, + const std::function& fn, + pid_t* child, int* execve_errno) { int pfds[2]; int ret = pipe2(pfds, O_CLOEXEC); if (ret < 0) { @@ -76,7 +78,9 @@ PosixErrorOr ForkAndExec(const std::string& filename, fn(); } - execve(filename.c_str(), argv.get(), envv.get()); + // Call variant of exec function. + exec_fn(); + int error = errno; if (WriteFd(pfds[1], &error, sizeof(error)) != sizeof(error)) { // We can't do much if the write fails, but we can at least exit with a @@ -116,6 +120,36 @@ PosixErrorOr ForkAndExec(const std::string& filename, return std::move(cleanup); } +} // namespace + +PosixErrorOr ForkAndExec(const std::string& filename, + const ExecveArray& argv, + const ExecveArray& envv, + const std::function& fn, pid_t* child, + int* execve_errno) { + char* const* argv_data = argv.get(); + char* const* envv_data = envv.get(); + const std::function exec_fn = [=] { + execve(filename.c_str(), argv_data, envv_data); + }; + return ForkAndExecHelper(exec_fn, fn, child, execve_errno); +} + +PosixErrorOr ForkAndExecveat(const int32_t dirfd, + const std::string& pathname, + const ExecveArray& argv, + const ExecveArray& envv, const int flags, + const std::function& fn, + pid_t* child, int* execve_errno) { + char* const* argv_data = argv.get(); + char* const* envv_data = envv.get(); + const std::function exec_fn = [=] { + syscall(__NR_execveat, dirfd, pathname.c_str(), argv_data, envv_data, + flags); + }; + return ForkAndExecHelper(exec_fn, fn, child, execve_errno); +} + PosixErrorOr InForkedProcess(const std::function& fn) { pid_t pid = fork(); if (pid == 0) { diff --git a/test/util/multiprocess_util.h b/test/util/multiprocess_util.h index 0aecd3439..c413d63ea 100644 --- a/test/util/multiprocess_util.h +++ b/test/util/multiprocess_util.h @@ -102,6 +102,13 @@ inline PosixErrorOr ForkAndExec(const std::string& filename, return ForkAndExec(filename, argv, envv, [] {}, child, execve_errno); } +// Equivalent to ForkAndExec, except using dirfd and flags with execveat. +PosixErrorOr ForkAndExecveat(int32_t dirfd, const std::string& pathname, + const ExecveArray& argv, + const ExecveArray& envv, int flags, + const std::function& fn, + pid_t* child, int* execve_errno); + // Calls fn in a forked subprocess and returns the exit status of the // subprocess. //