// Copyright (c) 2012 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/credentials.h" #include <errno.h> #include <fcntl.h> #include <limits.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include <sys/capability.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <memory> #include <vector> #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/files/scoped_file.h" #include "base/logging.h" #include "sandbox/linux/services/proc_util.h" #include "sandbox/linux/services/syscall_wrappers.h" #include "sandbox/linux/system_headers/capability.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace sandbox { namespace { struct CapFreeDeleter { inline void operator()(cap_t cap) const { int ret = cap_free(cap); CHECK_EQ(0, ret); } }; // Wrapper to manage libcap2's cap_t type. typedef std::unique_ptr<typeof(*((cap_t)0)), CapFreeDeleter> ScopedCap; bool WorkingDirectoryIsRoot() { char current_dir[PATH_MAX]; char* cwd = getcwd(current_dir, sizeof(current_dir)); PCHECK(cwd); if (strcmp("/", cwd)) return false; // The current directory is the root. Add a few paranoid checks. struct stat current; CHECK_EQ(0, stat(".", ¤t)); struct stat parrent; CHECK_EQ(0, stat("..", &parrent)); CHECK_EQ(current.st_dev, parrent.st_dev); CHECK_EQ(current.st_ino, parrent.st_ino); CHECK_EQ(current.st_mode, parrent.st_mode); CHECK_EQ(current.st_uid, parrent.st_uid); CHECK_EQ(current.st_gid, parrent.st_gid); return true; } SANDBOX_TEST(Credentials, DropAllCaps) { CHECK(Credentials::DropAllCapabilities()); CHECK(!Credentials::HasAnyCapability()); } SANDBOX_TEST(Credentials, MoveToNewUserNS) { CHECK(Credentials::DropAllCapabilities()); bool moved_to_new_ns = Credentials::MoveToNewUserNS(); fprintf(stdout, "Unprivileged CLONE_NEWUSER supported: %s\n", moved_to_new_ns ? "true." : "false."); fflush(stdout); if (!moved_to_new_ns) { fprintf(stdout, "This kernel does not support unprivileged namespaces. " "USERNS tests will succeed without running.\n"); fflush(stdout); return; } CHECK(Credentials::HasAnyCapability()); CHECK(Credentials::DropAllCapabilities()); CHECK(!Credentials::HasAnyCapability()); } SANDBOX_TEST(Credentials, CanCreateProcessInNewUserNS) { CHECK(Credentials::DropAllCapabilities()); bool user_ns_supported = Credentials::CanCreateProcessInNewUserNS(); bool moved_to_new_ns = Credentials::MoveToNewUserNS(); CHECK_EQ(user_ns_supported, moved_to_new_ns); } SANDBOX_TEST(Credentials, UidIsPreserved) { CHECK(Credentials::DropAllCapabilities()); uid_t old_ruid, old_euid, old_suid; gid_t old_rgid, old_egid, old_sgid; PCHECK(0 == getresuid(&old_ruid, &old_euid, &old_suid)); PCHECK(0 == getresgid(&old_rgid, &old_egid, &old_sgid)); // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; uid_t new_ruid, new_euid, new_suid; PCHECK(0 == getresuid(&new_ruid, &new_euid, &new_suid)); CHECK(old_ruid == new_ruid); CHECK(old_euid == new_euid); CHECK(old_suid == new_suid); gid_t new_rgid, new_egid, new_sgid; PCHECK(0 == getresgid(&new_rgid, &new_egid, &new_sgid)); CHECK(old_rgid == new_rgid); CHECK(old_egid == new_egid); CHECK(old_sgid == new_sgid); } bool NewUserNSCycle() { if (!Credentials::MoveToNewUserNS() || !Credentials::HasAnyCapability() || !Credentials::DropAllCapabilities() || Credentials::HasAnyCapability()) { return false; } return true; } SANDBOX_TEST(Credentials, NestedUserNS) { CHECK(Credentials::DropAllCapabilities()); // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; CHECK(Credentials::DropAllCapabilities()); // As of 3.12, the kernel has a limit of 32. See create_user_ns(). const int kNestLevel = 10; for (int i = 0; i < kNestLevel; ++i) { CHECK(NewUserNSCycle()) << "Creating new user NS failed at iteration " << i << "."; } } // Test the WorkingDirectoryIsRoot() helper. SANDBOX_TEST(Credentials, CanDetectRoot) { PCHECK(0 == chdir("/proc/")); CHECK(!WorkingDirectoryIsRoot()); PCHECK(0 == chdir("/")); CHECK(WorkingDirectoryIsRoot()); } // Disabled on ASAN because of crbug.com/451603. SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessIsSafe)) { CHECK(Credentials::DropAllCapabilities()); // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); CHECK(!base::DirectoryExists(base::FilePath("/proc"))); CHECK(WorkingDirectoryIsRoot()); CHECK(base::IsDirectoryEmpty(base::FilePath("/"))); // We want the chroot to never have a subdirectory. A subdirectory // could allow a chroot escape. CHECK_NE(0, mkdir("/test", 0700)); } // Check that after dropping filesystem access and dropping privileges // it is not possible to regain capabilities. SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(CannotRegainPrivileges)) { base::ScopedFD proc_fd(ProcUtil::OpenProc()); CHECK(Credentials::DropAllCapabilities(proc_fd.get())); // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; CHECK(Credentials::DropFileSystemAccess(proc_fd.get())); CHECK(Credentials::DropAllCapabilities(proc_fd.get())); // The kernel should now prevent us from regaining capabilities because we // are in a chroot. CHECK(!Credentials::CanCreateProcessInNewUserNS()); CHECK(!Credentials::MoveToNewUserNS()); } SANDBOX_TEST(Credentials, SetCapabilities) { // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; base::ScopedFD proc_fd(ProcUtil::OpenProc()); CHECK(Credentials::HasCapability(Credentials::Capability::SYS_ADMIN)); CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); std::vector<Credentials::Capability> caps; caps.push_back(Credentials::Capability::SYS_CHROOT); CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); CHECK(!Credentials::HasCapability(Credentials::Capability::SYS_ADMIN)); CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); const std::vector<Credentials::Capability> no_caps; CHECK(Credentials::SetCapabilities(proc_fd.get(), no_caps)); CHECK(!Credentials::HasAnyCapability()); } SANDBOX_TEST(Credentials, SetCapabilitiesAndChroot) { // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; base::ScopedFD proc_fd(ProcUtil::OpenProc()); CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT)); PCHECK(chroot("/") == 0); std::vector<Credentials::Capability> caps; caps.push_back(Credentials::Capability::SYS_CHROOT); CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); PCHECK(chroot("/") == 0); CHECK(Credentials::DropAllCapabilities()); PCHECK(chroot("/") == -1 && errno == EPERM); } SANDBOX_TEST(Credentials, SetCapabilitiesMatchesLibCap2) { // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; base::ScopedFD proc_fd(ProcUtil::OpenProc()); std::vector<Credentials::Capability> caps; caps.push_back(Credentials::Capability::SYS_CHROOT); CHECK(Credentials::SetCapabilities(proc_fd.get(), caps)); ScopedCap actual_cap(cap_get_proc()); PCHECK(actual_cap != nullptr); ScopedCap expected_cap(cap_init()); PCHECK(expected_cap != nullptr); const cap_value_t allowed_cap = CAP_SYS_CHROOT; for (const cap_flag_t flag : {CAP_EFFECTIVE, CAP_PERMITTED}) { PCHECK(cap_set_flag(expected_cap.get(), flag, 1, &allowed_cap, CAP_SET) == 0); } CHECK_EQ(0, cap_compare(expected_cap.get(), actual_cap.get())); } volatile sig_atomic_t signal_handler_called; void SignalHandler(int sig) { signal_handler_called = 1; } // Disabled on ASAN because of crbug.com/451603. SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessPreservesTLS)) { // Probably missing kernel support. if (!Credentials::MoveToNewUserNS()) return; CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get())); // In glibc, pthread_getattr_np makes an assertion about the cached PID/TID in // TLS. pthread_attr_t attr; EXPECT_EQ(0, pthread_getattr_np(pthread_self(), &attr)); // raise also uses the cached TID in glibc. struct sigaction action = {}; action.sa_handler = &SignalHandler; PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0); PCHECK(raise(SIGUSR1) == 0); CHECK_EQ(1, signal_handler_called); } } // namespace. } // namespace sandbox.