// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "sandbox/linux/services/namespace_sandbox.h" #include <signal.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <string> #include <utility> #include "base/command_line.h" #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/process/launch.h" #include "base/process/process.h" #include "base/test/multiprocess_test.h" #include "sandbox/linux/services/credentials.h" #include "sandbox/linux/services/namespace_utils.h" #include "sandbox/linux/services/proc_util.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/multiprocess_func_list.h" namespace sandbox { namespace { bool RootDirectoryIsEmpty() { base::FilePath root("/"); int file_type = base::FileEnumerator::DIRECTORIES | base::FileEnumerator::FILES; base::FileEnumerator enumerator_before(root, false, file_type); return enumerator_before.Next().empty(); } class NamespaceSandboxTest : public base::MultiProcessTest { public: void TestProc(const std::string& procname) { TestProcWithOptions(procname, NamespaceSandbox::Options()); } void TestProcWithOptions( const std::string& procname, const NamespaceSandbox::Options& ns_sandbox_options) { if (!Credentials::CanCreateProcessInNewUserNS()) { return; } base::FileHandleMappingVector fds_to_remap = { std::make_pair(STDOUT_FILENO, STDOUT_FILENO), std::make_pair(STDERR_FILENO, STDERR_FILENO), }; base::LaunchOptions launch_options; launch_options.fds_to_remap = &fds_to_remap; base::Process process = NamespaceSandbox::LaunchProcessWithOptions( MakeCmdLine(procname), launch_options, ns_sandbox_options); ASSERT_TRUE(process.IsValid()); const int kDummyExitCode = 42; int exit_code = kDummyExitCode; EXPECT_TRUE(process.WaitForExit(&exit_code)); EXPECT_EQ(0, exit_code); } }; MULTIPROCESS_TEST_MAIN(SimpleChildProcess) { const bool in_user_ns = NamespaceSandbox::InNewUserNamespace(); const bool in_pid_ns = NamespaceSandbox::InNewPidNamespace(); const bool in_net_ns = NamespaceSandbox::InNewNetNamespace(); CHECK(in_user_ns); CHECK_EQ(in_pid_ns, NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWPID)); CHECK_EQ(in_net_ns, NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWNET)); if (in_pid_ns) { CHECK_EQ(1, getpid()); } return 0; } TEST_F(NamespaceSandboxTest, BasicUsage) { TestProc("SimpleChildProcess"); } MULTIPROCESS_TEST_MAIN(PidNsOnlyChildProcess) { const bool in_user_ns = NamespaceSandbox::InNewUserNamespace(); const bool in_pid_ns = NamespaceSandbox::InNewPidNamespace(); const bool in_net_ns = NamespaceSandbox::InNewNetNamespace(); CHECK(in_user_ns); CHECK_EQ(in_pid_ns, NamespaceUtils::KernelSupportsUnprivilegedNamespace(CLONE_NEWPID)); CHECK(!in_net_ns); if (in_pid_ns) { CHECK_EQ(1, getpid()); } return 0; } TEST_F(NamespaceSandboxTest, BasicUsageWithOptions) { NamespaceSandbox::Options options; options.ns_types = CLONE_NEWUSER | CLONE_NEWPID; TestProcWithOptions("PidNsOnlyChildProcess", options); } MULTIPROCESS_TEST_MAIN(ChrootMe) { CHECK(!RootDirectoryIsEmpty()); CHECK(sandbox::Credentials::MoveToNewUserNS()); CHECK(sandbox::Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); CHECK(RootDirectoryIsEmpty()); return 0; } // Temporarily disabled on ASAN due to crbug.com/451603. TEST_F(NamespaceSandboxTest, DISABLE_ON_ASAN(ChrootAndDropCapabilities)) { TestProc("ChrootMe"); } MULTIPROCESS_TEST_MAIN(NestedNamespaceSandbox) { base::FileHandleMappingVector fds_to_remap = { std::make_pair(STDOUT_FILENO, STDOUT_FILENO), std::make_pair(STDERR_FILENO, STDERR_FILENO), }; base::LaunchOptions launch_options; launch_options.fds_to_remap = &fds_to_remap; base::Process process = NamespaceSandbox::LaunchProcess( base::CommandLine(base::FilePath("/bin/true")), launch_options); CHECK(process.IsValid()); const int kDummyExitCode = 42; int exit_code = kDummyExitCode; CHECK(process.WaitForExit(&exit_code)); CHECK_EQ(0, exit_code); return 0; } TEST_F(NamespaceSandboxTest, NestedNamespaceSandbox) { TestProc("NestedNamespaceSandbox"); } const int kNormalExitCode = 0; // Ensure that CHECK(false) is distinguishable from _exit(kNormalExitCode). // Allowing noise since CHECK(false) will write a stack trace to stderr. SANDBOX_TEST_ALLOW_NOISE(ForkInNewPidNamespace, CheckDoesNotReturnZero) { if (!Credentials::CanCreateProcessInNewUserNS()) { return; } CHECK(sandbox::Credentials::MoveToNewUserNS()); const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( /*drop_capabilities_in_child=*/true); CHECK_GE(pid, 0); if (pid == 0) { CHECK(false); _exit(kNormalExitCode); } int status; PCHECK(waitpid(pid, &status, 0) == pid); if (WIFEXITED(status)) { CHECK_NE(kNormalExitCode, WEXITSTATUS(status)); } } SANDBOX_TEST(ForkInNewPidNamespace, BasicUsage) { if (!Credentials::CanCreateProcessInNewUserNS()) { return; } CHECK(sandbox::Credentials::MoveToNewUserNS()); const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( /*drop_capabilities_in_child=*/true); CHECK_GE(pid, 0); if (pid == 0) { CHECK_EQ(1, getpid()); CHECK(!Credentials::HasAnyCapability()); _exit(kNormalExitCode); } int status; PCHECK(waitpid(pid, &status, 0) == pid); CHECK(WIFEXITED(status)); CHECK_EQ(kNormalExitCode, WEXITSTATUS(status)); } SANDBOX_TEST(ForkInNewPidNamespace, ExitWithSignal) { if (!Credentials::CanCreateProcessInNewUserNS()) { return; } CHECK(sandbox::Credentials::MoveToNewUserNS()); const pid_t pid = NamespaceSandbox::ForkInNewPidNamespace( /*drop_capabilities_in_child=*/true); CHECK_GE(pid, 0); if (pid == 0) { CHECK_EQ(1, getpid()); CHECK(!Credentials::HasAnyCapability()); CHECK(NamespaceSandbox::InstallTerminationSignalHandler( SIGTERM, NamespaceSandbox::SignalExitCode(SIGTERM))); while (true) { raise(SIGTERM); } } int status; PCHECK(waitpid(pid, &status, 0) == pid); CHECK(WIFEXITED(status)); CHECK_EQ(NamespaceSandbox::SignalExitCode(SIGTERM), WEXITSTATUS(status)); } volatile sig_atomic_t signal_handler_called; void ExitSuccessfully(int sig) { signal_handler_called = 1; } SANDBOX_TEST(InstallTerminationSignalHandler, DoesNotOverrideExistingHandlers) { struct sigaction action = {}; action.sa_handler = &ExitSuccessfully; PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0); NamespaceSandbox::InstallDefaultTerminationSignalHandlers(); CHECK(!NamespaceSandbox::InstallTerminationSignalHandler( SIGUSR1, NamespaceSandbox::SignalExitCode(SIGUSR1))); raise(SIGUSR1); CHECK_EQ(1, signal_handler_called); } } // namespace } // namespace sandbox