Index: sandbox/linux/services/credentials_unittest.cc |
diff --git a/sandbox/linux/services/credentials_unittest.cc b/sandbox/linux/services/credentials_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..28199fb30a5294526a622ee4a7bb58a845b4f1cf |
--- /dev/null |
+++ b/sandbox/linux/services/credentials_unittest.cc |
@@ -0,0 +1,236 @@ |
+// 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/files/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. |