Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | 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 | 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> |
| (...skipping 19 matching lines...) Expand all Loading... | |
| 30 #include "process_util.h" | 30 #include "process_util.h" |
| 31 #include "suid_unsafe_environment_variables.h" | 31 #include "suid_unsafe_environment_variables.h" |
| 32 | 32 |
| 33 #if !defined(CLONE_NEWPID) | 33 #if !defined(CLONE_NEWPID) |
| 34 #define CLONE_NEWPID 0x20000000 | 34 #define CLONE_NEWPID 0x20000000 |
| 35 #endif | 35 #endif |
| 36 #if !defined(CLONE_NEWNET) | 36 #if !defined(CLONE_NEWNET) |
| 37 #define CLONE_NEWNET 0x40000000 | 37 #define CLONE_NEWNET 0x40000000 |
| 38 #endif | 38 #endif |
| 39 | 39 |
| 40 #if !defined(BTRFS_SUPER_MAGIC) | |
| 41 #define BTRFS_SUPER_MAGIC 0x9123683E | |
| 42 #endif | |
| 43 #if !defined(EXT2_SUPER_MAGIC) | |
| 44 #define EXT2_SUPER_MAGIC 0xEF53 | |
| 45 #endif | |
| 46 #if !defined(EXT3_SUPER_MAGIC) | |
| 47 #define EXT3_SUPER_MAGIC 0xEF53 | |
| 48 #endif | |
| 49 #if !defined(EXT4_SUPER_MAGIC) | |
| 50 #define EXT4_SUPER_MAGIC 0xEF53 | |
| 51 #endif | |
| 52 #if !defined(REISERFS_SUPER_MAGIC) | |
| 53 #define REISERFS_SUPER_MAGIC 0x52654973 | |
| 54 #endif | |
| 55 #if !defined(TMPFS_MAGIC) | |
| 56 #define TMPFS_MAGIC 0x01021994 | |
| 57 #endif | |
| 58 #if !defined(XFS_SUPER_MAGIC) | |
| 59 #define XFS_SUPER_MAGIC 0x58465342 | |
| 60 #endif | |
| 61 | |
| 62 static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D"; | 40 static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D"; |
| 41 static const char kSandboxHelperPidEnvironmentVarName[] = "SBX_HELPER_PID"; | |
| 63 | 42 |
| 64 // These are the magic byte values which the sandboxed process uses to request | 43 // These are the magic byte values which the sandboxed process uses to request |
| 65 // that it be chrooted. | 44 // that it be chrooted. |
| 66 static const char kMsgChrootMe = 'C'; | 45 static const char kMsgChrootMe = 'C'; |
| 67 static const char kMsgChrootSuccessful = 'O'; | 46 static const char kMsgChrootSuccessful = 'O'; |
| 68 | 47 |
| 69 static void FatalError(const char *msg, ...) | 48 static void FatalError(const char *msg, ...) |
| 70 __attribute__((noreturn, format(printf, 1, 2))); | 49 __attribute__((noreturn, format(printf, 1, 2))); |
| 71 | 50 |
| 72 static void FatalError(const char *msg, ...) { | 51 static void FatalError(const char *msg, ...) { |
| 73 va_list ap; | 52 va_list ap; |
| 74 va_start(ap, msg); | 53 va_start(ap, msg); |
| 75 | 54 |
| 76 vfprintf(stderr, msg, ap); | 55 vfprintf(stderr, msg, ap); |
| 77 fprintf(stderr, ": %s\n", strerror(errno)); | 56 fprintf(stderr, ": %s\n", strerror(errno)); |
| 78 fflush(stderr); | 57 fflush(stderr); |
| 79 exit(1); | 58 exit(1); |
| 80 } | 59 } |
| 81 | 60 |
| 82 static int CloneChrootHelperProcess() { | 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. | |
| 63 // | |
| 64 // /proc/self/fdinfo or /proc/self/fd are especially safe and will be empty | |
| 65 // even if the helper survives as a zombie. | |
| 66 // | |
| 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" | |
|
Markus (顧孟勤)
2011/03/24 23:26:58
Do we ever need to worry about nesting of setuid s
| |
| 71 | |
| 72 static bool SpawnChrootHelper() { | |
| 83 int sv[2]; | 73 int sv[2]; |
| 84 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { | 74 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { |
| 85 perror("socketpair"); | 75 perror("socketpair"); |
| 86 return -1; | 76 return false; |
| 87 } | 77 } |
| 88 | 78 |
| 89 // Some people mount /tmp on a non-POSIX filesystem (e.g. NFS). This | 79 char *safedir = NULL; |
|
agl
2011/03/25 14:05:21
double space.
| |
| 90 // breaks all sorts of assumption in our code. So, if we don't recognize the | 80 struct stat sdir_stat; |
| 91 // filesystem, we will try to use an alternative location for our temp | 81 if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) |
| 92 // directory. | 82 safedir = SAFE_DIR; |
| 93 char tempDirectoryTemplate[80] = "/tmp/chrome-sandbox-chroot-XXXXXX"; | 83 else |
| 94 struct statfs sfs; | 84 if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) |
| 95 if (!statfs("/tmp", &sfs) && | 85 safedir = SAFE_DIR2; |
| 96 (unsigned long)sfs.f_type != BTRFS_SUPER_MAGIC && | 86 else { |
| 97 (unsigned long)sfs.f_type != EXT2_SUPER_MAGIC && | 87 fprintf(stderr, "Could not find %s\n", SAFE_DIR2); |
| 98 (unsigned long)sfs.f_type != EXT3_SUPER_MAGIC && | 88 return false; |
| 99 (unsigned long)sfs.f_type != EXT4_SUPER_MAGIC && | |
| 100 (unsigned long)sfs.f_type != REISERFS_SUPER_MAGIC && | |
| 101 (unsigned long)sfs.f_type != TMPFS_MAGIC && | |
| 102 (unsigned long)sfs.f_type != XFS_SUPER_MAGIC) { | |
| 103 // If /dev/shm exists, it is supposed to be a tmpfs filesystem. While we | |
| 104 // are not actually using it for shared memory, moving our temp directory | |
| 105 // into a known tmpfs filesystem is preferable over using a potentially | |
| 106 // unreliable non-POSIX filesystem. | |
| 107 if (!statfs("/dev/shm", &sfs) && sfs.f_type == TMPFS_MAGIC) { | |
| 108 *tempDirectoryTemplate = '\000'; | |
| 109 strncat(tempDirectoryTemplate, "/dev/shm/chrome-sandbox-chroot-XXXXXX", | |
| 110 sizeof(tempDirectoryTemplate) - 1); | |
| 111 } else { | |
| 112 // Neither /tmp is a well-known POSIX filesystem, nor /dev/shm is a | |
| 113 // tmpfs. After all, we now use /tmp as the location of our temp | |
| 114 // directory, but we quite likely fail the moment we try to access it | |
| 115 // through chroot_dir_fd. If so, we will print a verbose error message | |
| 116 // (see below) | |
| 117 } | 89 } |
| 118 } | |
| 119 | |
| 120 // We create a temp directory for our chroot. Nobody should ever write into | |
| 121 // it, so it's root:root mode 000. | |
| 122 const char* temp_dir = mkdtemp(tempDirectoryTemplate); | |
| 123 if (!temp_dir) { | |
| 124 perror("Failed to create temp directory for chroot"); | |
| 125 return -1; | |
| 126 } | |
| 127 | |
| 128 const int chroot_dir_fd = open(temp_dir, O_DIRECTORY | O_RDONLY); | |
| 129 if (chroot_dir_fd < 0) { | |
| 130 rmdir(temp_dir); | |
| 131 perror("Failed to open chroot temp directory"); | |
| 132 return -1; | |
| 133 } | |
| 134 | |
| 135 if (rmdir(temp_dir)) { | |
| 136 perror("rmdir"); | |
| 137 return -1; | |
| 138 } | |
| 139 | |
| 140 char proc_self_fd_str[128]; | |
| 141 int printed = snprintf(proc_self_fd_str, sizeof(proc_self_fd_str), | |
| 142 "/proc/self/fd/%d", chroot_dir_fd); | |
| 143 if (printed < 0 || printed >= (int)sizeof(proc_self_fd_str)) { | |
| 144 fprintf(stderr, "Error in snprintf"); | |
| 145 return -1; | |
| 146 } | |
| 147 | |
| 148 if (fchown(chroot_dir_fd, 0 /* root */, 0 /* root */)) { | |
| 149 fprintf(stderr, "Could not set up sandbox work directory. Maybe, /tmp is " | |
| 150 "a non-POSIX filesystem and /dev/shm doesn't exist " | |
| 151 "either. Consider mounting a \"tmpfs\" on /tmp.\n"); | |
| 152 return -1; | |
| 153 } | |
| 154 | |
| 155 if (fchmod(chroot_dir_fd, 0000 /* no-access */)) { | |
| 156 perror("fchmod"); | |
| 157 return -1; | |
| 158 } | |
| 159 | |
| 160 | 90 |
| 161 const pid_t pid = syscall( | 91 const pid_t pid = syscall( |
| 162 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); | 92 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); |
| 163 | 93 |
| 164 if (pid == -1) { | 94 if (pid == -1) { |
| 165 perror("clone"); | 95 perror("clone"); |
| 166 close(sv[0]); | 96 close(sv[0]); |
| 167 close(sv[1]); | 97 close(sv[1]); |
| 168 return -1; | 98 return false; |
| 169 } | 99 } |
| 170 | 100 |
| 171 if (pid == 0) { | 101 if (pid == 0) { |
| 172 // We share our files structure with an untrusted process. As a security in | 102 // We share our files structure with an untrusted process. As a security in |
| 173 // depth measure, we make sure that we can't open anything by mistake. | 103 // depth measure, we make sure that we can't open anything by mistake. |
| 174 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT | 104 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT |
| 175 | 105 |
| 176 const struct rlimit nofile = {0, 0}; | 106 const struct rlimit nofile = {0, 0}; |
| 177 if (setrlimit(RLIMIT_NOFILE, &nofile)) | 107 if (setrlimit(RLIMIT_NOFILE, &nofile)) |
| 178 FatalError("Setting RLIMIT_NOFILE"); | 108 FatalError("Setting RLIMIT_NOFILE"); |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 189 | 119 |
| 190 if (bytes == 0) | 120 if (bytes == 0) |
| 191 _exit(0); | 121 _exit(0); |
| 192 if (bytes != 1) | 122 if (bytes != 1) |
| 193 FatalError("read"); | 123 FatalError("read"); |
| 194 | 124 |
| 195 // do chrooting | 125 // do chrooting |
| 196 if (msg != kMsgChrootMe) | 126 if (msg != kMsgChrootMe) |
| 197 FatalError("Unknown message from sandboxed process"); | 127 FatalError("Unknown message from sandboxed process"); |
| 198 | 128 |
| 199 if (fchdir(chroot_dir_fd)) | 129 // sanity check |
| 200 FatalError("Cannot chdir into chroot temp directory"); | 130 if (chdir(safedir)) |
| 131 FatalError("Cannot chdir into /proc/ directory"); | |
| 201 | 132 |
| 202 struct stat st; | 133 if (chroot(safedir)) |
| 203 if (fstat(chroot_dir_fd, &st)) | 134 FatalError("Cannot chroot into /proc/ directory"); |
| 204 FatalError("stat"); | |
| 205 | |
| 206 if (st.st_uid || st.st_gid || st.st_mode & 0777) | |
| 207 FatalError("Bad permissions on chroot temp directory"); | |
| 208 | |
| 209 if (chroot(proc_self_fd_str)) | |
| 210 FatalError("Cannot chroot into temp directory"); | |
| 211 | 135 |
| 212 if (chdir("/")) | 136 if (chdir("/")) |
| 213 FatalError("Cannot chdir to / after chroot"); | 137 FatalError("Cannot chdir to / after chroot"); |
| 214 | 138 |
| 215 const char reply = kMsgChrootSuccessful; | 139 const char reply = kMsgChrootSuccessful; |
| 216 do { | 140 do { |
| 217 bytes = write(sv[0], &reply, 1); | 141 bytes = write(sv[0], &reply, 1); |
| 218 } while (bytes == -1 && errno == EINTR); | 142 } while (bytes == -1 && errno == EINTR); |
| 219 | 143 |
| 220 if (bytes != 1) | 144 if (bytes != 1) |
| 221 FatalError("Writing reply"); | 145 FatalError("Writing reply"); |
| 222 | 146 |
| 223 _exit(0); | 147 _exit(0); |
| 224 } | 148 // We now become a zombie. /proc/self/fd(info) is now an empty dir and we |
| 225 | 149 // are chrooted there. |
| 226 if (close(chroot_dir_fd)) { | 150 // Our (unprivileged) parent should not even be able to open "." or "/" |
| 227 close(sv[0]); | 151 // since they would need to pass the ptrace() check. If our parent wait() |
| 228 close(sv[1]); | 152 // for us, our root directory will completely disappear. |
| 229 perror("close(chroot_dir_fd)"); | |
| 230 return false; | |
| 231 } | 153 } |
| 232 | 154 |
| 233 if (close(sv[0])) { | 155 if (close(sv[0])) { |
| 234 close(sv[1]); | 156 close(sv[1]); |
| 235 perror("close"); | 157 perror("close"); |
| 236 return false; | 158 return false; |
| 237 } | 159 } |
| 238 | 160 |
| 239 return sv[1]; | |
| 240 } | |
| 241 | |
| 242 static bool SpawnChrootHelper() { | |
| 243 const int chroot_signal_fd = CloneChrootHelperProcess(); | |
| 244 | |
| 245 if (chroot_signal_fd == -1) | |
| 246 return false; | |
| 247 | |
| 248 // In the parent process, we install an environment variable containing the | 161 // In the parent process, we install an environment variable containing the |
| 249 // number of the file descriptor. | 162 // number of the file descriptor. |
| 250 char desc_str[64]; | 163 char desc_str[64]; |
| 251 int printed = snprintf(desc_str, sizeof(desc_str), "%d", chroot_signal_fd); | 164 int printed = snprintf(desc_str, sizeof(desc_str), "%u", sv[1]); |
| 252 if (printed < 0 || printed >= (int)sizeof(desc_str)) { | 165 if (printed < 0 || printed >= (int)sizeof(desc_str)) { |
| 253 fprintf(stderr, "Failed to snprintf\n"); | 166 fprintf(stderr, "Failed to snprintf\n"); |
| 254 return false; | 167 return false; |
| 255 } | 168 } |
| 256 | 169 |
| 257 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { | 170 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { |
| 258 perror("setenv"); | 171 perror("setenv"); |
| 259 close(chroot_signal_fd); | 172 close(sv[1]); |
| 173 return false; | |
| 174 } | |
| 175 | |
| 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"); | |
| 181 return false; | |
| 182 } | |
| 183 | |
| 184 if (setenv(kSandboxHelperPidEnvironmentVarName, helper_pid_str, 1)) { | |
| 185 perror("setenv"); | |
| 186 close(sv[1]); | |
| 260 return false; | 187 return false; |
| 261 } | 188 } |
| 262 | 189 |
| 263 return true; | 190 return true; |
| 264 } | 191 } |
| 265 | 192 |
| 266 static bool MoveToNewNamespaces() { | 193 static bool MoveToNewNamespaces() { |
| 267 // These are the sets of flags which we'll try, in order. | 194 // These are the sets of flags which we'll try, in order. |
| 268 const int kCloneExtraFlags[] = { | 195 const int kCloneExtraFlags[] = { |
| 269 CLONE_NEWPID | CLONE_NEWNET, | 196 CLONE_NEWPID | CLONE_NEWNET, |
| (...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 420 if (!DropRoot()) | 347 if (!DropRoot()) |
| 421 return 1; | 348 return 1; |
| 422 if (!SetupChildEnvironment()) | 349 if (!SetupChildEnvironment()) |
| 423 return 1; | 350 return 1; |
| 424 | 351 |
| 425 execv(argv[1], &argv[1]); | 352 execv(argv[1], &argv[1]); |
| 426 FatalError("execv failed"); | 353 FatalError("execv failed"); |
| 427 | 354 |
| 428 return 1; | 355 return 1; |
| 429 } | 356 } |
| OLD | NEW |