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 |
40 static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D"; | 62 static const char kSandboxDescriptorEnvironmentVarName[] = "SBX_D"; |
41 static const char kSandboxHelperPidEnvironmentVarName[] = "SBX_HELPER_PID"; | |
42 | 63 |
43 // These are the magic byte values which the sandboxed process uses to request | 64 // These are the magic byte values which the sandboxed process uses to request |
44 // that it be chrooted. | 65 // that it be chrooted. |
45 static const char kMsgChrootMe = 'C'; | 66 static const char kMsgChrootMe = 'C'; |
46 static const char kMsgChrootSuccessful = 'O'; | 67 static const char kMsgChrootSuccessful = 'O'; |
47 | 68 |
48 static void FatalError(const char *msg, ...) | 69 static void FatalError(const char *msg, ...) |
49 __attribute__((noreturn, format(printf, 1, 2))); | 70 __attribute__((noreturn, format(printf, 1, 2))); |
50 | 71 |
51 static void FatalError(const char *msg, ...) { | 72 static void FatalError(const char *msg, ...) { |
52 va_list ap; | 73 va_list ap; |
53 va_start(ap, msg); | 74 va_start(ap, msg); |
54 | 75 |
55 vfprintf(stderr, msg, ap); | 76 vfprintf(stderr, msg, ap); |
56 fprintf(stderr, ": %s\n", strerror(errno)); | 77 fprintf(stderr, ": %s\n", strerror(errno)); |
57 fflush(stderr); | 78 fflush(stderr); |
58 exit(1); | 79 exit(1); |
59 } | 80 } |
60 | 81 |
61 // We will chroot() to the helper's /proc/self directory. Anything there will | 82 static int CloneChrootHelperProcess() { |
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" | |
71 | |
72 static bool SpawnChrootHelper() { | |
73 int sv[2]; | 83 int sv[2]; |
74 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { | 84 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) { |
75 perror("socketpair"); | 85 perror("socketpair"); |
76 return false; | 86 return -1; |
77 } | 87 } |
78 | 88 |
79 char *safedir = NULL; | 89 // Some people mount /tmp on a non-POSIX filesystem (e.g. NFS). This |
80 struct stat sdir_stat; | 90 // breaks all sorts of assumption in our code. So, if we don't recognize the |
81 if (!stat(SAFE_DIR, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) | 91 // filesystem, we will try to use an alternative location for our temp |
82 safedir = SAFE_DIR; | 92 // directory. |
83 else | 93 char tempDirectoryTemplate[80] = "/tmp/chrome-sandbox-chroot-XXXXXX"; |
84 if (!stat(SAFE_DIR2, &sdir_stat) && S_ISDIR(sdir_stat.st_mode)) | 94 struct statfs sfs; |
85 safedir = SAFE_DIR2; | 95 if (!statfs("/tmp", &sfs) && |
86 else { | 96 (unsigned long)sfs.f_type != BTRFS_SUPER_MAGIC && |
87 fprintf(stderr, "Could not find %s\n", SAFE_DIR2); | 97 (unsigned long)sfs.f_type != EXT2_SUPER_MAGIC && |
88 return false; | 98 (unsigned long)sfs.f_type != EXT3_SUPER_MAGIC && |
| 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) |
89 } | 117 } |
| 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 |
90 | 160 |
91 const pid_t pid = syscall( | 161 const pid_t pid = syscall( |
92 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); | 162 __NR_clone, CLONE_FS | SIGCHLD, 0, 0, 0); |
93 | 163 |
94 if (pid == -1) { | 164 if (pid == -1) { |
95 perror("clone"); | 165 perror("clone"); |
96 close(sv[0]); | 166 close(sv[0]); |
97 close(sv[1]); | 167 close(sv[1]); |
98 return false; | 168 return -1; |
99 } | 169 } |
100 | 170 |
101 if (pid == 0) { | 171 if (pid == 0) { |
102 // We share our files structure with an untrusted process. As a security in | 172 // 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. | 173 // depth measure, we make sure that we can't open anything by mistake. |
104 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT | 174 // TODO(agl): drop CAP_SYS_RESOURCE / use SECURE_NOROOT |
105 | 175 |
106 const struct rlimit nofile = {0, 0}; | 176 const struct rlimit nofile = {0, 0}; |
107 if (setrlimit(RLIMIT_NOFILE, &nofile)) | 177 if (setrlimit(RLIMIT_NOFILE, &nofile)) |
108 FatalError("Setting RLIMIT_NOFILE"); | 178 FatalError("Setting RLIMIT_NOFILE"); |
(...skipping 10 matching lines...) Expand all Loading... |
119 | 189 |
120 if (bytes == 0) | 190 if (bytes == 0) |
121 _exit(0); | 191 _exit(0); |
122 if (bytes != 1) | 192 if (bytes != 1) |
123 FatalError("read"); | 193 FatalError("read"); |
124 | 194 |
125 // do chrooting | 195 // do chrooting |
126 if (msg != kMsgChrootMe) | 196 if (msg != kMsgChrootMe) |
127 FatalError("Unknown message from sandboxed process"); | 197 FatalError("Unknown message from sandboxed process"); |
128 | 198 |
129 // sanity check | 199 if (fchdir(chroot_dir_fd)) |
130 if (chdir(safedir)) | 200 FatalError("Cannot chdir into chroot temp directory"); |
131 FatalError("Cannot chdir into /proc/ directory"); | |
132 | 201 |
133 if (chroot(safedir)) | 202 struct stat st; |
134 FatalError("Cannot chroot into /proc/ directory"); | 203 if (fstat(chroot_dir_fd, &st)) |
| 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"); |
135 | 211 |
136 if (chdir("/")) | 212 if (chdir("/")) |
137 FatalError("Cannot chdir to / after chroot"); | 213 FatalError("Cannot chdir to / after chroot"); |
138 | 214 |
139 const char reply = kMsgChrootSuccessful; | 215 const char reply = kMsgChrootSuccessful; |
140 do { | 216 do { |
141 bytes = write(sv[0], &reply, 1); | 217 bytes = write(sv[0], &reply, 1); |
142 } while (bytes == -1 && errno == EINTR); | 218 } while (bytes == -1 && errno == EINTR); |
143 | 219 |
144 if (bytes != 1) | 220 if (bytes != 1) |
145 FatalError("Writing reply"); | 221 FatalError("Writing reply"); |
146 | 222 |
147 _exit(0); | 223 _exit(0); |
148 // We now become a zombie. /proc/self/fd(info) is now an empty dir and we | 224 } |
149 // are chrooted there. | 225 |
150 // Our (unprivileged) parent should not even be able to open "." or "/" | 226 if (close(chroot_dir_fd)) { |
151 // since they would need to pass the ptrace() check. If our parent wait() | 227 close(sv[0]); |
152 // for us, our root directory will completely disappear. | 228 close(sv[1]); |
| 229 perror("close(chroot_dir_fd)"); |
| 230 return false; |
153 } | 231 } |
154 | 232 |
155 if (close(sv[0])) { | 233 if (close(sv[0])) { |
156 close(sv[1]); | 234 close(sv[1]); |
157 perror("close"); | 235 perror("close"); |
158 return false; | 236 return false; |
159 } | 237 } |
160 | 238 |
| 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 |
161 // In the parent process, we install an environment variable containing the | 248 // In the parent process, we install an environment variable containing the |
162 // number of the file descriptor. | 249 // number of the file descriptor. |
163 char desc_str[64]; | 250 char desc_str[64]; |
164 int printed = snprintf(desc_str, sizeof(desc_str), "%u", sv[1]); | 251 int printed = snprintf(desc_str, sizeof(desc_str), "%d", chroot_signal_fd); |
165 if (printed < 0 || printed >= (int)sizeof(desc_str)) { | 252 if (printed < 0 || printed >= (int)sizeof(desc_str)) { |
166 fprintf(stderr, "Failed to snprintf\n"); | 253 fprintf(stderr, "Failed to snprintf\n"); |
167 return false; | 254 return false; |
168 } | 255 } |
169 | 256 |
170 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { | 257 if (setenv(kSandboxDescriptorEnvironmentVarName, desc_str, 1)) { |
171 perror("setenv"); | 258 perror("setenv"); |
172 close(sv[1]); | 259 close(chroot_signal_fd); |
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]); | |
187 return false; | 260 return false; |
188 } | 261 } |
189 | 262 |
190 return true; | 263 return true; |
191 } | 264 } |
192 | 265 |
193 static bool MoveToNewNamespaces() { | 266 static bool MoveToNewNamespaces() { |
194 // These are the sets of flags which we'll try, in order. | 267 // These are the sets of flags which we'll try, in order. |
195 const int kCloneExtraFlags[] = { | 268 const int kCloneExtraFlags[] = { |
196 CLONE_NEWPID | CLONE_NEWNET, | 269 CLONE_NEWPID | CLONE_NEWNET, |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
347 if (!DropRoot()) | 420 if (!DropRoot()) |
348 return 1; | 421 return 1; |
349 if (!SetupChildEnvironment()) | 422 if (!SetupChildEnvironment()) |
350 return 1; | 423 return 1; |
351 | 424 |
352 execv(argv[1], &argv[1]); | 425 execv(argv[1], &argv[1]); |
353 FatalError("execv failed"); | 426 FatalError("execv failed"); |
354 | 427 |
355 return 1; | 428 return 1; |
356 } | 429 } |
OLD | NEW |