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 <limits.h>
10 #include <pthread.h>
11 #include <signal.h>
12 #include <stdio.h>
13 #include <sys/capability.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <unistd.h>
17
18 #include <memory>
19 #include <vector>
20
21 #include "base/files/file_path.h"
22 #include "base/files/file_util.h"
23 #include "base/files/scoped_file.h"
24 #include "base/logging.h"
25 #include "sandbox/linux/services/proc_util.h"
26 #include "sandbox/linux/services/syscall_wrappers.h"
27 #include "sandbox/linux/system_headers/capability.h"
28 #include "sandbox/linux/tests/unit_tests.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30
31 namespace sandbox {
32
33 namespace {
34
35 struct CapFreeDeleter {
operator ()sandbox::__anon1ca9f0860111::CapFreeDeleter36 inline void operator()(cap_t cap) const {
37 int ret = cap_free(cap);
38 CHECK_EQ(0, ret);
39 }
40 };
41
42 // Wrapper to manage libcap2's cap_t type.
43 typedef std::unique_ptr<typeof(*((cap_t)0)), CapFreeDeleter> ScopedCap;
44
WorkingDirectoryIsRoot()45 bool WorkingDirectoryIsRoot() {
46 char current_dir[PATH_MAX];
47 char* cwd = getcwd(current_dir, sizeof(current_dir));
48 PCHECK(cwd);
49 if (strcmp("/", cwd)) return false;
50
51 // The current directory is the root. Add a few paranoid checks.
52 struct stat current;
53 CHECK_EQ(0, stat(".", ¤t));
54 struct stat parrent;
55 CHECK_EQ(0, stat("..", &parrent));
56 CHECK_EQ(current.st_dev, parrent.st_dev);
57 CHECK_EQ(current.st_ino, parrent.st_ino);
58 CHECK_EQ(current.st_mode, parrent.st_mode);
59 CHECK_EQ(current.st_uid, parrent.st_uid);
60 CHECK_EQ(current.st_gid, parrent.st_gid);
61 return true;
62 }
63
SANDBOX_TEST(Credentials,DropAllCaps)64 SANDBOX_TEST(Credentials, DropAllCaps) {
65 CHECK(Credentials::DropAllCapabilities());
66 CHECK(!Credentials::HasAnyCapability());
67 }
68
SANDBOX_TEST(Credentials,MoveToNewUserNS)69 SANDBOX_TEST(Credentials, MoveToNewUserNS) {
70 CHECK(Credentials::DropAllCapabilities());
71 bool moved_to_new_ns = Credentials::MoveToNewUserNS();
72 fprintf(stdout,
73 "Unprivileged CLONE_NEWUSER supported: %s\n",
74 moved_to_new_ns ? "true." : "false.");
75 fflush(stdout);
76 if (!moved_to_new_ns) {
77 fprintf(stdout, "This kernel does not support unprivileged namespaces. "
78 "USERNS tests will succeed without running.\n");
79 fflush(stdout);
80 return;
81 }
82 CHECK(Credentials::HasAnyCapability());
83 CHECK(Credentials::DropAllCapabilities());
84 CHECK(!Credentials::HasAnyCapability());
85 }
86
SANDBOX_TEST(Credentials,CanCreateProcessInNewUserNS)87 SANDBOX_TEST(Credentials, CanCreateProcessInNewUserNS) {
88 CHECK(Credentials::DropAllCapabilities());
89 bool user_ns_supported = Credentials::CanCreateProcessInNewUserNS();
90 bool moved_to_new_ns = Credentials::MoveToNewUserNS();
91 CHECK_EQ(user_ns_supported, moved_to_new_ns);
92 }
93
SANDBOX_TEST(Credentials,UidIsPreserved)94 SANDBOX_TEST(Credentials, UidIsPreserved) {
95 CHECK(Credentials::DropAllCapabilities());
96 uid_t old_ruid, old_euid, old_suid;
97 gid_t old_rgid, old_egid, old_sgid;
98 PCHECK(0 == getresuid(&old_ruid, &old_euid, &old_suid));
99 PCHECK(0 == getresgid(&old_rgid, &old_egid, &old_sgid));
100 // Probably missing kernel support.
101 if (!Credentials::MoveToNewUserNS()) return;
102 uid_t new_ruid, new_euid, new_suid;
103 PCHECK(0 == getresuid(&new_ruid, &new_euid, &new_suid));
104 CHECK(old_ruid == new_ruid);
105 CHECK(old_euid == new_euid);
106 CHECK(old_suid == new_suid);
107
108 gid_t new_rgid, new_egid, new_sgid;
109 PCHECK(0 == getresgid(&new_rgid, &new_egid, &new_sgid));
110 CHECK(old_rgid == new_rgid);
111 CHECK(old_egid == new_egid);
112 CHECK(old_sgid == new_sgid);
113 }
114
NewUserNSCycle()115 bool NewUserNSCycle() {
116 if (!Credentials::MoveToNewUserNS() ||
117 !Credentials::HasAnyCapability() ||
118 !Credentials::DropAllCapabilities() ||
119 Credentials::HasAnyCapability()) {
120 return false;
121 }
122 return true;
123 }
124
SANDBOX_TEST(Credentials,NestedUserNS)125 SANDBOX_TEST(Credentials, NestedUserNS) {
126 CHECK(Credentials::DropAllCapabilities());
127 // Probably missing kernel support.
128 if (!Credentials::MoveToNewUserNS()) return;
129 CHECK(Credentials::DropAllCapabilities());
130 // As of 3.12, the kernel has a limit of 32. See create_user_ns().
131 const int kNestLevel = 10;
132 for (int i = 0; i < kNestLevel; ++i) {
133 CHECK(NewUserNSCycle()) << "Creating new user NS failed at iteration "
134 << i << ".";
135 }
136 }
137
138 // Test the WorkingDirectoryIsRoot() helper.
SANDBOX_TEST(Credentials,CanDetectRoot)139 SANDBOX_TEST(Credentials, CanDetectRoot) {
140 PCHECK(0 == chdir("/proc/"));
141 CHECK(!WorkingDirectoryIsRoot());
142 PCHECK(0 == chdir("/"));
143 CHECK(WorkingDirectoryIsRoot());
144 }
145
146 // Disabled on ASAN because of crbug.com/451603.
SANDBOX_TEST(Credentials,DISABLE_ON_ASAN (DropFileSystemAccessIsSafe))147 SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessIsSafe)) {
148 CHECK(Credentials::DropAllCapabilities());
149 // Probably missing kernel support.
150 if (!Credentials::MoveToNewUserNS()) return;
151 CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get()));
152 CHECK(!base::DirectoryExists(base::FilePath("/proc")));
153 CHECK(WorkingDirectoryIsRoot());
154 CHECK(base::IsDirectoryEmpty(base::FilePath("/")));
155 // We want the chroot to never have a subdirectory. A subdirectory
156 // could allow a chroot escape.
157 CHECK_NE(0, mkdir("/test", 0700));
158 }
159
160 // Check that after dropping filesystem access and dropping privileges
161 // it is not possible to regain capabilities.
SANDBOX_TEST(Credentials,DISABLE_ON_ASAN (CannotRegainPrivileges))162 SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(CannotRegainPrivileges)) {
163 base::ScopedFD proc_fd(ProcUtil::OpenProc());
164 CHECK(Credentials::DropAllCapabilities(proc_fd.get()));
165 // Probably missing kernel support.
166 if (!Credentials::MoveToNewUserNS()) return;
167 CHECK(Credentials::DropFileSystemAccess(proc_fd.get()));
168 CHECK(Credentials::DropAllCapabilities(proc_fd.get()));
169
170 // The kernel should now prevent us from regaining capabilities because we
171 // are in a chroot.
172 CHECK(!Credentials::CanCreateProcessInNewUserNS());
173 CHECK(!Credentials::MoveToNewUserNS());
174 }
175
SANDBOX_TEST(Credentials,SetCapabilities)176 SANDBOX_TEST(Credentials, SetCapabilities) {
177 // Probably missing kernel support.
178 if (!Credentials::MoveToNewUserNS())
179 return;
180
181 base::ScopedFD proc_fd(ProcUtil::OpenProc());
182
183 CHECK(Credentials::HasCapability(Credentials::Capability::SYS_ADMIN));
184 CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
185
186 std::vector<Credentials::Capability> caps;
187 caps.push_back(Credentials::Capability::SYS_CHROOT);
188 CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
189
190 CHECK(!Credentials::HasCapability(Credentials::Capability::SYS_ADMIN));
191 CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
192
193 const std::vector<Credentials::Capability> no_caps;
194 CHECK(Credentials::SetCapabilities(proc_fd.get(), no_caps));
195 CHECK(!Credentials::HasAnyCapability());
196 }
197
SANDBOX_TEST(Credentials,SetCapabilitiesAndChroot)198 SANDBOX_TEST(Credentials, SetCapabilitiesAndChroot) {
199 // Probably missing kernel support.
200 if (!Credentials::MoveToNewUserNS())
201 return;
202
203 base::ScopedFD proc_fd(ProcUtil::OpenProc());
204
205 CHECK(Credentials::HasCapability(Credentials::Capability::SYS_CHROOT));
206 PCHECK(chroot("/") == 0);
207
208 std::vector<Credentials::Capability> caps;
209 caps.push_back(Credentials::Capability::SYS_CHROOT);
210 CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
211 PCHECK(chroot("/") == 0);
212
213 CHECK(Credentials::DropAllCapabilities());
214 PCHECK(chroot("/") == -1 && errno == EPERM);
215 }
216
SANDBOX_TEST(Credentials,SetCapabilitiesMatchesLibCap2)217 SANDBOX_TEST(Credentials, SetCapabilitiesMatchesLibCap2) {
218 // Probably missing kernel support.
219 if (!Credentials::MoveToNewUserNS())
220 return;
221
222 base::ScopedFD proc_fd(ProcUtil::OpenProc());
223
224 std::vector<Credentials::Capability> caps;
225 caps.push_back(Credentials::Capability::SYS_CHROOT);
226 CHECK(Credentials::SetCapabilities(proc_fd.get(), caps));
227
228 ScopedCap actual_cap(cap_get_proc());
229 PCHECK(actual_cap != nullptr);
230
231 ScopedCap expected_cap(cap_init());
232 PCHECK(expected_cap != nullptr);
233
234 const cap_value_t allowed_cap = CAP_SYS_CHROOT;
235 for (const cap_flag_t flag : {CAP_EFFECTIVE, CAP_PERMITTED}) {
236 PCHECK(cap_set_flag(expected_cap.get(), flag, 1, &allowed_cap, CAP_SET) ==
237 0);
238 }
239
240 CHECK_EQ(0, cap_compare(expected_cap.get(), actual_cap.get()));
241 }
242
243 volatile sig_atomic_t signal_handler_called;
SignalHandler(int sig)244 void SignalHandler(int sig) {
245 signal_handler_called = 1;
246 }
247
248 // Disabled on ASAN because of crbug.com/451603.
SANDBOX_TEST(Credentials,DISABLE_ON_ASAN (DropFileSystemAccessPreservesTLS))249 SANDBOX_TEST(Credentials, DISABLE_ON_ASAN(DropFileSystemAccessPreservesTLS)) {
250 // Probably missing kernel support.
251 if (!Credentials::MoveToNewUserNS()) return;
252 CHECK(Credentials::DropFileSystemAccess(ProcUtil::OpenProc().get()));
253
254 // In glibc, pthread_getattr_np makes an assertion about the cached PID/TID in
255 // TLS.
256 pthread_attr_t attr;
257 EXPECT_EQ(0, pthread_getattr_np(pthread_self(), &attr));
258
259 // raise also uses the cached TID in glibc.
260 struct sigaction action = {};
261 action.sa_handler = &SignalHandler;
262 PCHECK(sigaction(SIGUSR1, &action, nullptr) == 0);
263
264 PCHECK(raise(SIGUSR1) == 0);
265 CHECK_EQ(1, signal_handler_called);
266 }
267
268 } // namespace.
269
270 } // namespace sandbox.
271