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" |
| 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; |
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 |