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