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.
5 // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox
7 #include "sandbox/linux/suid/common/sandbox.h"
10 #include <asm/unistd.h>
22 #include <sys/prctl.h>
23 #include <sys/resource.h>
24 #include <sys/socket.h>
27 #include <sys/types.h>
32 #include "sandbox/linux/suid/common/suid_unsafe_environment_variables.h"
33 #include "sandbox/linux/suid/process_util.h"
35 #if !defined(CLONE_NEWPID)
36 #define CLONE_NEWPID 0x20000000
38 #if !defined(CLONE_NEWNET)
39 #define CLONE_NEWNET 0x40000000
42 static bool DropRoot();
44 #define HANDLE_EINTR(x) TEMP_FAILURE_RETRY(x)
46 static void FatalError(const char* msg
, ...)
47 __attribute__((noreturn
, format(printf
, 1, 2)));
49 static void FatalError(const char* msg
, ...) {
53 vfprintf(stderr
, msg
, ap
);
54 fprintf(stderr
, ": %s\n", strerror(errno
));
60 static void ExitWithErrorSignalHandler(int signal
) {
61 const char msg
[] = "\nThe setuid sandbox got signaled, exiting.\n";
62 if (-1 == write(2, msg
, sizeof(msg
) - 1)) {
69 // We will chroot() to the helper's /proc/self directory. Anything there will
70 // not exist anymore if we make sure to wait() for the helper.
72 // /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty
73 // even if the helper survives as a zombie.
75 // There is very little reason to use fdinfo/ instead of fd/ but we are
76 // paranoid. fdinfo/ only exists since 2.6.22 so we allow fallback to fd/
77 #define SAFE_DIR "/proc/self/fdinfo"
78 #define SAFE_DIR2 "/proc/self/fd"
80 static bool SpawnChrootHelper() {
82 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, sv
) == -1) {
88 struct stat sdir_stat
;
89 if (!stat(SAFE_DIR
, &sdir_stat
) && S_ISDIR(sdir_stat
.st_mode
)) {
91 } else if (!stat(SAFE_DIR2
, &sdir_stat
) && S_ISDIR(sdir_stat
.st_mode
)) {
94 fprintf(stderr
, "Could not find %s\n", SAFE_DIR2
);
98 const pid_t pid
= syscall(__NR_clone
, CLONE_FS
| SIGCHLD
, 0, 0, 0);
108 // We share our files structure with an untrusted process. As a security in
109 // depth measure, we make sure that we can't open anything by mistake.
110 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT
112 const struct rlimit nofile
= {0, 0};
113 if (setrlimit(RLIMIT_NOFILE
, &nofile
))
114 FatalError("Setting RLIMIT_NOFILE");
123 bytes
= read(sv
[0], &msg
, 1);
124 } while (bytes
== -1 && errno
== EINTR
);
132 if (msg
!= kMsgChrootMe
)
133 FatalError("Unknown message from sandboxed process");
137 FatalError("Cannot chdir into /proc/ directory");
140 FatalError("Cannot chroot into /proc/ directory");
143 FatalError("Cannot chdir to / after chroot");
145 const char reply
= kMsgChrootSuccessful
;
147 bytes
= write(sv
[0], &reply
, 1);
148 } while (bytes
== -1 && errno
== EINTR
);
151 FatalError("Writing reply");
154 // We now become a zombie. /proc/self/fd(info) is now an empty dir and we
155 // are chrooted there.
156 // Our (unprivileged) parent should not even be able to open "." or "/"
157 // since they would need to pass the ptrace() check. If our parent wait()
158 // for us, our root directory will completely disappear.
167 // In the parent process, we install an environment variable containing the
168 // number of the file descriptor.
170 int printed
= snprintf(desc_str
, sizeof(desc_str
), "%u", sv
[1]);
171 if (printed
< 0 || printed
>= (int)sizeof(desc_str
)) {
172 fprintf(stderr
, "Failed to snprintf\n");
176 if (setenv(kSandboxDescriptorEnvironmentVarName
, desc_str
, 1)) {
182 // We also install an environment variable containing the pid of the child
183 char helper_pid_str
[64];
184 printed
= snprintf(helper_pid_str
, sizeof(helper_pid_str
), "%u", pid
);
185 if (printed
< 0 || printed
>= (int)sizeof(helper_pid_str
)) {
186 fprintf(stderr
, "Failed to snprintf\n");
190 if (setenv(kSandboxHelperPidEnvironmentVarName
, helper_pid_str
, 1)) {
199 // Block until child_pid exits, then exit. Try to preserve the exit code.
200 static void WaitForChildAndExit(pid_t child_pid
) {
202 siginfo_t reaped_child_info
;
204 // Don't "Core" on SIGABRT. SIGABRT is sent by the Chrome OS session manager
205 // when things are hanging.
206 // Here, the current process is going to waitid() and _exit(), so there is no
207 // point in generating a crash report. The child process is the one
209 if (signal(SIGABRT
, ExitWithErrorSignalHandler
) == SIG_ERR
) {
210 FatalError("Failed to change signal handler");
214 HANDLE_EINTR(waitid(P_PID
, child_pid
, &reaped_child_info
, WEXITED
));
216 if (!wait_ret
&& reaped_child_info
.si_pid
== child_pid
) {
217 if (reaped_child_info
.si_code
== CLD_EXITED
) {
218 exit_code
= reaped_child_info
.si_status
;
220 // Exit with code 0 if the child got signaled.
227 static bool MoveToNewNamespaces() {
228 // These are the sets of flags which we'll try, in order.
229 const int kCloneExtraFlags
[] = {CLONE_NEWPID
| CLONE_NEWNET
, CLONE_NEWPID
, };
231 // We need to close kZygoteIdFd before the child can continue. We use this
232 // socketpair to tell the child when to continue;
234 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, sync_fds
)) {
235 FatalError("Failed to create a socketpair");
238 for (size_t i
= 0; i
< sizeof(kCloneExtraFlags
) / sizeof(kCloneExtraFlags
[0]);
240 pid_t pid
= syscall(__NR_clone
, SIGCHLD
| kCloneExtraFlags
[i
], 0, 0, 0);
241 const int clone_errno
= errno
;
245 FatalError("Could not drop privileges");
247 if (close(sync_fds
[0]) || shutdown(sync_fds
[1], SHUT_RD
))
248 FatalError("Could not close socketpair");
249 // The kZygoteIdFd needs to be closed in the parent before
250 // Zygote gets started.
251 if (close(kZygoteIdFd
))
253 // Tell our child to continue
254 if (HANDLE_EINTR(send(sync_fds
[1], "C", 1, MSG_NOSIGNAL
)) != 1)
256 if (close(sync_fds
[1]))
258 // We want to keep a full process tree and we don't want our childs to
259 // be reparented to (the outer PID namespace) init. So we wait for it.
260 WaitForChildAndExit(pid
);
263 FatalError("Not reached");
267 if (close(sync_fds
[1]) || shutdown(sync_fds
[0], SHUT_WR
))
268 FatalError("Could not close socketpair");
270 // Wait for the parent to confirm it closed kZygoteIdFd before we
272 char should_continue
;
273 if (HANDLE_EINTR(read(sync_fds
[0], &should_continue
, 1)) != 1)
274 FatalError("Read on socketpair");
275 if (close(sync_fds
[0]))
278 if (kCloneExtraFlags
[i
] & CLONE_NEWPID
) {
279 setenv(kSandboxPIDNSEnvironmentVarName
, "", 1 /* overwrite */);
281 unsetenv(kSandboxPIDNSEnvironmentVarName
);
284 if (kCloneExtraFlags
[i
] & CLONE_NEWNET
) {
285 setenv(kSandboxNETNSEnvironmentVarName
, "", 1 /* overwrite */);
287 unsetenv(kSandboxNETNSEnvironmentVarName
);
293 // If EINVAL then the system doesn't support the requested flags, so
294 // continue to try a different set.
295 // On any other errno value the system *does* support these flags but
296 // something went wrong, hence we bail with an error message rather then
297 // provide less security.
298 if (errno
!= EINVAL
) {
299 fprintf(stderr
, "Failed to move to new namespace:");
300 if (kCloneExtraFlags
[i
] & CLONE_NEWPID
) {
301 fprintf(stderr
, " PID namespaces supported,");
303 if (kCloneExtraFlags
[i
] & CLONE_NEWNET
) {
304 fprintf(stderr
, " Network namespace supported,");
306 fprintf(stderr
, " but failed: errno = %s\n", strerror(clone_errno
));
311 // If the system doesn't support NEWPID then we carry on anyway.
315 static bool DropRoot() {
316 if (prctl(PR_SET_DUMPABLE
, 0, 0, 0, 0)) {
317 perror("prctl(PR_SET_DUMPABLE)");
321 if (prctl(PR_GET_DUMPABLE
, 0, 0, 0, 0)) {
322 perror("Still dumpable after prctl(PR_SET_DUMPABLE)");
326 gid_t rgid
, egid
, sgid
;
327 if (getresgid(&rgid
, &egid
, &sgid
)) {
332 if (setresgid(rgid
, rgid
, rgid
)) {
337 uid_t ruid
, euid
, suid
;
338 if (getresuid(&ruid
, &euid
, &suid
)) {
343 if (setresuid(ruid
, ruid
, ruid
)) {
351 static bool SetupChildEnvironment() {
354 // ld.so may have cleared several environment variables because we are SUID.
355 // However, the child process might need them so zygote_host_linux.cc saves a
356 // copy in SANDBOX_$x. This is safe because we have dropped root by this
357 // point, so we can only exec a binary with the permissions of the user who
358 // ran us in the first place.
360 for (i
= 0; kSUIDUnsafeEnvironmentVariables
[i
]; ++i
) {
361 const char* const envvar
= kSUIDUnsafeEnvironmentVariables
[i
];
362 char* const saved_envvar
= SandboxSavedEnvironmentVariable(envvar
);
366 const char* const value
= getenv(saved_envvar
);
368 setenv(envvar
, value
, 1 /* overwrite */);
369 unsetenv(saved_envvar
);
378 bool CheckAndExportApiVersion() {
379 // Check the environment to see if a specific API version was requested.
380 // assume version 0 if none.
381 long api_number
= -1;
382 char* api_string
= getenv(kSandboxEnvironmentApiRequest
);
388 api_number
= strtol(api_string
, &endptr
, 10);
389 if (!endptr
|| *endptr
|| errno
!= 0)
393 // Warn only for now.
394 if (api_number
!= kSUIDSandboxApiNumber
) {
397 "The setuid sandbox provides API version %ld, "
400 "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment."
402 kSUIDSandboxApiNumber
,
406 // Export our version so that the sandboxed process can verify it did not
407 // use an old sandbox.
408 char version_string
[64];
410 version_string
, sizeof(version_string
), "%ld", kSUIDSandboxApiNumber
);
411 if (setenv(kSandboxEnvironmentApiProvides
, version_string
, 1)) {
419 int main(int argc
, char** argv
) {
425 fprintf(stderr
, "Usage: %s <renderer process> <args...>\n", argv
[0]);
429 // Allow someone to query our API version
430 if (argc
== 2 && 0 == strcmp(argv
[1], kSuidSandboxGetApiSwitch
)) {
431 printf("%ld\n", kSUIDSandboxApiNumber
);
435 // We cannot adjust /proc/pid/oom_adj for sandboxed renderers
436 // because those files are owned by root. So we need a helper here.
437 if (argc
== 4 && (0 == strcmp(argv
[1], kAdjustOOMScoreSwitch
))) {
441 unsigned long pid_ul
= strtoul(argv
[2], &endptr
, 10);
442 if (pid_ul
== ULONG_MAX
|| !endptr
|| *endptr
|| errno
!= 0)
447 score
= strtol(argv
[3], &endptr
, 10);
448 if (score
== LONG_MAX
|| score
== LONG_MIN
|| !endptr
|| *endptr
||
452 return AdjustOOMScore(pid
, score
);
455 // Protect the core setuid sandbox functionality with an API version
456 if (!CheckAndExportApiVersion()) {
460 if (geteuid() != 0) {
462 "The setuid sandbox is not running as root. Common causes:\n"
463 " * An unprivileged process using ptrace on it, like a debugger.\n"
464 " * A parent process set prctl(PR_SET_NO_NEW_PRIVS, ...)\n");
467 if (!MoveToNewNamespaces())
469 if (!SpawnChrootHelper())
473 if (!SetupChildEnvironment())
476 execv(argv
[1], &argv
[1]);
477 FatalError("execv failed");