int main(int argc, char **argv) { if (argc <= 1) { fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]); return EXIT_FAILURE; } if (MoveToNewPIDNamespace()) return EXIT_FAILURE; if (SpawnChrootHelper()) return EXIT_FAILURE; if (DropRoot()) return EXIT_FAILURE; if (SetupChildEnvironment()) return EXIT_FAILURE; execv(argv[1], &argv[1]); _err(EXIT_FAILURE, "execv failed"); return EXIT_FAILURE; }
int main(int argc, char** argv) { if (argc <= 1) { if (argc <= 0) { return 1; } fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]); return 1; } // Allow someone to query our API version if (argc == 2 && 0 == strcmp(argv[1], kSuidSandboxGetApiSwitch)) { printf("%ld\n", kSUIDSandboxApiNumber); return 0; } // We cannot adjust /proc/pid/oom_adj for sandboxed renderers // because those files are owned by root. So we need a helper here. if (argc == 4 && (0 == strcmp(argv[1], kAdjustOOMScoreSwitch))) { char* endptr = NULL; long score; errno = 0; unsigned long pid_ul = strtoul(argv[2], &endptr, 10); if (pid_ul == ULONG_MAX || !endptr || *endptr || errno != 0) return 1; pid_t pid = pid_ul; endptr = NULL; errno = 0; score = strtol(argv[3], &endptr, 10); if (score == LONG_MAX || score == LONG_MIN || !endptr || *endptr || errno != 0) { return 1; } return AdjustOOMScore(pid, score); } // Protect the core setuid sandbox functionality with an API version if (!CheckAndExportApiVersion()) { return 1; } if (geteuid() != 0) { fprintf(stderr, "The setuid sandbox is not running as root. Common causes:\n" " * An unprivileged process using ptrace on it, like a debugger.\n" " * A parent process set prctl(PR_SET_NO_NEW_PRIVS, ...)\n"); } if (!MoveToNewNamespaces()) return 1; if (!SpawnChrootHelper()) return 1; if (!DropRoot()) return 1; if (!SetupChildEnvironment()) return 1; execv(argv[1], &argv[1]); FatalError("execv failed"); return 1; }
static bool MoveToNewNamespaces() { // These are the sets of flags which we'll try, in order. const int kCloneExtraFlags[] = {CLONE_NEWPID | CLONE_NEWNET, CLONE_NEWPID, }; // We need to close kZygoteIdFd before the child can continue. We use this // socketpair to tell the child when to continue; int sync_fds[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_fds)) { FatalError("Failed to create a socketpair"); } for (size_t i = 0; i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); i++) { pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); const int clone_errno = errno; if (pid > 0) { if (!DropRoot()) { FatalError("Could not drop privileges"); } else { if (close(sync_fds[0]) || shutdown(sync_fds[1], SHUT_RD)) FatalError("Could not close socketpair"); // The kZygoteIdFd needs to be closed in the parent before // Zygote gets started. if (close(kZygoteIdFd)) FatalError("close"); // Tell our child to continue if (HANDLE_EINTR(send(sync_fds[1], "C", 1, MSG_NOSIGNAL)) != 1) FatalError("send"); if (close(sync_fds[1])) FatalError("close"); // We want to keep a full process tree and we don't want our childs to // be reparented to (the outer PID namespace) init. So we wait for it. WaitForChildAndExit(pid); } // NOTREACHED FatalError("Not reached"); } if (pid == 0) { if (close(sync_fds[1]) || shutdown(sync_fds[0], SHUT_WR)) FatalError("Could not close socketpair"); // Wait for the parent to confirm it closed kZygoteIdFd before we // continue char should_continue; if (HANDLE_EINTR(read(sync_fds[0], &should_continue, 1)) != 1) FatalError("Read on socketpair"); if (close(sync_fds[0])) FatalError("close"); if (kCloneExtraFlags[i] & CLONE_NEWPID) { setenv(kSandboxPIDNSEnvironmentVarName, "", 1 /* overwrite */); } else { unsetenv(kSandboxPIDNSEnvironmentVarName); } if (kCloneExtraFlags[i] & CLONE_NEWNET) { setenv(kSandboxNETNSEnvironmentVarName, "", 1 /* overwrite */); } else { unsetenv(kSandboxNETNSEnvironmentVarName); } break; } // If EINVAL then the system doesn't support the requested flags, so // continue to try a different set. // On any other errno value the system *does* support these flags but // something went wrong, hence we bail with an error message rather then // provide less security. if (errno != EINVAL) { fprintf(stderr, "Failed to move to new namespace:"); if (kCloneExtraFlags[i] & CLONE_NEWPID) { fprintf(stderr, " PID namespaces supported,"); } if (kCloneExtraFlags[i] & CLONE_NEWNET) { fprintf(stderr, " Network namespace supported,"); } fprintf(stderr, " but failed: errno = %s\n", strerror(clone_errno)); return false; } } // If the system doesn't support NEWPID then we carry on anyway. return true; }
int main(int argc, char **argv) { if (argc <= 1) { if (argc <= 0) { return 1; } fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]); return 1; } // Allow someone to query our API version if (argc == 2 && 0 == strcmp(argv[1], kSuidSandboxGetApiSwitch)) { printf("%ld\n", kSUIDSandboxApiNumber); return 0; } // In the SUID sandbox, if we succeed in calling MoveToNewNamespaces() // below, then the zygote and all the renderers are in an alternate PID // namespace and do not know their real PIDs. As such, they report the wrong // PIDs to the task manager. // // To fix this, when the zygote spawns a new renderer, it gives the renderer // a dummy socket, which has a unique inode number. Then it asks the sandbox // host to find the PID of the process holding that fd by searching /proc. // // Since the zygote and renderers are all spawned by this setuid executable, // their entries in /proc are owned by root and only readable by root. In // order to search /proc for the fd we want, this setuid executable has to // double as a helper and perform the search. The code block below does this // when you call it with --find-inode INODE_NUMBER. if (argc == 3 && (0 == strcmp(argv[1], kFindInodeSwitch))) { pid_t pid; char* endptr = NULL; errno = 0; ino_t inode = strtoull(argv[2], &endptr, 10); if (inode == ULLONG_MAX || !endptr || *endptr || errno != 0) return 1; if (!FindProcessHoldingSocket(&pid, inode)) return 1; printf("%d\n", pid); return 0; } // Likewise, we cannot adjust /proc/pid/oom_adj for sandboxed renderers // because those files are owned by root. So we need another helper here. if (argc == 4 && (0 == strcmp(argv[1], kAdjustOOMScoreSwitch))) { char* endptr = NULL; long score; errno = 0; unsigned long pid_ul = strtoul(argv[2], &endptr, 10); if (pid_ul == ULONG_MAX || !endptr || *endptr || errno != 0) return 1; pid_t pid = pid_ul; endptr = NULL; errno = 0; score = strtol(argv[3], &endptr, 10); if (score == LONG_MAX || score == LONG_MIN || !endptr || *endptr || errno != 0) return 1; return AdjustOOMScore(pid, score); } #if defined(OS_CHROMEOS) if (argc == 3 && (0 == strcmp(argv[1], kAdjustLowMemMarginSwitch))) { char* endptr = NULL; errno = 0; unsigned long margin_mb = strtoul(argv[2], &endptr, 10); if (!endptr || *endptr || errno != 0) return 1; return AdjustLowMemoryMargin(margin_mb); } #endif // Protect the core setuid sandbox functionality with an API version if (!CheckAndExportApiVersion()) { return 1; } if (!MoveToNewNamespaces()) return 1; if (!SpawnChrootHelper()) return 1; if (!DropRoot()) return 1; if (!SetupChildEnvironment()) return 1; execv(argv[1], &argv[1]); FatalError("execv failed"); return 1; }
static bool MoveToNewNamespaces() { // These are the sets of flags which we'll try, in order. const int kCloneExtraFlags[] = { CLONE_NEWPID | CLONE_NEWNET, CLONE_NEWPID, }; // We need to close kZygoteIdFd before the child can continue. We use this // socketpair to tell the child when to continue; int sync_fds[2]; if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_fds)) { FatalError("Failed to create a socketpair"); } for (size_t i = 0; i < sizeof(kCloneExtraFlags) / sizeof(kCloneExtraFlags[0]); i++) { pid_t pid = syscall(__NR_clone, SIGCHLD | kCloneExtraFlags[i], 0, 0, 0); if (pid > 0) { if (!DropRoot()) { FatalError("Could not drop privileges"); } else { if (close(sync_fds[0]) || shutdown(sync_fds[1], SHUT_RD)) FatalError("Could not close socketpair"); // The kZygoteIdFd needs to be closed in the parent before // Zygote gets started. if (close(kZygoteIdFd)) FatalError("close"); // Tell our child to continue if (HANDLE_EINTR(send(sync_fds[1], "C", 1, MSG_NOSIGNAL)) != 1) FatalError("send"); if (close(sync_fds[1])) FatalError("close"); // We want to keep a full process tree and we don't want our childs to // be reparented to (the outer PID namespace) init. So we wait for it. WaitForChildAndExit(pid); } // NOTREACHED FatalError("Not reached"); } if (pid == 0) { if (close(sync_fds[1]) || shutdown(sync_fds[0], SHUT_WR)) FatalError("Could not close socketpair"); // Wait for the parent to confirm it closed kZygoteIdFd before we // continue char should_continue; if (HANDLE_EINTR(read(sync_fds[0], &should_continue, 1)) != 1) FatalError("Read on socketpair"); if (close(sync_fds[0])) FatalError("close"); if (kCloneExtraFlags[i] & CLONE_NEWPID) { setenv("SBX_PID_NS", "", 1 /* overwrite */); } else { unsetenv("SBX_PID_NS"); } if (kCloneExtraFlags[i] & CLONE_NEWNET) { setenv("SBX_NET_NS", "", 1 /* overwrite */); } else { unsetenv("SBX_NET_NS"); } break; } if (errno != EINVAL) { perror("Failed to move to new PID namespace"); return false; } } // If the system doesn't support NEWPID then we carry on anyway. return true; }