OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox | 5 // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox |
6 | 6 |
7 #include "sandbox.h" | 7 #include "sandbox.h" |
8 | 8 |
9 #define _GNU_SOURCE | 9 #define _GNU_SOURCE |
10 #include <asm/unistd.h> | 10 #include <asm/unistd.h> |
11 #include <errno.h> | 11 #include <errno.h> |
12 #include <fcntl.h> | 12 #include <fcntl.h> |
13 #include <limits.h> | 13 #include <limits.h> |
14 #include <sched.h> | 14 #include <sched.h> |
15 #include <signal.h> | 15 #include <signal.h> |
16 #include <stdarg.h> | 16 #include <stdarg.h> |
17 #include <stdbool.h> | 17 #include <stdbool.h> |
18 #include <stdint.h> | 18 #include <stdint.h> |
19 #include <stdio.h> | 19 #include <stdio.h> |
20 #include <stdlib.h> | 20 #include <stdlib.h> |
21 #include <string.h> | 21 #include <string.h> |
22 #include <sys/prctl.h> | 22 #include <sys/prctl.h> |
23 #include <sys/resource.h> | 23 #include <sys/resource.h> |
24 #include <sys/socket.h> | 24 #include <sys/socket.h> |
25 #include <sys/stat.h> | 25 #include <sys/stat.h> |
26 #include <sys/time.h> | 26 #include <sys/time.h> |
27 #include <sys/types.h> | 27 #include <sys/types.h> |
28 #include <sys/vfs.h> | 28 #include <sys/vfs.h> |
29 #include <unistd.h> | 29 #include <unistd.h> |
30 | 30 |
31 #include "init_process.h" | |
32 #include "linux_util.h" | 31 #include "linux_util.h" |
33 #include "process_util.h" | 32 #include "process_util.h" |
34 #include "suid_unsafe_environment_variables.h" | 33 #include "suid_unsafe_environment_variables.h" |
35 | 34 |
36 #if !defined(CLONE_NEWPID) | 35 #if !defined(CLONE_NEWPID) |
37 #define CLONE_NEWPID 0x20000000 | 36 #define CLONE_NEWPID 0x20000000 |
38 #endif | 37 #endif |
39 #if !defined(CLONE_NEWNET) | 38 #if !defined(CLONE_NEWNET) |
40 #define CLONE_NEWNET 0x40000000 | 39 #define CLONE_NEWNET 0x40000000 |
41 #endif | 40 #endif |
(...skipping 23 matching lines...) Expand all Loading... |
65 // not exist anymore if we make sure to wait() for the helper. | 64 // not exist anymore if we make sure to wait() for the helper. |
66 // | 65 // |
67 // /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty | 66 // /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty |
68 // even if the helper survives as a zombie. | 67 // even if the helper survives as a zombie. |
69 // | 68 // |
70 // There is very little reason to use fdinfo/ instead of fd/ but we are | 69 // There is very little reason to use fdinfo/ instead of fd/ but we are |
71 // paranoid. fdinfo/ only exists since 2.6.22 so we allow fallback to fd/ | 70 // paranoid. fdinfo/ only exists since 2.6.22 so we allow fallback to fd/ |
72 #define SAFE_DIR "/proc/self/fdinfo" | 71 #define SAFE_DIR "/proc/self/fdinfo" |
73 #define SAFE_DIR2 "/proc/self/fd" | 72 #define SAFE_DIR2 "/proc/self/fd" |
74 | 73 |
75 static bool DropRoot() { | 74 static bool SpawnChrootHelper() { |
76 if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)) { | 75 int sv[2]; |
77 perror("prctl(PR_SET_DUMPABLE)"); | 76 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { |
| 77 perror("socketpair"); |
78 return false; | 78 return false; |
79 } | 79 } |
80 | 80 |
81 if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { | |
82 perror("Still dumpable after prctl(PR_SET_DUMPABLE)"); | |
83 return false; | |
84 } | |
85 | |
86 gid_t rgid, egid, sgid; | |
87 if (getresgid(&rgid, &egid, &sgid)) { | |
88 perror("getresgid"); | |
89 return false; | |
90 } | |
91 | |
92 if (setresgid(rgid, rgid, rgid)) { | |
93 perror("setresgid"); | |
94 return false; | |
95 } | |
96 | |
97 uid_t ruid, euid, suid; | |
98 if (getresuid(&ruid, &euid, &suid)) { | |
99 perror("getresuid"); | |
100 return false; | |
101 } | |
102 | |
103 if (setresuid(ruid, ruid, ruid)) { | |
104 perror("setresuid"); | |
105 return false; | |
106 } | |
107 | |
108 return true; | |
109 } | |
110 | |
111 static int SpawnChrootHelper() { | |
112 int sv[2]; | |
113 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { | |
114 perror("socketpair"); | |
115 return -1; | |
116 } | |
117 | |
118 char *safedir = NULL; | 81 char *safedir = NULL; |
119 struct stat sdir_stat; | 82 struct stat sdir_stat; |
120 if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) | 83 if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) |
121 safedir = SAFE_DIR; | 84 safedir = SAFE_DIR; |
122 else | 85 else |
123 if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) | 86 if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) |
124 safedir = SAFE_DIR2; | 87 safedir = SAFE_DIR2; |
125 else { | 88 else { |
126 fprintf(stderr, "Could not find %s\n", SAFE_DIR2); | 89 fprintf(stderr, "Could not find %s\n", SAFE_DIR2); |
127 return -1; | 90 return false; |
128 } | 91 } |
129 | 92 |
130 const pid_t pid = syscall( | 93 const pid_t pid = syscall( |
131 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); | 94 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); |
132 | 95 |
133 if (pid == -1) { | 96 if (pid == -1) { |
134 perror("clone"); | 97 perror("clone"); |
135 close(sv[0]); | 98 close(sv[0]); |
136 close(sv[1]); | 99 close(sv[1]); |
137 return -1; | 100 return false; |
138 } | 101 } |
139 | 102 |
140 if (pid == 0) { | 103 if (pid == 0) { |
141 // We share our files structure with an untrusted process. As a security in | 104 // We share our files structure with an untrusted process. As a security in |
142 // depth measure, we make sure that we can't open anything by mistake. | 105 // depth measure, we make sure that we can't open anything by mistake. |
143 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT | 106 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT |
144 | 107 |
145 const struct rlimit nofile = {0, 0}; | 108 const struct rlimit nofile = {0, 0}; |
146 if (setrlimit(RLIMIT_NOFILE, &nofile)) | 109 if (setrlimit(RLIMIT_NOFILE, &nofile)) |
147 FatalError("Setting RLIMIT_NOFILE"); | 110 FatalError("Setting RLIMIT_NOFILE"); |
148 | 111 |
149 if (close(sv[1])) | 112 if (close(sv[1])) |
150 FatalError("close"); | 113 FatalError("close"); |
151 | 114 |
152 // wait for message | 115 // wait for message |
153 char msg; | 116 char msg; |
154 ssize_t bytes; | 117 ssize_t bytes; |
155 do { | 118 do { |
156 bytes = read(sv[0], &msg, 1); | 119 bytes = read(sv[0], &msg, 1); |
157 } while (bytes == -1 && errno == EINTR); | 120 } while (bytes == -1 && errno == EINTR); |
158 | 121 |
159 if (bytes == 0) | 122 if (bytes == 0) |
160 _exit(0); | 123 _exit(0); |
161 if (bytes != 1) | 124 if (bytes != 1) |
162 FatalError("read"); | 125 FatalError("read"); |
163 | 126 |
164 // do chrooting | 127 // do chrooting |
165 errno = 0; | |
166 if (msg != kMsgChrootMe) | 128 if (msg != kMsgChrootMe) |
167 FatalError("Unknown message from sandboxed process"); | 129 FatalError("Unknown message from sandboxed process"); |
168 | 130 |
169 // sanity check | 131 // sanity check |
170 if (chdir(safedir)) | 132 if (chdir(safedir)) |
171 FatalError("Cannot chdir into /proc/ directory"); | 133 FatalError("Cannot chdir into /proc/ directory"); |
172 | 134 |
173 if (chroot(safedir)) | 135 if (chroot(safedir)) |
174 FatalError("Cannot chroot into /proc/ directory"); | 136 FatalError("Cannot chroot into /proc/ directory"); |
175 | 137 |
(...skipping 12 matching lines...) Expand all Loading... |
188 // We now become a zombie. /proc/self/fd(info) is now an empty dir and we | 150 // We now become a zombie. /proc/self/fd(info) is now an empty dir and we |
189 // are chrooted there. | 151 // are chrooted there. |
190 // Our (unprivileged) parent should not even be able to open "." or "/" | 152 // Our (unprivileged) parent should not even be able to open "." or "/" |
191 // since they would need to pass the ptrace() check. If our parent wait() | 153 // since they would need to pass the ptrace() check. If our parent wait() |
192 // for us, our root directory will completely disappear. | 154 // for us, our root directory will completely disappear. |
193 } | 155 } |
194 | 156 |
195 if (close(sv[0])) { | 157 if (close(sv[0])) { |
196 close(sv[1]); | 158 close(sv[1]); |
197 perror("close"); | 159 perror("close"); |
198 return -1; | 160 return false; |
199 } | 161 } |
200 | 162 |
201 // In the parent process, we install an environment variable containing the | 163 // In the parent process, we install an environment variable containing the |
202 // number of the file descriptor. | 164 // number of the file descriptor. |
203 char desc_str[64]; | 165 char desc_str[64]; |
204 int printed = snprintf(desc_str, sizeof(desc_str), "%u", sv[1]); | 166 int printed = snprintf(desc_str, sizeof(desc_str), "%u", sv[1]); |
205 if (printed < 0 || printed >= (int)sizeof(desc_str)) { | 167 if (printed < 0 || printed >= (int)sizeof(desc_str)) { |
206 fprintf(stderr, "Failed to snprintf\n"); | 168 fprintf(stderr, "Failed to snprintf\n"); |
207 close(sv[1]); | 169 return false; |
208 return -1; | |
209 } | 170 } |
210 | 171 |
211 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { | 172 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { |
212 perror("setenv"); | 173 perror("setenv"); |
213 close(sv[1]); | 174 close(sv[1]); |
214 return -1; | 175 return false; |
215 } | 176 } |
216 | 177 |
217 // We also install an environment variable containing the pid of the child | 178 // We also install an environment variable containing the pid of the child |
218 char helper_pid_str[64]; | 179 char helper_pid_str[64]; |
219 printed = snprintf(helper_pid_str, sizeof(helper_pid_str), "%u", pid); | 180 printed = snprintf(helper_pid_str, sizeof(helper_pid_str), "%u", pid); |
220 if (printed < 0 || printed >= (int)sizeof(helper_pid_str)) { | 181 if (printed < 0 || printed >= (int)sizeof(helper_pid_str)) { |
221 fprintf(stderr, "Failed to snprintf\n"); | 182 fprintf(stderr, "Failed to snprintf\n"); |
222 close(sv[1]); | 183 return false; |
223 return -1; | |
224 } | 184 } |
225 | 185 |
226 if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) { | 186 if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) { |
227 perror("setenv"); | 187 perror("setenv"); |
228 close(sv[1]); | 188 close(sv[1]); |
229 return -1; | 189 return false; |
230 } | 190 } |
231 | 191 |
232 return sv[1]; | |
233 } | |
234 | |
235 static bool JailMe() { | |
236 int fd = SpawnChrootHelper(); | |
237 if (fd < 0) { | |
238 return false; | |
239 } | |
240 if (!DropRoot()) { | |
241 close(fd); | |
242 return false; | |
243 } | |
244 ssize_t bytes; | |
245 char ch = kMsgChrootMe; | |
246 do { | |
247 errno = 0; | |
248 bytes = write(fd, &ch, 1); | |
249 } while (bytes == -1 && errno == EINTR); | |
250 if (bytes != 1) { | |
251 perror("write"); | |
252 close(fd); | |
253 return false; | |
254 } | |
255 do { | |
256 errno = 0; | |
257 bytes = read(fd, &ch, 1); | |
258 } while (bytes == -1 && errno == EINTR); | |
259 close(fd); | |
260 if (bytes != 1) { | |
261 perror("read"); | |
262 return false; | |
263 } | |
264 if (ch != kMsgChrootSuccessful) { | |
265 return false; | |
266 } | |
267 return true; | 192 return true; |
268 } | 193 } |
269 | 194 |
270 static bool MoveToNewNamespaces() { | 195 static bool MoveToNewNamespaces() { |
271 // These are the sets of flags which we'll try, in order. | 196 // These are the sets of flags which we'll try, in order. |
272 const int kCloneExtraFlags[] = { | 197 const int kCloneExtraFlags[] = { |
273 CLONE_NEWPID | CLONE_NEWNET, | 198 CLONE_NEWPID | CLONE_NEWNET, |
274 CLONE_NEWPID, | 199 CLONE_NEWPID, |
275 }; | 200 }; |
276 | 201 |
277 for (size_t i = 0; | 202 for (size_t i = 0; |
278 i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); | 203 i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); |
279 i++) { | 204 i++) { |
280 pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); | 205 pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); |
281 | 206 |
282 if (pid > 0) | 207 if (pid > 0) |
283 _exit(0); | 208 _exit(0); |
284 | 209 |
285 if (pid == 0) { | 210 if (pid == 0) { |
286 if (syscall(__NR_getpid) == 1) { | |
287 int fds[2]; | |
288 char ch = 0; | |
289 if (pipe(fds)) { | |
290 perror("Failed to create pipe"); | |
291 _exit(1); | |
292 } | |
293 pid = fork(); | |
294 if (pid > 0) { | |
295 // The very first process in the new namespace takes on the | |
296 // role of the traditional "init" process. It must reap exit | |
297 // codes of daemon processes until the namespace is completely | |
298 // empty. | |
299 // We have to be careful that this "init" process doesn't | |
300 // provide a new attack surface. So, we also move it into | |
301 // a separate chroot and we drop all privileges. It does | |
302 // still need to access "/proc" and "/dev/null", though. So, | |
303 // we have to provide it with a file handles to these resources. | |
304 // These file handle are not accessible by any other processes in | |
305 // the sandbox and thus safe. | |
306 close(fds[0]); | |
307 int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); | |
308 int null_fd = open("/dev/null", O_RDWR); | |
309 if (!JailMe()) { | |
310 FatalError("Could not remove privileges from " | |
311 "new \"init\" process"); | |
312 } | |
313 SystemInitProcess(fds[1], pid, proc_fd, null_fd); | |
314 } else if (pid != 0) { | |
315 perror("Failed to fork"); | |
316 _exit(1); | |
317 } | |
318 // Wait for the "init" process to complete initialization. | |
319 close(fds[1]); | |
320 errno = 0; | |
321 while (read(fds[0], &ch, 1) < 0 && errno == EINTR) { | |
322 } | |
323 close(fds[0]); | |
324 if (ch != ' ') { | |
325 // We'll likely never get here. If the "init" process fails, it's | |
326 // death typically takes everyone of its children with it. | |
327 FatalError("Failed to set up new \"init\" process inside sandbox"); | |
328 } | |
329 } | |
330 | |
331 if (kCloneExtraFlags[i] & CLONE_NEWPID) { | 211 if (kCloneExtraFlags[i] & CLONE_NEWPID) { |
332 setenv("SBX_PID_NS", "", 1 /* overwrite */); | 212 setenv("SBX_PID_NS", "", 1 /* overwrite */); |
333 } else { | 213 } else { |
334 unsetenv("SBX_PID_NS"); | 214 unsetenv("SBX_PID_NS"); |
335 } | 215 } |
336 | 216 |
337 if (kCloneExtraFlags[i] & CLONE_NEWNET) { | 217 if (kCloneExtraFlags[i] & CLONE_NEWNET) { |
338 setenv("SBX_NET_NS", "", 1 /* overwrite */); | 218 setenv("SBX_NET_NS", "", 1 /* overwrite */); |
339 } else { | 219 } else { |
340 unsetenv("SBX_NET_NS"); | 220 unsetenv("SBX_NET_NS"); |
341 } | 221 } |
342 | 222 |
343 break; | 223 break; |
344 } | 224 } |
345 | 225 |
346 if (errno != EINVAL) { | 226 if (errno != EINVAL) { |
347 perror("Failed to move to new PID namespace"); | 227 perror("Failed to move to new PID namespace"); |
348 return false; | 228 return false; |
349 } | 229 } |
350 } | 230 } |
351 | 231 |
352 // If the system doesn't support NEWPID then we carry on anyway. | 232 // If the system doesn't support NEWPID then we carry on anyway. |
353 return true; | 233 return true; |
354 } | 234 } |
355 | 235 |
| 236 static bool DropRoot() { |
| 237 if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)) { |
| 238 perror("prctl(PR_SET_DUMPABLE)"); |
| 239 return false; |
| 240 } |
| 241 |
| 242 if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { |
| 243 perror("Still dumpable after prctl(PR_SET_DUMPABLE)"); |
| 244 return false; |
| 245 } |
| 246 |
| 247 gid_t rgid, egid, sgid; |
| 248 if (getresgid(&rgid, &egid, &sgid)) { |
| 249 perror("getresgid"); |
| 250 return false; |
| 251 } |
| 252 |
| 253 if (setresgid(rgid, rgid, rgid)) { |
| 254 perror("setresgid"); |
| 255 return false; |
| 256 } |
| 257 |
| 258 uid_t ruid, euid, suid; |
| 259 if (getresuid(&ruid, &euid, &suid)) { |
| 260 perror("getresuid"); |
| 261 return false; |
| 262 } |
| 263 |
| 264 if (setresuid(ruid, ruid, ruid)) { |
| 265 perror("setresuid"); |
| 266 return false; |
| 267 } |
| 268 |
| 269 return true; |
| 270 } |
| 271 |
356 static bool SetupChildEnvironment() { | 272 static bool SetupChildEnvironment() { |
357 unsigned i; | 273 unsigned i; |
358 | 274 |
359 // ld.so may have cleared several environment variables because we are SUID. | 275 // ld.so may have cleared several environment variables because we are SUID. |
360 // However, the child process might need them so zygote_host_linux.cc saves a | 276 // However, the child process might need them so zygote_host_linux.cc saves a |
361 // copy in SANDBOX_$x. This is safe because we have dropped root by this | 277 // copy in SANDBOX_$x. This is safe because we have dropped root by this |
362 // point, so we can only exec a binary with the permissions of the user who | 278 // point, so we can only exec a binary with the permissions of the user who |
363 // ran us in the first place. | 279 // ran us in the first place. |
364 | 280 |
365 for (i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { | 281 for (i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
440 errno = 0; | 356 errno = 0; |
441 unsigned long margin_mb = strtoul(argv[2], &endptr, 10); | 357 unsigned long margin_mb = strtoul(argv[2], &endptr, 10); |
442 if (!endptr || *endptr || errno != 0) | 358 if (!endptr || *endptr || errno != 0) |
443 return 1; | 359 return 1; |
444 return AdjustLowMemoryMargin(margin_mb); | 360 return AdjustLowMemoryMargin(margin_mb); |
445 } | 361 } |
446 #endif | 362 #endif |
447 | 363 |
448 if (!MoveToNewNamespaces()) | 364 if (!MoveToNewNamespaces()) |
449 return 1; | 365 return 1; |
450 if (SpawnChrootHelper() < 0) | 366 if (!SpawnChrootHelper()) |
451 return 1; | 367 return 1; |
452 if (!DropRoot()) | 368 if (!DropRoot()) |
453 return 1; | 369 return 1; |
454 if (!SetupChildEnvironment()) | 370 if (!SetupChildEnvironment()) |
455 return 1; | 371 return 1; |
456 | 372 |
457 execv(argv[1], &argv[1]); | 373 execv(argv[1], &argv[1]); |
458 FatalError("execv failed"); | 374 FatalError("execv failed"); |
459 | 375 |
460 return 1; | 376 return 1; |
461 } | 377 } |
OLD | NEW |