OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2009 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. |
| 4 |
| 5 #include <asm/unistd.h> |
| 6 #include <errno.h> |
| 7 #include <sched.h> |
| 8 #include <signal.h> |
| 9 #include <stdarg.h> |
| 10 #include <stdio.h> |
| 11 #include <stdlib.h> |
| 12 #include <string.h> |
| 13 #include <sys/prctl.h> |
| 14 #include <sys/resource.h> |
| 15 #include <sys/socket.h> |
| 16 #include <sys/stat.h> |
| 17 #include <sys/time.h> |
| 18 #include <sys/types.h> |
| 19 #include <unistd.h> |
| 20 |
| 21 #if !defined(CLONE_NEWPID) |
| 22 #define CLONE_NEWPID 20000000 |
| 23 #endif |
| 24 |
| 25 static const char kSandboxPath[] = "/var/run/chrome-sandbox"; |
| 26 static const char kChromeBinary[] = "/opt/google/chrome/chrome"; |
| 27 |
| 28 // These are the magic byte values which the sandboxed process uses to request |
| 29 // that it be chrooted. |
| 30 static const char kMsgChrootMe = 'C'; |
| 31 static const char kMsgChrootSuccessful = 'O'; |
| 32 |
| 33 static void FatalError(const char *msg, ...) { |
| 34 va_list ap; |
| 35 va_start(ap, msg); |
| 36 |
| 37 vfprintf(stderr, msg, ap); |
| 38 fprintf(stderr, ": %s\n", strerror(errno)); |
| 39 fflush(stderr); |
| 40 _exit(1); |
| 41 } |
| 42 |
| 43 static int CloneChrootHelperProcess() { |
| 44 int sv[2]; |
| 45 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { |
| 46 perror("socketpair"); |
| 47 return -1; |
| 48 } |
| 49 |
| 50 const pid_t pid = syscall( |
| 51 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); |
| 52 |
| 53 if (pid == -1) { |
| 54 perror("clone"); |
| 55 close(sv[0]); |
| 56 close(sv[1]); |
| 57 return -1; |
| 58 } |
| 59 |
| 60 if (pid == 0) { |
| 61 // We share our files structure with an untrusted process. As a security in |
| 62 // depth measure, we make sure that we can't open anything by mistake. |
| 63 // TODO: drop CAP_SYS_RESOURCE |
| 64 |
| 65 const struct rlimit nofile = {0, 0}; |
| 66 if (setrlimit(RLIMIT_NOFILE, &nofile)) |
| 67 FatalError("Setting RLIMIT_NOFILE"); |
| 68 |
| 69 if (close(sv[1])) |
| 70 FatalError("close"); |
| 71 |
| 72 char msg; |
| 73 ssize_t bytes; |
| 74 do { |
| 75 bytes = read(sv[0], &msg, 1); |
| 76 } while (bytes == -1 && errno == EINTR); |
| 77 |
| 78 if (bytes == 0) |
| 79 exit(0); |
| 80 if (bytes != 1) |
| 81 FatalError("read"); |
| 82 |
| 83 if (msg != kMsgChrootMe) |
| 84 FatalError("Unknown message from sandboxed process"); |
| 85 |
| 86 if (chdir(kSandboxPath)) |
| 87 FatalError("Cannot chdir into %s", kSandboxPath); |
| 88 |
| 89 struct stat st; |
| 90 if (stat("/", &st)) |
| 91 FatalError("stat"); |
| 92 |
| 93 if (st.st_uid || st.st_gid || st.st_mode & S_IWOTH) |
| 94 FatalError("Bad permissions on chroot directory (%s)", kSandboxPath); |
| 95 |
| 96 if (chroot(kSandboxPath)) |
| 97 FatalError("Cannot chroot into %s", kSandboxPath); |
| 98 |
| 99 if (chdir("/")) |
| 100 FatalError("Cannot chdir to / after chroot"); |
| 101 |
| 102 const char reply = kMsgChrootSuccessful; |
| 103 do { |
| 104 bytes = write(sv[0], &reply, 1); |
| 105 } while (bytes == -1 && errno == EINTR); |
| 106 |
| 107 if (bytes != 1) |
| 108 FatalError("Writing reply"); |
| 109 |
| 110 exit(0); |
| 111 } |
| 112 |
| 113 if (close(sv[0])) { |
| 114 close(sv[1]); |
| 115 perror("close"); |
| 116 return false; |
| 117 } |
| 118 |
| 119 return sv[1]; |
| 120 } |
| 121 |
| 122 static bool SpawnChrootHelper() { |
| 123 const int chroot_signal_fd = CloneChrootHelperProcess(); |
| 124 |
| 125 if (chroot_signal_fd == -1) |
| 126 return false; |
| 127 |
| 128 // In the parent process, we install an environment variable containing the |
| 129 // number of the file descriptor. |
| 130 char desc_str[64]; |
| 131 snprintf(desc_str, sizeof(desc_str), "%d", chroot_signal_fd); |
| 132 |
| 133 if (setenv("SBX_D", desc_str, 1)) { |
| 134 perror("setenv"); |
| 135 close(chroot_signal_fd); |
| 136 return false; |
| 137 } |
| 138 |
| 139 return true; |
| 140 } |
| 141 |
| 142 static bool MoveToNewPIDNamespace() { |
| 143 const pid_t pid = syscall( |
| 144 __NR_clone, CLONE_NEWPID | SIGCHLD, 0, 0, 0); |
| 145 |
| 146 if (pid == -1) |
| 147 return false; |
| 148 |
| 149 if (pid) |
| 150 syscall(__NR_exit, 0); |
| 151 |
| 152 return true; |
| 153 } |
| 154 |
| 155 static bool DropRoot() { |
| 156 if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0)) { |
| 157 perror("prctl(PR_SET_DUMPABLE)"); |
| 158 return false; |
| 159 } |
| 160 |
| 161 if (prctl(PR_GET_DUMPABLE, 0, 0, 0, 0)) { |
| 162 perror("Still dumpable after prctl(PR_SET_DUMPABLE)"); |
| 163 return false; |
| 164 } |
| 165 |
| 166 gid_t rgid, egid, sgid; |
| 167 if (getresgid(&rgid, &egid, &sgid)) { |
| 168 perror("getresgid"); |
| 169 return false; |
| 170 } |
| 171 |
| 172 if (setresgid(rgid, rgid, rgid)) { |
| 173 perror("setresgid"); |
| 174 return false; |
| 175 } |
| 176 |
| 177 uid_t ruid, euid, suid; |
| 178 if (getresuid(&ruid, &euid, &suid)) { |
| 179 perror("getresuid"); |
| 180 return false; |
| 181 } |
| 182 |
| 183 if (setresuid(ruid, ruid, ruid)) { |
| 184 perror("setresuid"); |
| 185 return false; |
| 186 } |
| 187 |
| 188 return true; |
| 189 } |
| 190 |
| 191 int main(int argc, char **argv) { |
| 192 if (argc == 1) { |
| 193 fprintf(stderr, "Usage: %s <renderer process> <args...>\n", argv[0]); |
| 194 return 1; |
| 195 } |
| 196 |
| 197 if (strcmp(argv[1], kChromeBinary)) { |
| 198 fprintf(stderr, "This wrapper can only run %s!\n", kChromeBinary); |
| 199 return 1; |
| 200 } |
| 201 |
| 202 if (!MoveToNewPIDNamespace()) |
| 203 return 1; |
| 204 if (!SpawnChrootHelper()) |
| 205 return 1; |
| 206 if (!DropRoot()) |
| 207 return 1; |
| 208 |
| 209 execv(argv[1], &argv[1]); |
| 210 FatalError("execv failed"); |
| 211 |
| 212 return 1; |
| 213 } |
OLD | NEW |