diff options
Diffstat (limited to 'src/firejail/sandbox.c')
-rw-r--r-- | src/firejail/sandbox.c | 490 |
1 files changed, 490 insertions, 0 deletions
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c new file mode 100644 index 000000000..3f5fb51fa --- /dev/null +++ b/src/firejail/sandbox.c | |||
@@ -0,0 +1,490 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014, 2015 Firejail Authors | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | |||
21 | #include "firejail.h" | ||
22 | #include <sys/mount.h> | ||
23 | #include <sys/wait.h> | ||
24 | #include <sys/stat.h> | ||
25 | #include <sys/prctl.h> | ||
26 | #include <sys/time.h> | ||
27 | #include <sys/resource.h> | ||
28 | |||
29 | #include <sched.h> | ||
30 | #ifndef CLONE_NEWUSER | ||
31 | #define CLONE_NEWUSER 0x10000000 | ||
32 | #endif | ||
33 | |||
34 | static void set_caps(void) { | ||
35 | if (arg_caps_drop_all) | ||
36 | caps_drop_all(); | ||
37 | else if (arg_caps_drop) | ||
38 | caps_drop_list(arg_caps_list); | ||
39 | else if (arg_caps_keep) | ||
40 | caps_keep_list(arg_caps_list); | ||
41 | else if (arg_caps_default_filter) | ||
42 | caps_default_filter(); | ||
43 | } | ||
44 | |||
45 | void save_nogroups(void) { | ||
46 | if (arg_nogroups == 0) | ||
47 | return; | ||
48 | |||
49 | char *fname; | ||
50 | if (asprintf(&fname, "%s/groups", MNT_DIR) == -1) | ||
51 | errExit("asprintf"); | ||
52 | FILE *fp = fopen(fname, "w"); | ||
53 | if (fp) { | ||
54 | fprintf(fp, "\n"); | ||
55 | fclose(fp); | ||
56 | if (chown(fname, 0, 0) < 0) | ||
57 | errExit("chown"); | ||
58 | } | ||
59 | else { | ||
60 | fprintf(stderr, "Error: cannot save nogroups state\n"); | ||
61 | free(fname); | ||
62 | exit(1); | ||
63 | } | ||
64 | |||
65 | free(fname); | ||
66 | } | ||
67 | |||
68 | static void sandbox_if_up(Bridge *br) { | ||
69 | assert(br); | ||
70 | if (!br->configured) | ||
71 | return; | ||
72 | |||
73 | char *dev = br->devsandbox; | ||
74 | net_if_up(dev); | ||
75 | |||
76 | if (br->arg_ip_none == 1); // do nothing | ||
77 | else if (br->arg_ip_none == 0 && br->macvlan == 0) { | ||
78 | if (br->ipsandbox == br->ip) { | ||
79 | fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address.\n", PRINT_IP(br->ipsandbox), br->dev); | ||
80 | exit(1); | ||
81 | } | ||
82 | |||
83 | // just assign the address | ||
84 | assert(br->ipsandbox); | ||
85 | if (arg_debug) | ||
86 | printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(br->ipsandbox), dev); | ||
87 | net_if_ip(dev, br->ipsandbox, br->mask); | ||
88 | net_if_up(dev); | ||
89 | } | ||
90 | else if (br->arg_ip_none == 0 && br->macvlan == 1) { | ||
91 | // reassign the macvlan address | ||
92 | if (br->ipsandbox == 0) | ||
93 | // ip address assigned by arp-scan for a macvlan device | ||
94 | br->ipsandbox = arp_assign(dev, br); //br->ip, br->mask); | ||
95 | else { | ||
96 | if (br->ipsandbox == br->ip) { | ||
97 | fprintf(stderr, "Error: %d.%d.%d.%d is interface %s address.\n", PRINT_IP(br->ipsandbox), br->dev); | ||
98 | exit(1); | ||
99 | } | ||
100 | |||
101 | uint32_t rv = arp_check(dev, br->ipsandbox, br->ip); | ||
102 | if (rv) { | ||
103 | fprintf(stderr, "Error: the address %d.%d.%d.%d is already in use.\n", PRINT_IP(br->ipsandbox)); | ||
104 | exit(1); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | if (arg_debug) | ||
109 | printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(br->ipsandbox), dev); | ||
110 | net_if_ip(dev, br->ipsandbox, br->mask); | ||
111 | net_if_up(dev); | ||
112 | } | ||
113 | } | ||
114 | |||
115 | static void chk_chroot(void) { | ||
116 | // if we are starting firejail inside some other container technology, we don't care about this | ||
117 | char *mycont = getenv("container"); | ||
118 | if (mycont) | ||
119 | return; | ||
120 | |||
121 | // check if this is a regular chroot | ||
122 | struct stat s; | ||
123 | if (stat("/", &s) == 0) { | ||
124 | if (s.st_ino != 2) | ||
125 | return; | ||
126 | } | ||
127 | |||
128 | fprintf(stderr, "Error: cannot mount filesystem as slave\n"); | ||
129 | exit(1); | ||
130 | } | ||
131 | |||
132 | int sandbox(void* sandbox_arg) { | ||
133 | pid_t child_pid = getpid(); | ||
134 | if (arg_debug) | ||
135 | printf("Initializing child process\n"); | ||
136 | |||
137 | // close each end of the unused pipes | ||
138 | close(parent_to_child_fds[1]); | ||
139 | close(child_to_parent_fds[0]); | ||
140 | |||
141 | // wait for parent to do base setup | ||
142 | wait_for_other(parent_to_child_fds[0]); | ||
143 | |||
144 | if (arg_debug && child_pid == 1) | ||
145 | printf("PID namespace installed\n"); | ||
146 | |||
147 | //**************************** | ||
148 | // set hostname | ||
149 | //**************************** | ||
150 | if (cfg.hostname) { | ||
151 | if (sethostname(cfg.hostname, strlen(cfg.hostname)) < 0) | ||
152 | errExit("sethostname"); | ||
153 | } | ||
154 | |||
155 | //**************************** | ||
156 | // mount namespace | ||
157 | //**************************** | ||
158 | // mount events are not forwarded between the host the sandbox | ||
159 | if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) { | ||
160 | chk_chroot(); | ||
161 | } | ||
162 | |||
163 | //**************************** | ||
164 | // netfilter | ||
165 | //**************************** | ||
166 | if (arg_netfilter && any_bridge_configured()) { // assuming by default the client filter | ||
167 | netfilter(arg_netfilter_file); | ||
168 | } | ||
169 | |||
170 | //**************************** | ||
171 | // trace pre-install | ||
172 | //**************************** | ||
173 | if (arg_trace) | ||
174 | fs_trace_preload(); | ||
175 | |||
176 | //**************************** | ||
177 | // configure filesystem | ||
178 | //**************************** | ||
179 | #ifdef HAVE_CHROOT | ||
180 | if (cfg.chrootdir) { | ||
181 | fs_chroot(cfg.chrootdir); | ||
182 | // force caps and seccomp if not started as root | ||
183 | if (getuid() != 0) { | ||
184 | // force default seccomp inside the chroot, no keep or drop list | ||
185 | // the list build on top of the default drop list is kept intact | ||
186 | arg_seccomp = 1; | ||
187 | if (arg_seccomp_list_drop) { | ||
188 | free(arg_seccomp_list_drop); | ||
189 | arg_seccomp_list_drop = NULL; | ||
190 | } | ||
191 | if (arg_seccomp_list_keep) { | ||
192 | free(arg_seccomp_list_keep); | ||
193 | arg_seccomp_list_keep = NULL; | ||
194 | } | ||
195 | |||
196 | // disable all capabilities | ||
197 | if (arg_caps_default_filter || arg_caps_list) | ||
198 | fprintf(stderr, "Warning: all capabilities disabled for a regular user during chroot\n"); | ||
199 | arg_caps_drop_all = 1; | ||
200 | |||
201 | // drop all supplementary groups; /etc/group file inside chroot | ||
202 | // is controlled by a regular usr | ||
203 | arg_nogroups = 1; | ||
204 | printf("Dropping all Linux capabilities and enforcing default seccomp filter\n"); | ||
205 | } | ||
206 | |||
207 | //**************************** | ||
208 | // trace pre-install, this time inside chroot | ||
209 | //**************************** | ||
210 | if (arg_trace) | ||
211 | fs_trace_preload(); | ||
212 | } | ||
213 | else | ||
214 | #endif | ||
215 | if (arg_overlay) | ||
216 | fs_overlayfs(); | ||
217 | else | ||
218 | fs_basic_fs(); | ||
219 | |||
220 | |||
221 | //**************************** | ||
222 | // set hostname in /etc/hostname | ||
223 | //**************************** | ||
224 | if (cfg.hostname) { | ||
225 | fs_hostname(cfg.hostname); | ||
226 | } | ||
227 | |||
228 | //**************************** | ||
229 | // apply the profile file | ||
230 | //**************************** | ||
231 | if (cfg.profile) | ||
232 | fs_blacklist(cfg.homedir); | ||
233 | |||
234 | //**************************** | ||
235 | // private mode | ||
236 | //**************************** | ||
237 | if (arg_private) { | ||
238 | if (cfg.home_private) // --private= | ||
239 | fs_private_homedir(); | ||
240 | else if (cfg.home_private_keep) // --private.keep= | ||
241 | fs_private_home_list(); | ||
242 | else // --private | ||
243 | fs_private(); | ||
244 | } | ||
245 | |||
246 | if (arg_private_dev) | ||
247 | fs_private_dev(); | ||
248 | |||
249 | //**************************** | ||
250 | // install trace | ||
251 | //**************************** | ||
252 | if (arg_trace) | ||
253 | fs_trace(); | ||
254 | |||
255 | //**************************** | ||
256 | // update /proc, /dev, /boot directorymy | ||
257 | //**************************** | ||
258 | fs_proc_sys_dev_boot(); | ||
259 | |||
260 | //**************************** | ||
261 | // networking | ||
262 | //**************************** | ||
263 | if (arg_nonetwork) { | ||
264 | net_if_up("lo"); | ||
265 | } | ||
266 | else if (any_bridge_configured()) { | ||
267 | // configure lo and eth0...eth3 | ||
268 | net_if_up("lo"); | ||
269 | |||
270 | if (mac_not_zero(cfg.bridge0.macsandbox)) | ||
271 | net_config_mac(cfg.bridge0.devsandbox, cfg.bridge0.macsandbox); | ||
272 | sandbox_if_up(&cfg.bridge0); | ||
273 | |||
274 | if (mac_not_zero(cfg.bridge1.macsandbox)) | ||
275 | net_config_mac(cfg.bridge1.devsandbox, cfg.bridge1.macsandbox); | ||
276 | sandbox_if_up(&cfg.bridge1); | ||
277 | |||
278 | if (mac_not_zero(cfg.bridge2.macsandbox)) | ||
279 | net_config_mac(cfg.bridge2.devsandbox, cfg.bridge2.macsandbox); | ||
280 | sandbox_if_up(&cfg.bridge2); | ||
281 | |||
282 | if (mac_not_zero(cfg.bridge3.macsandbox)) | ||
283 | net_config_mac(cfg.bridge3.devsandbox, cfg.bridge3.macsandbox); | ||
284 | sandbox_if_up(&cfg.bridge3); | ||
285 | |||
286 | // add a default route | ||
287 | if (cfg.defaultgw) { | ||
288 | // set the default route | ||
289 | if (net_add_route(0, 0, cfg.defaultgw)) | ||
290 | fprintf(stderr, "Warning: cannot configure default route\n"); | ||
291 | } | ||
292 | |||
293 | if (arg_debug) | ||
294 | printf("Network namespace enabled\n"); | ||
295 | } | ||
296 | |||
297 | // if any dns server is configured, it is time to set it now | ||
298 | fs_resolvconf(); | ||
299 | |||
300 | // print network configuration | ||
301 | if (any_bridge_configured() || cfg.defaultgw || cfg.dns1) { | ||
302 | printf("\n"); | ||
303 | if (any_bridge_configured()) | ||
304 | net_ifprint(); | ||
305 | if (cfg.defaultgw != 0) | ||
306 | printf("Default gateway %d.%d.%d.%d\n", PRINT_IP(cfg.defaultgw)); | ||
307 | if (cfg.dns1 != 0) | ||
308 | printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns1)); | ||
309 | if (cfg.dns2 != 0) | ||
310 | printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns2)); | ||
311 | if (cfg.dns3 != 0) | ||
312 | printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns3)); | ||
313 | printf("\n"); | ||
314 | } | ||
315 | |||
316 | |||
317 | |||
318 | //**************************** | ||
319 | // start executable | ||
320 | //**************************** | ||
321 | prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); // kill the child in case the parent died | ||
322 | int cwd = 0; | ||
323 | if (cfg.cwd) { | ||
324 | if (chdir(cfg.cwd) == 0) | ||
325 | cwd = 1; | ||
326 | } | ||
327 | |||
328 | if (!cwd) { | ||
329 | if (chdir("/") < 0) | ||
330 | errExit("chdir"); | ||
331 | if (cfg.homedir) { | ||
332 | struct stat s; | ||
333 | if (stat(cfg.homedir, &s) == 0) { | ||
334 | /* coverity[toctou] */ | ||
335 | if (chdir(cfg.homedir) < 0) | ||
336 | errExit("chdir"); | ||
337 | } | ||
338 | } | ||
339 | } | ||
340 | |||
341 | // set environment | ||
342 | // fix qt 4.8 | ||
343 | if (setenv("QT_X11_NO_MITSHM", "1", 1) < 0) | ||
344 | errExit("setenv"); | ||
345 | if (setenv("container", "firejail", 1) < 0) // LXC sets container=lxc, | ||
346 | errExit("setenv"); | ||
347 | if (arg_zsh && setenv("SHELL", "/usr/bin/zsh", 1) < 0) | ||
348 | errExit("setenv"); | ||
349 | if (arg_csh && setenv("SHELL", "/bin/csh", 1) < 0) | ||
350 | errExit("setenv"); | ||
351 | if (cfg.shell && setenv("SHELL", cfg.shell, 1) < 0) | ||
352 | errExit("setenv"); | ||
353 | // set prompt color to green | ||
354 | //export PS1='\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] ' | ||
355 | if (setenv("PROMPT_COMMAND", "export PS1=\"\\[\\e[1;32m\\][\\u@\\h \\W]\\$\\[\\e[0m\\] \"", 1) < 0) | ||
356 | errExit("setenv"); | ||
357 | |||
358 | |||
359 | // set capabilities | ||
360 | if (!arg_noroot) | ||
361 | set_caps(); | ||
362 | |||
363 | // set rlimits | ||
364 | set_rlimits(); | ||
365 | |||
366 | // set seccomp | ||
367 | #ifdef HAVE_SECCOMP | ||
368 | // if a keep list is available, disregard the drop list | ||
369 | if (arg_seccomp == 1) { | ||
370 | if (arg_seccomp_list_keep) | ||
371 | seccomp_filter_keep(); // this will also save the fmyilter to MNT_DIR/seccomp file | ||
372 | else | ||
373 | seccomp_filter_drop(); // this will also save the filter to MNT_DIR/seccomp file | ||
374 | } | ||
375 | #endif | ||
376 | |||
377 | // set cpu affinity | ||
378 | if (cfg.cpus) { | ||
379 | save_cpu(); // save cpu affinity mask to MNT_DIR/cpu file | ||
380 | set_cpu_affinity(); | ||
381 | } | ||
382 | |||
383 | // save cgroup in MNT_DIR/cgroup file | ||
384 | if (cfg.cgroup) | ||
385 | save_cgroup(); | ||
386 | |||
387 | //**************************************** | ||
388 | // drop privileges or create a new user namespace | ||
389 | //**************************************** | ||
390 | save_nogroups(); | ||
391 | if (arg_noroot) { | ||
392 | int rv = unshare(CLONE_NEWUSER); | ||
393 | if (rv == -1) { | ||
394 | fprintf(stderr, "Warning: cannot mount a new user namespace\n"); | ||
395 | perror("unshare"); | ||
396 | drop_privs(arg_nogroups); | ||
397 | } | ||
398 | } | ||
399 | else | ||
400 | drop_privs(arg_nogroups); | ||
401 | |||
402 | // notify parent that new user namespace has been created so a proper | ||
403 | // UID/GID map can be setup | ||
404 | notify_other(child_to_parent_fds[1]); | ||
405 | close(child_to_parent_fds[1]); | ||
406 | |||
407 | // wait for parent to finish setting up a proper UID/GID map | ||
408 | wait_for_other(parent_to_child_fds[0]); | ||
409 | close(parent_to_child_fds[0]); | ||
410 | |||
411 | // somehow, the new user namespace resets capabilities; | ||
412 | // we need to do them again | ||
413 | if (arg_noroot) { | ||
414 | set_caps(); | ||
415 | if (arg_debug) | ||
416 | printf("User namespace (noroot) installed\n"); | ||
417 | } | ||
418 | |||
419 | |||
420 | //**************************************** | ||
421 | // start the program without using a shell | ||
422 | //**************************************** | ||
423 | if (arg_shell_none) { | ||
424 | if (arg_debug) { | ||
425 | int i; | ||
426 | for (i = cfg.original_program_index; i < cfg.original_argc; i++) { | ||
427 | if (cfg.original_argv[i] == NULL) | ||
428 | break; | ||
429 | printf("execvp argument %d: %s\n", i - cfg.original_program_index, cfg.original_argv[i]); | ||
430 | } | ||
431 | } | ||
432 | |||
433 | if (!arg_command) | ||
434 | printf("Child process initialized\n"); | ||
435 | execvp(cfg.original_argv[cfg.original_program_index], &cfg.original_argv[cfg.original_program_index + 1]); | ||
436 | } | ||
437 | //**************************************** | ||
438 | // start the program using a shell | ||
439 | //**************************************** | ||
440 | else { | ||
441 | // choose the shell requested by the user, or use bash as default | ||
442 | char *sh; | ||
443 | if (cfg.shell) | ||
444 | sh = cfg.shell; | ||
445 | else if (arg_zsh) | ||
446 | sh = "/usr/bin/zsh"; | ||
447 | else if (arg_csh) | ||
448 | sh = "/bin/csh"; | ||
449 | else | ||
450 | sh = "/bin/bash"; | ||
451 | |||
452 | char *arg[5]; | ||
453 | int index = 0; | ||
454 | arg[index++] = sh; | ||
455 | arg[index++] = "-c"; | ||
456 | assert(cfg.command_line); | ||
457 | if (arg_debug) | ||
458 | printf("Starting %s\n", cfg.command_line); | ||
459 | if (arg_doubledash) | ||
460 | arg[index++] = "--"; | ||
461 | arg[index++] = cfg.command_line; | ||
462 | arg[index] = NULL; | ||
463 | assert(index < 5); | ||
464 | |||
465 | if (arg_debug) { | ||
466 | char *msg; | ||
467 | if (asprintf(&msg, "sandbox %d, execvp into %s", sandbox_pid, cfg.command_line) == -1) | ||
468 | errExit("asprintf"); | ||
469 | logmsg(msg); | ||
470 | free(msg); | ||
471 | } | ||
472 | |||
473 | if (arg_debug) { | ||
474 | int i; | ||
475 | for (i = 0; i < 5; i++) { | ||
476 | if (arg[i] == NULL) | ||
477 | break; | ||
478 | printf("execvp argument %d: %s\n", i, arg[i]); | ||
479 | } | ||
480 | } | ||
481 | |||
482 | if (!arg_command) | ||
483 | printf("Child process initialized\n"); | ||
484 | execvp(sh, arg); | ||
485 | } | ||
486 | |||
487 | |||
488 | perror("execvp"); | ||
489 | return 0; | ||
490 | } | ||