// 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 <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include "base/file_util.h" #include "base/files/scoped_file.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "sandbox/linux/tests/unit_tests.h" #include "testing/gtest/include/gtest/gtest.h" namespace sandbox { namespace { bool DirectoryExists(const char* path) { struct stat dir; errno = 0; int ret = stat(path, &dir); return -1 != ret || ENOENT != errno; } 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; } // Give dynamic tools a simple thing to test. TEST(Credentials, CreateAndDestroy) { { Credentials cred1; (void) cred1; } scoped_ptr<Credentials> cred2(new Credentials); } TEST(Credentials, CountOpenFds) { base::ScopedFD proc_fd(open("/proc", O_RDONLY | O_DIRECTORY)); ASSERT_TRUE(proc_fd.is_valid()); Credentials creds; int fd_count = creds.CountOpenFds(proc_fd.get()); int fd = open("/dev/null", O_RDONLY); ASSERT_LE(0, fd); EXPECT_EQ(fd_count + 1, creds.CountOpenFds(proc_fd.get())); ASSERT_EQ(0, IGNORE_EINTR(close(fd))); EXPECT_EQ(fd_count, creds.CountOpenFds(proc_fd.get())); } TEST(Credentials, HasOpenDirectory) { Credentials creds; // No open directory should exist at startup. EXPECT_FALSE(creds.HasOpenDirectory(-1)); { // Have a "/dev" file descriptor around. int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); base::ScopedFD dev_fd_closer(dev_fd); EXPECT_TRUE(creds.HasOpenDirectory(-1)); } EXPECT_FALSE(creds.HasOpenDirectory(-1)); } TEST(Credentials, HasOpenDirectoryWithFD) { Credentials creds; int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); base::ScopedFD proc_fd_closer(proc_fd); ASSERT_LE(0, proc_fd); // Don't pass |proc_fd|, an open directory (proc_fd) should // be detected. EXPECT_TRUE(creds.HasOpenDirectory(-1)); // Pass |proc_fd| and no open directory should be detected. EXPECT_FALSE(creds.HasOpenDirectory(proc_fd)); { // Have a "/dev" file descriptor around. int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); base::ScopedFD dev_fd_closer(dev_fd); EXPECT_TRUE(creds.HasOpenDirectory(proc_fd)); } // The "/dev" file descriptor should now be closed, |proc_fd| is the only // directory file descriptor open. EXPECT_FALSE(creds.HasOpenDirectory(proc_fd)); } SANDBOX_TEST(Credentials, DropAllCaps) { Credentials creds; CHECK(creds.DropAllCapabilities()); CHECK(!creds.HasAnyCapability()); } SANDBOX_TEST(Credentials, GetCurrentCapString) { Credentials creds; CHECK(creds.DropAllCapabilities()); const char kNoCapabilityText[] = "="; CHECK(*creds.GetCurrentCapString() == kNoCapabilityText); } SANDBOX_TEST(Credentials, MoveToNewUserNS) { Credentials creds; creds.DropAllCapabilities(); bool moved_to_new_ns = creds.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(creds.HasAnyCapability()); creds.DropAllCapabilities(); CHECK(!creds.HasAnyCapability()); } SANDBOX_TEST(Credentials, SupportsUserNS) { Credentials creds; creds.DropAllCapabilities(); bool user_ns_supported = Credentials::SupportsNewUserNS(); bool moved_to_new_ns = creds.MoveToNewUserNS(); CHECK_EQ(user_ns_supported, moved_to_new_ns); } SANDBOX_TEST(Credentials, UidIsPreserved) { Credentials creds; creds.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 (!creds.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(Credentials* creds) { DCHECK(creds); if (!creds->MoveToNewUserNS() || !creds->HasAnyCapability() || !creds->DropAllCapabilities() || creds->HasAnyCapability()) { return false; } return true; } SANDBOX_TEST(Credentials, NestedUserNS) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. if (!creds.MoveToNewUserNS()) return; creds.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(&creds)) << "Creating new user NS failed at iteration " << i << "."; } } // Test the WorkingDirectoryIsRoot() helper. TEST(Credentials, CanDetectRoot) { ASSERT_EQ(0, chdir("/proc/")); ASSERT_FALSE(WorkingDirectoryIsRoot()); ASSERT_EQ(0, chdir("/")); ASSERT_TRUE(WorkingDirectoryIsRoot()); } SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(DropFileSystemAccessIsSafe)) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. if (!creds.MoveToNewUserNS()) return; CHECK(creds.DropFileSystemAccess()); CHECK(!DirectoryExists("/proc")); CHECK(WorkingDirectoryIsRoot()); // 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_LSAN(CannotRegainPrivileges)) { Credentials creds; CHECK(creds.DropAllCapabilities()); // Probably missing kernel support. if (!creds.MoveToNewUserNS()) return; CHECK(creds.DropFileSystemAccess()); CHECK(creds.DropAllCapabilities()); // The kernel should now prevent us from regaining capabilities because we // are in a chroot. CHECK(!Credentials::SupportsNewUserNS()); CHECK(!creds.MoveToNewUserNS()); } } // namespace. } // namespace sandbox.