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 "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 "linux_util.h"
33 #include "process_util.h"
34 #include "common/suid_unsafe_environment_variables.h"
36 #if !defined(CLONE_NEWPID)
37 #define CLONE_NEWPID 0x20000000
39 #if !defined(CLONE_NEWNET)
40 #define CLONE_NEWNET 0x40000000
43 static bool DropRoot();
45 #define HANDLE_EINTR(x) TEMP_FAILURE_RETRY(x)
47 static void FatalError(const char *msg
, ...)
48 __attribute__((noreturn
, format(printf
, 1, 2)));
50 static void FatalError(const char *msg
, ...) {
54 vfprintf(stderr
, msg
, ap
);
55 fprintf(stderr
, ": %s\n", strerror(errno
));
61 // We will chroot() to the helper's /proc/self directory. Anything there will
62 // not exist anymore if we make sure to wait() for the helper.
64 // /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty
65 // even if the helper survives as a zombie.
67 // There is very little reason to use fdinfo/ instead of fd/ but we are
68 // paranoid. fdinfo/ only exists since 2.6.22 so we allow fallback to fd/
69 #define SAFE_DIR "/proc/self/fdinfo"
70 #define SAFE_DIR2 "/proc/self/fd"
72 static bool SpawnChrootHelper() {
74 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, sv
) == -1) {
80 struct stat sdir_stat
;
81 if (!stat(SAFE_DIR
, &sdir_stat
) && S_ISDIR(sdir_stat
.st_mode
))
84 if (!stat(SAFE_DIR2
, &sdir_stat
) && S_ISDIR(sdir_stat
.st_mode
))
87 fprintf(stderr
, "Could not find %s\n", SAFE_DIR2
);
91 const pid_t pid
= syscall(
92 __NR_clone
, CLONE_FS
| SIGCHLD
, 0, 0, 0);
102 // We share our files structure with an untrusted process. As a security in
103 // depth measure, we make sure that we can't open anything by mistake.
104 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT
106 const struct rlimit nofile
= {0, 0};
107 if (setrlimit(RLIMIT_NOFILE
, &nofile
))
108 FatalError("Setting RLIMIT_NOFILE");
117 bytes
= read(sv
[0], &msg
, 1);
118 } while (bytes
== -1 && errno
== EINTR
);
126 if (msg
!= kMsgChrootMe
)
127 FatalError("Unknown message from sandboxed process");
131 FatalError("Cannot chdir into /proc/ directory");
134 FatalError("Cannot chroot into /proc/ directory");
137 FatalError("Cannot chdir to / after chroot");
139 const char reply
= kMsgChrootSuccessful
;
141 bytes
= write(sv
[0], &reply
, 1);
142 } while (bytes
== -1 && errno
== EINTR
);
145 FatalError("Writing reply");
148 // We now become a zombie. /proc/self/fd(info) is now an empty dir and we
149 // are chrooted there.
150 // Our (unprivileged) parent should not even be able to open "." or "/"
151 // since they would need to pass the ptrace() check. If our parent wait()
152 // for us, our root directory will completely disappear.
161 // In the parent process, we install an environment variable containing the
162 // number of the file descriptor.
164 int printed
= snprintf(desc_str
, sizeof(desc_str
), "%u", sv
[1]);
165 if (printed
< 0 || printed
>= (int)sizeof(desc_str
)) {
166 fprintf(stderr
, "Failed to snprintf\n");
170 if (setenv(kSandboxDescriptorEnvironmentVarName
, desc_str
, 1)) {
176 // We also install an environment variable containing the pid of the child
177 char helper_pid_str
[64];
178 printed
= snprintf(helper_pid_str
, sizeof(helper_pid_str
), "%u", pid
);
179 if (printed
< 0 || printed
>= (int)sizeof(helper_pid_str
)) {
180 fprintf(stderr
, "Failed to snprintf\n");
184 if (setenv(kSandboxHelperPidEnvironmentVarName
, helper_pid_str
, 1)) {
193 // Block until child_pid exits, then exit. Try to preserve the exit code.
194 static void WaitForChildAndExit(pid_t child_pid
) {
196 siginfo_t reaped_child_info
;
199 HANDLE_EINTR(waitid(P_PID
, child_pid
, &reaped_child_info
, WEXITED
));
201 if (!wait_ret
&& reaped_child_info
.si_pid
== child_pid
) {
202 if (reaped_child_info
.si_code
== CLD_EXITED
) {
203 exit_code
= reaped_child_info
.si_status
;
205 // Exit with code 0 if the child got signaled.
212 static bool MoveToNewNamespaces() {
213 // These are the sets of flags which we'll try, in order.
214 const int kCloneExtraFlags
[] = {
215 CLONE_NEWPID
| CLONE_NEWNET
,
219 // We need to close kZygoteIdFd before the child can continue. We use this
220 // socketpair to tell the child when to continue;
222 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, sync_fds
)) {
223 FatalError("Failed to create a socketpair");
227 i
< sizeof(kCloneExtraFlags
) / sizeof(kCloneExtraFlags
[0]);
229 pid_t pid
= syscall(__NR_clone
, SIGCHLD
| kCloneExtraFlags
[i
], 0, 0, 0);
233 FatalError("Could not drop privileges");
235 if (close(sync_fds
[0]) || shutdown(sync_fds
[1], SHUT_RD
))
236 FatalError("Could not close socketpair");
237 // The kZygoteIdFd needs to be closed in the parent before
238 // Zygote gets started.
239 if (close(kZygoteIdFd
))
241 // Tell our child to continue
242 if (HANDLE_EINTR(send(sync_fds
[1], "C", 1, MSG_NOSIGNAL
)) != 1)
244 if (close(sync_fds
[1]))
246 // We want to keep a full process tree and we don't want our childs to
247 // be reparented to (the outer PID namespace) init. So we wait for it.
248 WaitForChildAndExit(pid
);
251 FatalError("Not reached");
255 if (close(sync_fds
[1]) || shutdown(sync_fds
[0], SHUT_WR
))
256 FatalError("Could not close socketpair");
258 // Wait for the parent to confirm it closed kZygoteIdFd before we
260 char should_continue
;
261 if (HANDLE_EINTR(read(sync_fds
[0], &should_continue
, 1)) != 1)
262 FatalError("Read on socketpair");
263 if (close(sync_fds
[0]))
266 if (kCloneExtraFlags
[i
] & CLONE_NEWPID
) {
267 setenv(kSandboxPIDNSEnvironmentVarName
, "", 1 /* overwrite */);
269 unsetenv(kSandboxPIDNSEnvironmentVarName
);
272 if (kCloneExtraFlags
[i
] & CLONE_NEWNET
) {
273 setenv(kSandboxNETNSEnvironmentVarName
, "", 1 /* overwrite */);
275 unsetenv(kSandboxNETNSEnvironmentVarName
);
281 if (errno
!= EINVAL
) {
282 perror("Failed to move to new PID namespace");
287 // If the system doesn't support NEWPID then we carry on anyway.
291 static bool DropRoot() {
292 if (prctl(PR_SET_DUMPABLE
, 0, 0, 0, 0)) {
293 perror("prctl(PR_SET_DUMPABLE)");
297 if (prctl(PR_GET_DUMPABLE
, 0, 0, 0, 0)) {
298 perror("Still dumpable after prctl(PR_SET_DUMPABLE)");
302 gid_t rgid
, egid
, sgid
;
303 if (getresgid(&rgid
, &egid
, &sgid
)) {
308 if (setresgid(rgid
, rgid
, rgid
)) {
313 uid_t ruid
, euid
, suid
;
314 if (getresuid(&ruid
, &euid
, &suid
)) {
319 if (setresuid(ruid
, ruid
, ruid
)) {
327 static bool SetupChildEnvironment() {
330 // ld.so may have cleared several environment variables because we are SUID.
331 // However, the child process might need them so zygote_host_linux.cc saves a
332 // copy in SANDBOX_$x. This is safe because we have dropped root by this
333 // point, so we can only exec a binary with the permissions of the user who
334 // ran us in the first place.
336 for (i
= 0; kSUIDUnsafeEnvironmentVariables
[i
]; ++i
) {
337 const char* const envvar
= kSUIDUnsafeEnvironmentVariables
[i
];
338 char* const saved_envvar
= SandboxSavedEnvironmentVariable(envvar
);
342 const char* const value
= getenv(saved_envvar
);
344 setenv(envvar
, value
, 1 /* overwrite */);
345 unsetenv(saved_envvar
);
354 bool CheckAndExportApiVersion() {
355 // Check the environment to see if a specific API version was requested.
356 // assume version 0 if none.
357 long api_number
= -1;
358 char *api_string
= getenv(kSandboxEnvironmentApiRequest
);
364 api_number
= strtol(api_string
, &endptr
, 10);
365 if (!endptr
|| *endptr
|| errno
!= 0)
369 // Warn only for now.
370 if (api_number
!= kSUIDSandboxApiNumber
) {
371 fprintf(stderr
, "The setuid sandbox provides API version %ld, "
374 "https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment."
376 kSUIDSandboxApiNumber
,
380 // Export our version so that the sandboxed process can verify it did not
381 // use an old sandbox.
382 char version_string
[64];
383 snprintf(version_string
, sizeof(version_string
), "%ld",
384 kSUIDSandboxApiNumber
);
385 if (setenv(kSandboxEnvironmentApiProvides
, version_string
, 1)) {
393 int main(int argc
, char **argv
) {
399 fprintf(stderr
, "Usage: %s <renderer process> <args...>\n", argv
[0]);
403 // Allow someone to query our API version
404 if (argc
== 2 && 0 == strcmp(argv
[1], kSuidSandboxGetApiSwitch
)) {
405 printf("%ld\n", kSUIDSandboxApiNumber
);
409 // In the SUID sandbox, if we succeed in calling MoveToNewNamespaces()
410 // below, then the zygote and all the renderers are in an alternate PID
411 // namespace and do not know their real PIDs. As such, they report the wrong
412 // PIDs to the task manager.
414 // To fix this, when the zygote spawns a new renderer, it gives the renderer
415 // a dummy socket, which has a unique inode number. Then it asks the sandbox
416 // host to find the PID of the process holding that fd by searching /proc.
418 // Since the zygote and renderers are all spawned by this setuid executable,
419 // their entries in /proc are owned by root and only readable by root. In
420 // order to search /proc for the fd we want, this setuid executable has to
421 // double as a helper and perform the search. The code block below does this
422 // when you call it with --find-inode INODE_NUMBER.
423 if (argc
== 3 && (0 == strcmp(argv
[1], kFindInodeSwitch
))) {
427 ino_t inode
= strtoull(argv
[2], &endptr
, 10);
428 if (inode
== ULLONG_MAX
|| !endptr
|| *endptr
|| errno
!= 0)
430 if (!FindProcessHoldingSocket(&pid
, inode
))
435 // Likewise, we cannot adjust /proc/pid/oom_adj for sandboxed renderers
436 // because those files are owned by root. So we need another 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
||
449 !endptr
|| *endptr
|| errno
!= 0)
451 return AdjustOOMScore(pid
, score
);
454 // Protect the core setuid sandbox functionality with an API version
455 if (!CheckAndExportApiVersion()) {
459 if (!MoveToNewNamespaces())
461 if (!SpawnChrootHelper())
465 if (!SetupChildEnvironment())
468 execv(argv
[1], &argv
[1]);
469 FatalError("execv failed");