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
21namespace sandbox {
22
23namespace {
24
25bool 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
32bool 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(".", &current));
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.
52TEST(Credentials, CreateAndDestroy) {
53  {
54    Credentials cred1;
55    (void) cred1;
56  }
57  scoped_ptr<Credentials> cred2(new Credentials);
58}
59
60TEST(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
72TEST(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
85TEST(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
110SANDBOX_TEST(Credentials, DropAllCaps) {
111  Credentials creds;
112  CHECK(creds.DropAllCapabilities());
113  CHECK(!creds.HasAnyCapability());
114}
115
116SANDBOX_TEST(Credentials, GetCurrentCapString) {
117  Credentials creds;
118  CHECK(creds.DropAllCapabilities());
119  const char kNoCapabilityText[] = "=";
120  CHECK(*creds.GetCurrentCapString() == kNoCapabilityText);
121}
122
123SANDBOX_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
142SANDBOX_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
150SANDBOX_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
172bool 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
183SANDBOX_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.
198TEST(Credentials, CanDetectRoot) {
199  ASSERT_EQ(0, chdir("/proc/"));
200  ASSERT_FALSE(WorkingDirectoryIsRoot());
201  ASSERT_EQ(0, chdir("/"));
202  ASSERT_TRUE(WorkingDirectoryIsRoot());
203}
204
205SANDBOX_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.
220SANDBOX_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