1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "sandbox/linux/services/credentials.h" 6 7 #include <errno.h> 8 #include <fcntl.h> 9 #include <stdio.h> 10 #include <sys/stat.h> 11 #include <sys/types.h> 12 #include <unistd.h> 13 14 #include "base/files/file_util.h" 15 #include "base/files/scoped_file.h" 16 #include "base/logging.h" 17 #include "base/memory/scoped_ptr.h" 18 #include "sandbox/linux/tests/unit_tests.h" 19 #include "testing/gtest/include/gtest/gtest.h" 20 21 namespace sandbox { 22 23 namespace { 24 25 bool DirectoryExists(const char* path) { 26 struct stat dir; 27 errno = 0; 28 int ret = stat(path, &dir); 29 return -1 != ret || ENOENT != errno; 30 } 31 32 bool WorkingDirectoryIsRoot() { 33 char current_dir[PATH_MAX]; 34 char* cwd = getcwd(current_dir, sizeof(current_dir)); 35 PCHECK(cwd); 36 if (strcmp("/", cwd)) return false; 37 38 // The current directory is the root. Add a few paranoid checks. 39 struct stat current; 40 CHECK_EQ(0, stat(".", ¤t)); 41 struct stat parrent; 42 CHECK_EQ(0, stat("..", &parrent)); 43 CHECK_EQ(current.st_dev, parrent.st_dev); 44 CHECK_EQ(current.st_ino, parrent.st_ino); 45 CHECK_EQ(current.st_mode, parrent.st_mode); 46 CHECK_EQ(current.st_uid, parrent.st_uid); 47 CHECK_EQ(current.st_gid, parrent.st_gid); 48 return true; 49 } 50 51 // Give dynamic tools a simple thing to test. 52 TEST(Credentials, CreateAndDestroy) { 53 { 54 Credentials cred1; 55 (void) cred1; 56 } 57 scoped_ptr<Credentials> cred2(new Credentials); 58 } 59 60 TEST(Credentials, CountOpenFds) { 61 base::ScopedFD proc_fd(open("/proc", O_RDONLY | O_DIRECTORY)); 62 ASSERT_TRUE(proc_fd.is_valid()); 63 Credentials creds; 64 int fd_count = creds.CountOpenFds(proc_fd.get()); 65 int fd = open("/dev/null", O_RDONLY); 66 ASSERT_LE(0, fd); 67 EXPECT_EQ(fd_count + 1, creds.CountOpenFds(proc_fd.get())); 68 ASSERT_EQ(0, IGNORE_EINTR(close(fd))); 69 EXPECT_EQ(fd_count, creds.CountOpenFds(proc_fd.get())); 70 } 71 72 TEST(Credentials, HasOpenDirectory) { 73 Credentials creds; 74 // No open directory should exist at startup. 75 EXPECT_FALSE(creds.HasOpenDirectory(-1)); 76 { 77 // Have a "/dev" file descriptor around. 78 int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); 79 base::ScopedFD dev_fd_closer(dev_fd); 80 EXPECT_TRUE(creds.HasOpenDirectory(-1)); 81 } 82 EXPECT_FALSE(creds.HasOpenDirectory(-1)); 83 } 84 85 TEST(Credentials, HasOpenDirectoryWithFD) { 86 Credentials creds; 87 88 int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); 89 base::ScopedFD proc_fd_closer(proc_fd); 90 ASSERT_LE(0, proc_fd); 91 92 // Don't pass |proc_fd|, an open directory (proc_fd) should 93 // be detected. 94 EXPECT_TRUE(creds.HasOpenDirectory(-1)); 95 // Pass |proc_fd| and no open directory should be detected. 96 EXPECT_FALSE(creds.HasOpenDirectory(proc_fd)); 97 98 { 99 // Have a "/dev" file descriptor around. 100 int dev_fd = open("/dev", O_RDONLY | O_DIRECTORY); 101 base::ScopedFD dev_fd_closer(dev_fd); 102 EXPECT_TRUE(creds.HasOpenDirectory(proc_fd)); 103 } 104 105 // The "/dev" file descriptor should now be closed, |proc_fd| is the only 106 // directory file descriptor open. 107 EXPECT_FALSE(creds.HasOpenDirectory(proc_fd)); 108 } 109 110 SANDBOX_TEST(Credentials, DropAllCaps) { 111 Credentials creds; 112 CHECK(creds.DropAllCapabilities()); 113 CHECK(!creds.HasAnyCapability()); 114 } 115 116 SANDBOX_TEST(Credentials, GetCurrentCapString) { 117 Credentials creds; 118 CHECK(creds.DropAllCapabilities()); 119 const char kNoCapabilityText[] = "="; 120 CHECK(*creds.GetCurrentCapString() == kNoCapabilityText); 121 } 122 123 SANDBOX_TEST(Credentials, MoveToNewUserNS) { 124 Credentials creds; 125 creds.DropAllCapabilities(); 126 bool moved_to_new_ns = creds.MoveToNewUserNS(); 127 fprintf(stdout, 128 "Unprivileged CLONE_NEWUSER supported: %s\n", 129 moved_to_new_ns ? "true." : "false."); 130 fflush(stdout); 131 if (!moved_to_new_ns) { 132 fprintf(stdout, "This kernel does not support unprivileged namespaces. " 133 "USERNS tests will succeed without running.\n"); 134 fflush(stdout); 135 return; 136 } 137 CHECK(creds.HasAnyCapability()); 138 creds.DropAllCapabilities(); 139 CHECK(!creds.HasAnyCapability()); 140 } 141 142 SANDBOX_TEST(Credentials, SupportsUserNS) { 143 Credentials creds; 144 creds.DropAllCapabilities(); 145 bool user_ns_supported = Credentials::SupportsNewUserNS(); 146 bool moved_to_new_ns = creds.MoveToNewUserNS(); 147 CHECK_EQ(user_ns_supported, moved_to_new_ns); 148 } 149 150 SANDBOX_TEST(Credentials, UidIsPreserved) { 151 Credentials creds; 152 creds.DropAllCapabilities(); 153 uid_t old_ruid, old_euid, old_suid; 154 gid_t old_rgid, old_egid, old_sgid; 155 PCHECK(0 == getresuid(&old_ruid, &old_euid, &old_suid)); 156 PCHECK(0 == getresgid(&old_rgid, &old_egid, &old_sgid)); 157 // Probably missing kernel support. 158 if (!creds.MoveToNewUserNS()) return; 159 uid_t new_ruid, new_euid, new_suid; 160 PCHECK(0 == getresuid(&new_ruid, &new_euid, &new_suid)); 161 CHECK(old_ruid == new_ruid); 162 CHECK(old_euid == new_euid); 163 CHECK(old_suid == new_suid); 164 165 gid_t new_rgid, new_egid, new_sgid; 166 PCHECK(0 == getresgid(&new_rgid, &new_egid, &new_sgid)); 167 CHECK(old_rgid == new_rgid); 168 CHECK(old_egid == new_egid); 169 CHECK(old_sgid == new_sgid); 170 } 171 172 bool NewUserNSCycle(Credentials* creds) { 173 DCHECK(creds); 174 if (!creds->MoveToNewUserNS() || 175 !creds->HasAnyCapability() || 176 !creds->DropAllCapabilities() || 177 creds->HasAnyCapability()) { 178 return false; 179 } 180 return true; 181 } 182 183 SANDBOX_TEST(Credentials, NestedUserNS) { 184 Credentials creds; 185 CHECK(creds.DropAllCapabilities()); 186 // Probably missing kernel support. 187 if (!creds.MoveToNewUserNS()) return; 188 creds.DropAllCapabilities(); 189 // As of 3.12, the kernel has a limit of 32. See create_user_ns(). 190 const int kNestLevel = 10; 191 for (int i = 0; i < kNestLevel; ++i) { 192 CHECK(NewUserNSCycle(&creds)) << "Creating new user NS failed at iteration " 193 << i << "."; 194 } 195 } 196 197 // Test the WorkingDirectoryIsRoot() helper. 198 TEST(Credentials, CanDetectRoot) { 199 ASSERT_EQ(0, chdir("/proc/")); 200 ASSERT_FALSE(WorkingDirectoryIsRoot()); 201 ASSERT_EQ(0, chdir("/")); 202 ASSERT_TRUE(WorkingDirectoryIsRoot()); 203 } 204 205 SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(DropFileSystemAccessIsSafe)) { 206 Credentials creds; 207 CHECK(creds.DropAllCapabilities()); 208 // Probably missing kernel support. 209 if (!creds.MoveToNewUserNS()) return; 210 CHECK(creds.DropFileSystemAccess()); 211 CHECK(!DirectoryExists("/proc")); 212 CHECK(WorkingDirectoryIsRoot()); 213 // We want the chroot to never have a subdirectory. A subdirectory 214 // could allow a chroot escape. 215 CHECK_NE(0, mkdir("/test", 0700)); 216 } 217 218 // Check that after dropping filesystem access and dropping privileges 219 // it is not possible to regain capabilities. 220 SANDBOX_TEST(Credentials, DISABLE_ON_LSAN(CannotRegainPrivileges)) { 221 Credentials creds; 222 CHECK(creds.DropAllCapabilities()); 223 // Probably missing kernel support. 224 if (!creds.MoveToNewUserNS()) return; 225 CHECK(creds.DropFileSystemAccess()); 226 CHECK(creds.DropAllCapabilities()); 227 228 // The kernel should now prevent us from regaining capabilities because we 229 // are in a chroot. 230 CHECK(!Credentials::SupportsNewUserNS()); 231 CHECK(!creds.MoveToNewUserNS()); 232 } 233 234 } // namespace. 235 236 } // namespace sandbox. 237