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"); |
168 return false; | 206 close(sv[1]); |
| 207 return -1; |
169 } | 208 } |
170 | 209 |
171 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { | 210 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { |
172 perror("setenv"); | 211 perror("setenv"); |
173 close(sv[1]); | 212 close(sv[1]); |
174 return false; | 213 return -1; |
175 } | 214 } |
176 | 215 |
177 // We also install an environment variable containing the pid of the child | 216 // We also install an environment variable containing the pid of the child |
178 char helper_pid_str[64]; | 217 char helper_pid_str[64]; |
179 printed = snprintf(helper_pid_str, sizeof(helper_pid_str), "%u", pid); | 218 printed = snprintf(helper_pid_str, sizeof(helper_pid_str), "%u", pid); |
180 if (printed < 0 || printed >= (int)sizeof(helper_pid_str)) { | 219 if (printed < 0 || printed >= (int)sizeof(helper_pid_str)) { |
181 fprintf(stderr, "Failed to snprintf\n"); | 220 fprintf(stderr, "Failed to snprintf\n"); |
182 return false; | 221 close(sv[1]); |
| 222 return -1; |
183 } | 223 } |
184 | 224 |
185 if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) { | 225 if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) { |
186 perror("setenv"); | 226 perror("setenv"); |
187 close(sv[1]); | 227 close(sv[1]); |
| 228 return -1; |
| 229 } |
| 230 |
| 231 return sv[1]; |
| 232 } |
| 233 |
| 234 static bool JailMe() { |
| 235 int fd = SpawnChrootHelper(); |
| 236 if (fd < 0) { |
188 return false; | 237 return false; |
189 } | 238 } |
190 | 239 if (!DropRoot()) { |
| 240 close(fd); |
| 241 return false; |
| 242 } |
| 243 ssize_t bytes; |
| 244 char ch = kMsgChrootMe; |
| 245 do { |
| 246 errno = 0; |
| 247 bytes = write(fd, &ch, 1); |
| 248 } while (bytes == -1 && errno == EINTR); |
| 249 if (bytes != 1) { |
| 250 perror("write"); |
| 251 close(fd); |
| 252 return false; |
| 253 } |
| 254 do { |
| 255 errno = 0; |
| 256 bytes = read(fd, &ch, 1); |
| 257 } while (bytes == -1 && errno == EINTR); |
| 258 close(fd); |
| 259 if (bytes != 1) { |
| 260 perror("read"); |
| 261 return false; |
| 262 } |
| 263 if (ch != kMsgChrootSuccessful) { |
| 264 return false; |
| 265 } |
191 return true; | 266 return true; |
192 } | 267 } |
193 | 268 |
194 static bool MoveToNewNamespaces() { | 269 static bool MoveToNewNamespaces() { |
195 // These are the sets of flags which we'll try, in order. | 270 // These are the sets of flags which we'll try, in order. |
196 const int kCloneExtraFlags[] = { | 271 const int kCloneExtraFlags[] = { |
197 CLONE_NEWPID | CLONE_NEWNET, | 272 CLONE_NEWPID | CLONE_NEWNET, |
198 CLONE_NEWPID, | 273 CLONE_NEWPID, |
199 }; | 274 }; |
200 | 275 |
201 for (size_t i = 0; | 276 for (size_t i = 0; |
202 i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); | 277 i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); |
203 i++) { | 278 i++) { |
204 pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); | 279 pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); |
205 | 280 |
206 if (pid > 0) | 281 if (pid > 0) |
207 _exit(0); | 282 _exit(0); |
208 | 283 |
209 if (pid == 0) { | 284 if (pid == 0) { |
| 285 if (syscall(__NR_getpid) == 1) { |
| 286 int fds[2]; |
| 287 char ch = 0; |
| 288 if (pipe(fds)) { |
| 289 perror("Failed to create pipe"); |
| 290 _exit(1); |
| 291 } |
| 292 pid = fork(); |
| 293 if (pid > 0) { |
| 294 // The very first process in the new namespace takes on the |
| 295 // role of the traditional "init" process. It must reap exit |
| 296 // codes of daemon processes until the namespace is completely |
| 297 // empty. |
| 298 // We have to be careful that this "init" process doesn't |
| 299 // provide a new attack surface. So, we also move it into |
| 300 // a separate chroot and we drop all privileges. It does |
| 301 // still need to access "/proc" and "/dev/null", though. So, |
| 302 // we have to provide it with a file handles to these resources. |
| 303 // These file handle are not accessible by any other processes in |
| 304 // the sandbox and thus safe. |
| 305 close(fds[0]); |
| 306 int proc_fd = open("/proc", O_RDONLY | O_DIRECTORY); |
| 307 int null_fd = open("/dev/null", O_RDWR); |
| 308 if (!JailMe()) { |
| 309 FatalError("Could not remove privileges from " |
| 310 "new \"init\" process"); |
| 311 } |
| 312 SystemInitProcess(fds[1], pid, proc_fd, null_fd); |
| 313 } else if (pid != 0) { |
| 314 perror("Failed to fork"); |
| 315 _exit(1); |
| 316 } |
| 317 // Wait for the "init" process to complete initialization. |
| 318 close(fds[1]); |
| 319 errno = 0; |
| 320 while (read(fds[0], &ch, 1) < 0 && errno == EINTR) { |
| 321 } |
| 322 close(fds[0]); |
| 323 if (ch != ' ') { |
| 324 // We'll likely never get here. If the "init" process fails, it's |
| 325 // death typically takes everyone of its children with it. |
| 326 FatalError("Failed to set up new \"init\" process inside sandbox"); |
| 327 } |
| 328 } |
| 329 |
210 if (kCloneExtraFlags[i] & CLONE_NEWPID) { | 330 if (kCloneExtraFlags[i] & CLONE_NEWPID) { |
211 setenv("SBX_PID_NS", "", 1 /* overwrite */); | 331 setenv("SBX_PID_NS", "", 1 /* overwrite */); |
212 } else { | 332 } else { |
213 unsetenv("SBX_PID_NS"); | 333 unsetenv("SBX_PID_NS"); |
214 } | 334 } |
215 | 335 |
216 if (kCloneExtraFlags[i] & CLONE_NEWNET) { | 336 if (kCloneExtraFlags[i] & CLONE_NEWNET) { |
217 setenv("SBX_NET_NS", "", 1 /* overwrite */); | 337 setenv("SBX_NET_NS", "", 1 /* overwrite */); |
218 } else { | 338 } else { |
219 unsetenv("SBX_NET_NS"); | 339 unsetenv("SBX_NET_NS"); |
220 } | 340 } |
221 | 341 |
222 break; | 342 break; |
223 } | 343 } |
224 | 344 |
225 if (errno != EINVAL) { | 345 if (errno != EINVAL) { |
226 perror("Failed to move to new PID namespace"); | 346 perror("Failed to move to new PID namespace"); |
227 return false; | 347 return false; |
228 } | 348 } |
229 } | 349 } |
230 | 350 |
231 // If the system doesn't support NEWPID then we carry on anyway. | 351 // If the system doesn't support NEWPID then we carry on anyway. |
232 return true; | 352 return true; |
233 } | 353 } |
234 | 354 |
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() { | 355 static bool SetupChildEnvironment() { |
272 unsigned i; | 356 unsigned i; |
273 | 357 |
274 // ld.so may have cleared several environment variables because we are SUID. | 358 // 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 | 359 // 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 | 360 // 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 | 361 // point, so we can only exec a binary with the permissions of the user who |
278 // ran us in the first place. | 362 // ran us in the first place. |
279 | 363 |
280 for (i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { | 364 for (i = 0; kSUIDUnsafeEnvironmentVariables[i]; ++i) { |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
336 return 1; | 420 return 1; |
337 pid_t pid = pid_ul; | 421 pid_t pid = pid_ul; |
338 score = strtol(argv[3], &endptr, 10); | 422 score = strtol(argv[3], &endptr, 10); |
339 if (score == LONG_MAX || score == LONG_MIN || *endptr) | 423 if (score == LONG_MAX || score == LONG_MIN || *endptr) |
340 return 1; | 424 return 1; |
341 return AdjustOOMScore(pid, score); | 425 return AdjustOOMScore(pid, score); |
342 } | 426 } |
343 | 427 |
344 if (!MoveToNewNamespaces()) | 428 if (!MoveToNewNamespaces()) |
345 return 1; | 429 return 1; |
346 if (!SpawnChrootHelper()) | 430 if (SpawnChrootHelper() < 0) |
347 return 1; | 431 return 1; |
348 if (!DropRoot()) | 432 if (!DropRoot()) |
349 return 1; | 433 return 1; |
350 if (!SetupChildEnvironment()) | 434 if (!SetupChildEnvironment()) |
351 return 1; | 435 return 1; |
352 | 436 |
353 execv(argv[1], &argv[1]); | 437 execv(argv[1], &argv[1]); |
354 FatalError("execv failed"); | 438 FatalError("execv failed"); |
355 | 439 |
356 return 1; | 440 return 1; |
357 } | 441 } |
OLD | NEW |