void fs_private_etc_list(void) { char *private_list = cfg.etc_private_keep; assert(private_list); struct stat s; if (stat("/etc", &s) == -1) { fprintf(stderr, "Error: cannot find user /etc directory\n"); exit(1); } // create /tmp/firejail/mnt/etc directory fs_build_mnt_dir(); int rv = mkdir(RUN_ETC_DIR, 0755); if (rv == -1) errExit("mkdir"); if (chown(RUN_ETC_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_ETC_DIR, 0755) < 0) errExit("chmod"); // copy the list of files in the new etc directory // using a new child process without root privileges fs_logger_print(); // save the current log pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { if (arg_debug) printf("Copying files in the new etc directory:\n"); // elevate privileges - files in the new /etc directory belong to root if (setreuid(0, 0) < 0) errExit("setreuid"); if (setregid(0, 0) < 0) errExit("setregid"); // copy the list of files in the new home directory char *dlist = strdup(private_list); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); duplicate(ptr); while ((ptr = strtok(NULL, ",")) != NULL) duplicate(ptr); free(dlist); fs_logger_print(); exit(0); } // wait for the child to finish waitpid(child, NULL, 0); if (arg_debug) printf("Mount-bind %s on top of /etc\n", RUN_ETC_DIR); if (mount(RUN_ETC_DIR, "/etc", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); fs_logger("mount /etc"); }
// private mode (--private-home=list): // mount homedir on top of /home/user, // tmpfs on top of /root in nonroot mode, // tmpfs on top of /tmp in root mode, // set skel files, // restore .Xauthority void fs_private_home_list(void) { char *homedir = cfg.homedir; char *private_list = cfg.home_private_keep; assert(homedir); assert(private_list); int xflag = store_xauthority(); int aflag = store_asoundrc(); uid_t uid = getuid(); gid_t gid = getgid(); // create /run/firejail/mnt/home directory mkdir_attr(RUN_HOME_DIR, 0755, uid, gid); fs_logger_print(); // save the current log if (arg_debug) printf("Copying files in the new home:\n"); // copy the list of files in the new home directory char *dlist = strdup(cfg.home_private_keep); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); duplicate(ptr); while ((ptr = strtok(NULL, ",")) != NULL) duplicate(ptr); fs_logger_print(); // save the current log free(dlist); if (arg_debug) printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); if (mount(RUN_HOME_DIR, homedir, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); if (uid != 0) { // mask /root if (arg_debug) printf("Mounting a new /root directory\n"); if (mount("tmpfs", "/root", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=700,gid=0") < 0) errExit("mounting home directory"); } else { // mask /home if (arg_debug) printf("Mounting a new /home directory\n"); if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting home directory"); } skel(homedir, uid, gid); if (xflag) copy_xauthority(); if (aflag) copy_asoundrc(); }
static void duplicate(char *name) { char *fname = check_dir_or_file(name); if (arg_debug) printf("Private home: duplicating %s\n", fname); assert(strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0); struct stat s; if (lstat(fname, &s) == -1) { free(fname); return; } else if (S_ISDIR(s.st_mode)) { // create the directory in RUN_HOME_DIR char *name; char *ptr = strrchr(fname, '/'); ptr++; if (asprintf(&name, "%s/%s", RUN_HOME_DIR, ptr) == -1) errExit("asprintf"); mkdir_attr(name, 0755, getuid(), getgid()); sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FCOPY, fname, name); free(name); } else sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FCOPY, fname, RUN_HOME_DIR); fs_logger2("clone", fname); fs_logger_print(); // save the current log free(fname); }
int sandbox(void* sandbox_arg) { // Get rid of unused parameter warning (void)sandbox_arg; pid_t child_pid = getpid(); if (arg_debug) printf("Initializing child process\n"); // close each end of the unused pipes close(parent_to_child_fds[1]); close(child_to_parent_fds[0]); // wait for parent to do base setup wait_for_other(parent_to_child_fds[0]); if (arg_debug && child_pid == 1) printf("PID namespace installed\n"); //**************************** // set hostname //**************************** if (cfg.hostname) { if (sethostname(cfg.hostname, strlen(cfg.hostname)) < 0) errExit("sethostname"); } //**************************** // mount namespace //**************************** // mount events are not forwarded between the host the sandbox if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) { chk_chroot(); } //**************************** // log sandbox data //**************************** if (cfg.name) fs_logger2("sandbox name:", cfg.name); fs_logger2int("sandbox pid:", (int) sandbox_pid); if (cfg.chrootdir) fs_logger("sandbox filesystem: chroot"); else if (arg_overlay) fs_logger("sandbox filesystem: overlay"); else fs_logger("sandbox filesystem: local"); fs_logger("install mount namespace"); //**************************** // netfilter etc. //**************************** if (arg_netfilter && any_bridge_configured()) { // assuming by default the client filter netfilter(arg_netfilter_file); } if (arg_netfilter6 && any_bridge_configured()) { // assuming by default the client filter netfilter6(arg_netfilter6_file); } // load IBUS env variables if (arg_nonetwork || any_bridge_configured() || any_interface_configured()) { // do nothing - there are problems with ibus version 1.5.11 } else env_ibus_load(); // grab a copy of cp command fs_build_cp_command(); // trace pre-install if (arg_trace || arg_tracelog) fs_trace_preload(); //**************************** // configure filesystem //**************************** #ifdef HAVE_SECCOMP int enforce_seccomp = 0; #endif #ifdef HAVE_CHROOT if (cfg.chrootdir) { fs_chroot(cfg.chrootdir); // redo cp command fs_build_cp_command(); // force caps and seccomp if not started as root if (getuid() != 0) { // force default seccomp inside the chroot, no keep or drop list // the list build on top of the default drop list is kept intact arg_seccomp = 1; #ifdef HAVE_SECCOMP enforce_seccomp = 1; #endif if (cfg.seccomp_list_drop) { free(cfg.seccomp_list_drop); cfg.seccomp_list_drop = NULL; } if (cfg.seccomp_list_keep) { free(cfg.seccomp_list_keep); cfg.seccomp_list_keep = NULL; } // disable all capabilities if (arg_caps_default_filter || arg_caps_list) fprintf(stderr, "Warning: all capabilities disabled for a regular user during chroot\n"); arg_caps_drop_all = 1; // drop all supplementary groups; /etc/group file inside chroot // is controlled by a regular usr arg_nogroups = 1; if (!arg_quiet) printf("Dropping all Linux capabilities and enforcing default seccomp filter\n"); } else arg_seccomp = 1; //**************************** // trace pre-install, this time inside chroot //**************************** if (arg_trace || arg_tracelog) fs_trace_preload(); } else #endif if (arg_overlay) fs_overlayfs(); else fs_basic_fs(); //**************************** // set hostname in /etc/hostname //**************************** if (cfg.hostname) { fs_hostname(cfg.hostname); } //**************************** // private mode //**************************** if (arg_private) { if (cfg.home_private) // --private= fs_private_homedir(); else // --private fs_private(); } if (arg_private_dev) fs_private_dev(); if (arg_private_etc) { fs_private_etc_list(); // create /etc/ld.so.preload file again if (arg_trace || arg_tracelog) fs_trace_preload(); } if (arg_private_bin) fs_private_bin_list(); if (arg_private_tmp) fs_private_tmp(); //**************************** // apply the profile file //**************************** if (cfg.profile) { // apply all whitelist commands ... fs_whitelist(); // ... followed by blacklist commands fs_blacklist(); } //**************************** // install trace //**************************** if (arg_trace || arg_tracelog) fs_trace(); //**************************** // update /proc, /dev, /boot directorymy //**************************** fs_proc_sys_dev_boot(); //**************************** // --nosound and fix for pulseaudio 7.0 //**************************** if (arg_nosound) pulseaudio_disable(); else pulseaudio_init(); //**************************** // networking //**************************** if (arg_nonetwork) { net_if_up("lo"); if (arg_debug) printf("Network namespace enabled, only loopback interface available\n"); } else if (any_bridge_configured() || any_interface_configured()) { // configure lo and eth0...eth3 net_if_up("lo"); if (mac_not_zero(cfg.bridge0.macsandbox)) net_config_mac(cfg.bridge0.devsandbox, cfg.bridge0.macsandbox); sandbox_if_up(&cfg.bridge0); if (mac_not_zero(cfg.bridge1.macsandbox)) net_config_mac(cfg.bridge1.devsandbox, cfg.bridge1.macsandbox); sandbox_if_up(&cfg.bridge1); if (mac_not_zero(cfg.bridge2.macsandbox)) net_config_mac(cfg.bridge2.devsandbox, cfg.bridge2.macsandbox); sandbox_if_up(&cfg.bridge2); if (mac_not_zero(cfg.bridge3.macsandbox)) net_config_mac(cfg.bridge3.devsandbox, cfg.bridge3.macsandbox); sandbox_if_up(&cfg.bridge3); // add a default route if (cfg.defaultgw) { // set the default route if (net_add_route(0, 0, cfg.defaultgw)) fprintf(stderr, "Warning: cannot configure default route\n"); } // enable interfaces if (cfg.interface0.configured && cfg.interface0.ip) { if (arg_debug) printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(cfg.interface0.ip), cfg.interface0.dev); net_if_ip(cfg.interface0.dev, cfg.interface0.ip, cfg.interface0.mask, cfg.interface0.mtu); net_if_up(cfg.interface0.dev); } if (cfg.interface1.configured && cfg.interface1.ip) { if (arg_debug) printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(cfg.interface1.ip), cfg.interface1.dev); net_if_ip(cfg.interface1.dev, cfg.interface1.ip, cfg.interface1.mask, cfg.interface1.mtu); net_if_up(cfg.interface1.dev); } if (cfg.interface2.configured && cfg.interface2.ip) { if (arg_debug) printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(cfg.interface2.ip), cfg.interface2.dev); net_if_ip(cfg.interface2.dev, cfg.interface2.ip, cfg.interface2.mask, cfg.interface2.mtu); net_if_up(cfg.interface2.dev); } if (cfg.interface3.configured && cfg.interface3.ip) { if (arg_debug) printf("Configuring %d.%d.%d.%d address on interface %s\n", PRINT_IP(cfg.interface3.ip), cfg.interface3.dev); net_if_ip(cfg.interface3.dev, cfg.interface3.ip, cfg.interface3.mask, cfg.interface3.mtu); net_if_up(cfg.interface3.dev); } if (arg_debug) printf("Network namespace enabled\n"); } // if any dns server is configured, it is time to set it now fs_resolvconf(); fs_logger_print(); fs_logger_change_owner(); // print network configuration if (!arg_quiet) { if (any_bridge_configured() || any_interface_configured() || cfg.defaultgw || cfg.dns1) { printf("\n"); if (any_bridge_configured() || any_interface_configured()) net_ifprint(); if (cfg.defaultgw != 0) printf("Default gateway %d.%d.%d.%d\n", PRINT_IP(cfg.defaultgw)); if (cfg.dns1 != 0) printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns1)); if (cfg.dns2 != 0) printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns2)); if (cfg.dns3 != 0) printf("DNS server %d.%d.%d.%d\n", PRINT_IP(cfg.dns3)); printf("\n"); } } fs_delete_cp_command(); //**************************** // set application environment //**************************** prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); // kill the child in case the parent died int cwd = 0; if (cfg.cwd) { if (chdir(cfg.cwd) == 0) cwd = 1; } if (!cwd) { if (chdir("/") < 0) errExit("chdir"); if (cfg.homedir) { struct stat s; if (stat(cfg.homedir, &s) == 0) { /* coverity[toctou] */ if (chdir(cfg.homedir) < 0) errExit("chdir"); } } } // set environment env_defaults(); // set user-supplied environment variables env_apply(); // set nice if (arg_nice) { errno = 0; int rv = nice(cfg.nice); (void) rv; if (errno) { fprintf(stderr, "Warning: cannot set nice value\n"); errno = 0; } } // clean /tmp/.X11-unix sockets fs_x11(); //**************************** // set security filters //**************************** // set capabilities if (!arg_noroot) set_caps(); // set rlimits set_rlimits(); // set seccomp #ifdef HAVE_SECCOMP // install protocol filter if (cfg.protocol) { protocol_filter(); // install filter protocol_filter_save(); // save filter in PROTOCOL_CFG } // if a keep list is available, disregard the drop list if (arg_seccomp == 1) { if (cfg.seccomp_list_keep) seccomp_filter_keep(); else if (cfg.seccomp_list_errno) seccomp_filter_errno(); else seccomp_filter_drop(enforce_seccomp); } #endif // set cpu affinity if (cfg.cpus) { save_cpu(); // save cpu affinity mask to CPU_CFG file set_cpu_affinity(); } // save cgroup in CGROUP_CFG file if (cfg.cgroup) save_cgroup(); //**************************************** // drop privileges or create a new user namespace //**************************************** save_nogroups(); if (arg_noroot) { int rv = unshare(CLONE_NEWUSER); if (rv == -1) { fprintf(stderr, "Error: cannot mount a new user namespace\n"); perror("unshare"); drop_privs(arg_nogroups); } } else drop_privs(arg_nogroups); // notify parent that new user namespace has been created so a proper // UID/GID map can be setup notify_other(child_to_parent_fds[1]); close(child_to_parent_fds[1]); // wait for parent to finish setting up a proper UID/GID map wait_for_other(parent_to_child_fds[0]); close(parent_to_child_fds[0]); // somehow, the new user namespace resets capabilities; // we need to do them again if (arg_noroot) { set_caps(); if (arg_debug) printf("noroot user namespace installed\n"); } //**************************************** // fork the application and monitor it //**************************************** pid_t app_pid = fork(); if (app_pid == -1) errExit("fork"); if (app_pid == 0) { prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); // kill the child in case the parent died start_application(); // start app } monitor_application(app_pid); // monitor application return 0; }
void fs_private_bin_list(void) { char *private_list = cfg.bin_private_keep; assert(private_list); // create /tmp/firejail/mnt/bin directory fs_build_mnt_dir(); int rv = mkdir(RUN_BIN_DIR, 0755); if (rv == -1) errExit("mkdir"); if (chown(RUN_BIN_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_BIN_DIR, 0755) < 0) errExit("chmod"); // copy the list of files in the new etc directory // using a new child process without root privileges fs_logger_print(); // save the current log pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { if (arg_debug) printf("Copying files in the new home:\n"); // elevate privileges - files in the new /bin directory belong to root if (setreuid(0, 0) < 0) errExit("setreuid"); if (setregid(0, 0) < 0) errExit("setregid"); // copy the list of files in the new home directory char *dlist = strdup(private_list); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); duplicate(ptr); while ((ptr = strtok(NULL, ",")) != NULL) duplicate(ptr); free(dlist); fs_logger_print(); exit(0); } // wait for the child to finish waitpid(child, NULL, 0); // mount-bind int i = 0; while (paths[i]) { struct stat s; if (stat(paths[i], &s) == 0) { if (arg_debug) printf("Mount-bind %s on top of %s\n", RUN_BIN_DIR, paths[i]); if (mount(RUN_BIN_DIR, paths[i], NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); fs_logger2("tmpfs", paths[i]); fs_logger2("mount", paths[i]); } i++; } // log cloned files char *dlist = strdup(private_list); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); while (ptr) { i = 0; while (paths[i]) { struct stat s; if (stat(paths[i], &s) == 0) { char *fname; if (asprintf(&fname, "%s/%s", paths[i], ptr) == -1) errExit("asprintf"); fs_logger2("clone", fname); free(fname); } i++; } ptr = strtok(NULL, ","); } free(dlist); }