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; }
// check profile line; if line == 0, this was generated from a command line option // return 1 if the command is to be added to the linked list of profile commands // return 0 if the command was already executed inside the function int profile_check_line(char *ptr, int lineno, const char *fname) { EUID_ASSERT(); // check ignore list int i; for (i = 0; i < MAX_PROFILE_IGNORE; i++) { if (cfg.profile_ignore[i] == NULL) break; if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0) return 0; // ignore line } if (strncmp(ptr, "ignore ", 7) == 0) { char *str = strdup(ptr + 7); if (*str == '\0') { fprintf(stderr, "Error: invalid ignore option\n"); exit(1); } // find an empty entry in profile_ignore array int j; for (j = 0; j < MAX_PROFILE_IGNORE; j++) { if (cfg.profile_ignore[j] == NULL) break; } if (j >= MAX_PROFILE_IGNORE) { fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE); exit(1); } // ... and configure it else cfg.profile_ignore[j] = str; return 0; } // mkdir if (strncmp(ptr, "mkdir ", 6) == 0) { fs_mkdir(ptr + 6); return 0; } // sandbox name else if (strncmp(ptr, "name ", 5) == 0) { cfg.name = ptr + 5; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } else if (strcmp(ptr, "ipc-namespace") == 0) { arg_ipc = 1; return 0; } // seccomp, caps, private, user namespace else if (strcmp(ptr, "noroot") == 0) { #if HAVE_USERNS if (checkcfg(CFG_USERNS)) check_user_namespace(); else fprintf(stderr, "Warning: user namespace feature is disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "nonewprivs") == 0) { arg_nonewprivs = 1; return 0; } else if (strcmp(ptr, "seccomp") == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) arg_seccomp = 1; else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "caps") == 0) { arg_caps_default_filter = 1; return 0; } else if (strcmp(ptr, "caps.drop all") == 0) { arg_caps_drop_all = 1; return 0; } else if (strcmp(ptr, "shell none") == 0) { arg_shell_none = 1; return 0; } else if (strcmp(ptr, "tracelog") == 0) { arg_tracelog = 1; return 0; } else if (strcmp(ptr, "private") == 0) { arg_private = 1; return 0; } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; return 0; } else if (strcmp(ptr, "private-tmp") == 0) { arg_private_tmp = 1; return 0; } else if (strcmp(ptr, "nogroups") == 0) { arg_nogroups = 1; return 0; } else if (strcmp(ptr, "nosound") == 0) { arg_nosound = 1; arg_private_dev = 1; return 0; } else if (strcmp(ptr, "netfilter") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) arg_netfilter = 1; else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "netfilter ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter = 1; arg_netfilter_file = strdup(ptr + 10); if (!arg_netfilter_file) errExit("strdup"); check_netfilter_file(arg_netfilter_file); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "netfilter6 ", 11) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter6 = 1; arg_netfilter6_file = strdup(ptr + 11); if (!arg_netfilter6_file) errExit("strdup"); check_netfilter_file(arg_netfilter6_file); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "net none") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_nonetwork = 1; cfg.bridge0.configured = 0; cfg.bridge1.configured = 0; cfg.bridge2.configured = 0; cfg.bridge3.configured = 0; cfg.interface0.configured = 0; cfg.interface1.configured = 0; cfg.interface2.configured = 0; cfg.interface3.configured = 0; } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "net ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { #ifdef HAVE_NETWORK_RESTRICTED // compile time restricted networking if (getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } #endif // run time restricted networking if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } if (strcmp(ptr + 4, "lo") == 0) { fprintf(stderr, "Error: cannot attach to lo device\n"); exit(1); } Bridge *br; if (cfg.bridge0.configured == 0) br = &cfg.bridge0; else if (cfg.bridge1.configured == 0) br = &cfg.bridge1; else if (cfg.bridge2.configured == 0) br = &cfg.bridge2; else if (cfg.bridge3.configured == 0) br = &cfg.bridge3; else { fprintf(stderr, "Error: maximum 4 network devices are allowed\n"); exit(1); } net_configure_bridge(br, ptr + 4); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "iprange ", 8) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->iprange_start || br->iprange_end) { fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n"); exit(1); } // parse option arguments char *firstip = ptr + 8; char *secondip = firstip; while (*secondip != '\0') { if (*secondip == ',') break; secondip++; } if (*secondip == '\0') { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } *secondip = '\0'; secondip++; // check addresses if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) || br->iprange_start >= br->iprange_end) { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) { fprintf(stderr, "Error: IP range addresses not in network range\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } // from here else if (strncmp(ptr, "mac ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (mac_not_zero(br->macsandbox)) { fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n"); exit(1); } // read the address if (atomac(ptr + 4, br->macsandbox)) { fprintf(stderr, "Error: invalid MAC address\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "mtu ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) { fprintf(stderr, "Error: invalid mtu value\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "ip ", 3) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ipsandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined if (strcmp(ptr + 3, "none") == 0) br->arg_ip_none = 1; else { if (atoip(ptr + 3, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "ip6 ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined // todo: verify ipv6 syntax br->ip6sandbox = ptr + 4; // if (atoip(argv[i] + 5, &br->ipsandbox)) { // fprintf(stderr, "Error: invalid IP address\n"); // exit(1); // } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "defaultgw ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { if (atoip(ptr + 10, &cfg.defaultgw)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } if (strncmp(ptr, "protocol ", 9) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) protocol_store(ptr + 9); else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } if (strncmp(ptr, "env ", 4) == 0) { env_store(ptr + 4); return 0; } // seccomp drop list on top of default list if (strncmp(ptr, "seccomp ", 8) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list = strdup(ptr + 8); if (!cfg.seccomp_list) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_drop = strdup(ptr + 13); if (!cfg.seccomp_list_drop) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // seccomp keep list if (strncmp(ptr, "seccomp.keep ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_keep= strdup(ptr + 13); if (!cfg.seccomp_list_keep) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // caps drop list if (strncmp(ptr, "caps.drop ", 10) == 0) { arg_caps_drop = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // caps keep list if (strncmp(ptr, "caps.keep ", 10) == 0) { arg_caps_keep = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // hostname if (strncmp(ptr, "hostname ", 9) == 0) { cfg.hostname = ptr + 9; return 0; } // dns if (strncmp(ptr, "dns ", 4) == 0) { uint32_t dns; if (atoip(ptr + 4, &dns)) { fprintf(stderr, "Error: invalid DNS server IP address\n"); return 1; } if (cfg.dns1 == 0) cfg.dns1 = dns; else if (cfg.dns2 == 0) cfg.dns2 = dns; else if (cfg.dns3 == 0) cfg.dns3 = dns; else { fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); return 1; } return 0; } // cpu affinity if (strncmp(ptr, "cpu ", 4) == 0) { read_cpu_list(ptr + 4); return 0; } // nice value if (strncmp(ptr, "nice ", 4) == 0) { cfg.nice = atoi(ptr + 5); if (getuid() != 0 &&cfg.nice < 0) cfg.nice = 0; arg_nice = 1; return 0; } // cgroup if (strncmp(ptr, "cgroup ", 7) == 0) { set_cgroup(ptr + 7); return 0; } // writable-etc if (strcmp(ptr, "writable-etc") == 0) { if (cfg.etc_private_keep) { fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n"); exit(1); } arg_writable_etc = 1; return 0; } // writable-var if (strcmp(ptr, "writable-var") == 0) { arg_writable_var = 1; return 0; } // private directory if (strncmp(ptr, "private ", 8) == 0) { cfg.home_private = ptr + 8; fs_check_private_dir(); arg_private = 1; return 0; } // private /etc list of files and directories if (strncmp(ptr, "private-etc ", 12) == 0) { if (arg_writable_etc) { fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); exit(1); } cfg.etc_private_keep = ptr + 12; fs_check_etc_list(); arg_private_etc = 1; return 0; } // private /bin list of files if (strncmp(ptr, "private-bin ", 12) == 0) { cfg.bin_private_keep = ptr + 12; arg_private_bin = 1; fs_check_bin_list(); return 0; } // filesystem bind if (strncmp(ptr, "bind ", 5) == 0) { #ifdef HAVE_BIND if (checkcfg(CFG_BIND)) { if (getuid() != 0) { fprintf(stderr, "Error: --bind option is available only if running as root\n"); exit(1); } // extract two directories char *dname1 = ptr + 5; char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories if (dname2 == NULL) { fprintf(stderr, "Error: missing second directory for bind\n"); exit(1); } // check directories invalid_filename(dname1); invalid_filename(dname2); if (strstr(dname1, "..") || strstr(dname2, "..")) { fprintf(stderr, "Error: invalid file name.\n"); exit(1); } if (is_link(dname1) || is_link(dname2)) { fprintf(stderr, "Symbolic links are not allowed for bind command\n"); exit(1); } // insert comma back *(dname2 - 1) = ','; return 1; } else { fprintf(stderr, "Warning: bind feature is disabled in Firejail configuration file\n"); return 0; } #else return 0; #endif } // rlimit if (strncmp(ptr, "rlimit", 6) == 0) { if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { ptr += 14; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nofile); arg_rlimit_nofile = 1; } else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nproc); arg_rlimit_nproc = 1; } else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_fsize); arg_rlimit_fsize = 1; } else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { ptr += 18; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_sigpending); arg_rlimit_sigpending = 1; } else { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } return 0; } // read-write if (strncmp(ptr, "read-write ", 11) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: read-write command is available only for root user\n"); exit(1); } fs_rdwr_add(ptr + 11); return 0; } // rest of filesystem if (strncmp(ptr, "blacklist ", 10) == 0) ptr += 10; else if (strncmp(ptr, "blacklist-nolog ", 16) == 0) ptr += 16; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "whitelist ", 10) == 0) { #ifdef HAVE_WHITELIST if (checkcfg(CFG_WHITELIST)) { arg_whitelist = 1; ptr += 10; } else return 0; #else return 0; #endif } else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "tmpfs ", 6) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n"); exit(1); } ptr += 6; } else { if (lineno == 0) fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } // some characters just don't belong in filenames invalid_filename(ptr); if (strstr(ptr, "..")) { if (lineno == 0) fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } return 1; }
// check profile line; if line == 0, this was generated from a command line option // return 1 if the command is to be added to the linked list of profile commands // return 0 if the command was already executed inside the function int profile_check_line(char *ptr, int lineno, const char *fname) { EUID_ASSERT(); // check ignore list int i; for (i = 0; i < MAX_PROFILE_IGNORE; i++) { if (cfg.profile_ignore[i] == NULL) break; if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0) return 0; // ignore line } if (strncmp(ptr, "ignore ", 7) == 0) { char *str = strdup(ptr + 7); if (*str == '\0') { fprintf(stderr, "Error: invalid ignore option\n"); exit(1); } // find an empty entry in profile_ignore array int j; for (j = 0; j < MAX_PROFILE_IGNORE; j++) { if (cfg.profile_ignore[j] == NULL) break; } if (j >= MAX_PROFILE_IGNORE) { fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE); exit(1); } // ... and configure it else cfg.profile_ignore[j] = str; return 0; } // mkdir if (strncmp(ptr, "mkdir ", 6) == 0) { fs_mkdir(ptr + 6); return 1; // process mkdir again while applying blacklists } // mkfile if (strncmp(ptr, "mkfile ", 7) == 0) { fs_mkfile(ptr + 7); return 1; // process mkfile again while applying blacklists } // sandbox name else if (strncmp(ptr, "name ", 5) == 0) { cfg.name = ptr + 5; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } else if (strcmp(ptr, "ipc-namespace") == 0) { arg_ipc = 1; return 0; } // seccomp, caps, private, user namespace else if (strcmp(ptr, "noroot") == 0) { #if HAVE_USERNS if (checkcfg(CFG_USERNS)) check_user_namespace(); else warning_feature_disabled("noroot"); #endif return 0; } else if (strcmp(ptr, "nonewprivs") == 0) { arg_nonewprivs = 1; return 0; } else if (strcmp(ptr, "seccomp") == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) arg_seccomp = 1; else warning_feature_disabled("seccomp"); #endif return 0; } else if (strcmp(ptr, "caps") == 0) { arg_caps_default_filter = 1; return 0; } else if (strcmp(ptr, "caps.drop all") == 0) { arg_caps_drop_all = 1; return 0; } else if (strcmp(ptr, "shell none") == 0) { arg_shell_none = 1; return 0; } else if (strcmp(ptr, "tracelog") == 0) { arg_tracelog = 1; return 0; } else if (strcmp(ptr, "private") == 0) { arg_private = 1; return 0; } if (strncmp(ptr, "private-home ", 13) == 0) { #ifdef HAVE_PRIVATE_HOME if (checkcfg(CFG_PRIVATE_HOME)) { if (cfg.home_private_keep) { if ( asprintf(&cfg.home_private_keep, "%s,%s", cfg.home_private_keep, ptr + 13) < 0 ) errExit("asprintf"); } else cfg.home_private_keep = ptr + 13; arg_private = 1; } else warning_feature_disabled("private-home"); #endif return 0; } else if (strcmp(ptr, "allusers") == 0) { arg_allusers = 1; return 0; } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; return 0; } else if (strcmp(ptr, "private-tmp") == 0) { arg_private_tmp = 1; return 0; } else if (strcmp(ptr, "nogroups") == 0) { arg_nogroups = 1; return 0; } else if (strcmp(ptr, "nosound") == 0) { arg_nosound = 1; return 0; } else if (strcmp(ptr, "novideo") == 0) { arg_novideo = 1; return 0; } else if (strcmp(ptr, "no3d") == 0) { arg_no3d = 1; return 0; } else if (strcmp(ptr, "allow-private-blacklist") == 0) { arg_allow_private_blacklist = 1; return 0; } else if (strcmp(ptr, "netfilter") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) arg_netfilter = 1; else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "netfilter ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter = 1; arg_netfilter_file = strdup(ptr + 10); if (!arg_netfilter_file) errExit("strdup"); check_netfilter_file(arg_netfilter_file); } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "netfilter6 ", 11) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter6 = 1; arg_netfilter6_file = strdup(ptr + 11); if (!arg_netfilter6_file) errExit("strdup"); check_netfilter_file(arg_netfilter6_file); } else warning_feature_disabled("networking"); #endif return 0; } else if (strcmp(ptr, "net none") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_nonetwork = 1; cfg.bridge0.configured = 0; cfg.bridge1.configured = 0; cfg.bridge2.configured = 0; cfg.bridge3.configured = 0; cfg.interface0.configured = 0; cfg.interface1.configured = 0; cfg.interface2.configured = 0; cfg.interface3.configured = 0; } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "net ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { #ifdef HAVE_NETWORK_RESTRICTED // compile time restricted networking if (getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } #endif // run time restricted networking if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } if (strcmp(ptr + 4, "lo") == 0) { fprintf(stderr, "Error: cannot attach to lo device\n"); exit(1); } Bridge *br; if (cfg.bridge0.configured == 0) br = &cfg.bridge0; else if (cfg.bridge1.configured == 0) br = &cfg.bridge1; else if (cfg.bridge2.configured == 0) br = &cfg.bridge2; else if (cfg.bridge3.configured == 0) br = &cfg.bridge3; else { fprintf(stderr, "Error: maximum 4 network devices are allowed\n"); exit(1); } net_configure_bridge(br, ptr + 4); } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "veth-name ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } br->veth_name = strdup(ptr + 10); if (br->veth_name == NULL) errExit("strdup"); if (*br->veth_name == '\0') { fprintf(stderr, "Error: no veth-name configured\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "iprange ", 8) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->iprange_start || br->iprange_end) { fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n"); exit(1); } // parse option arguments char *firstip = ptr + 8; char *secondip = firstip; while (*secondip != '\0') { if (*secondip == ',') break; secondip++; } if (*secondip == '\0') { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } *secondip = '\0'; secondip++; // check addresses if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) || br->iprange_start >= br->iprange_end) { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) { fprintf(stderr, "Error: IP range addresses not in network range\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "mac ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (mac_not_zero(br->macsandbox)) { fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n"); exit(1); } // read the address if (atomac(ptr + 4, br->macsandbox)) { fprintf(stderr, "Error: invalid MAC address\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "mtu ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) { fprintf(stderr, "Error: invalid mtu value\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "ip ", 3) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ipsandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined if (strcmp(ptr + 3, "none") == 0) br->arg_ip_none = 1; else { if (atoip(ptr + 3, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "ip6 ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined // todo: verify ipv6 syntax br->ip6sandbox = ptr + 4; // if (atoip(argv[i] + 5, &br->ipsandbox)) { // fprintf(stderr, "Error: invalid IP address\n"); // exit(1); // } } else warning_feature_disabled("networking"); #endif return 0; } else if (strncmp(ptr, "defaultgw ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { if (atoip(ptr + 10, &cfg.defaultgw)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } else warning_feature_disabled("networking"); #endif return 0; } if (strcmp(ptr, "apparmor") == 0) { #ifdef HAVE_APPARMOR arg_apparmor = 1; #endif return 0; } if (strncmp(ptr, "protocol ", 9) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { if (cfg.protocol) { fwarning("a protocol list is present, the new list \"%s\" will not be installed\n", ptr + 9); return 0; } // store list cfg.protocol = strdup(ptr + 9); if (!cfg.protocol) errExit("strdup"); } else warning_feature_disabled("seccomp"); #endif return 0; } if (strncmp(ptr, "env ", 4) == 0) { env_store(ptr + 4, SETENV); return 0; } if (strncmp(ptr, "rmenv ", 6) == 0) { env_store(ptr + 6, RMENV); return 0; } // seccomp drop list on top of default list if (strncmp(ptr, "seccomp ", 8) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list = seccomp_check_list(ptr + 8); } else if (!arg_quiet) warning_feature_disabled("seccomp"); #endif return 0; } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_drop = seccomp_check_list(ptr + 13); } else warning_feature_disabled("seccomp"); #endif return 0; } // seccomp keep list if (strncmp(ptr, "seccomp.keep ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_keep= seccomp_check_list(ptr + 13); } else warning_feature_disabled("seccomp"); #endif return 0; } // caps drop list if (strncmp(ptr, "caps.drop ", 10) == 0) { arg_caps_drop = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify caps list and exit if problems caps_check_list(arg_caps_list, NULL); return 0; } // caps keep list if (strncmp(ptr, "caps.keep ", 10) == 0) { arg_caps_keep = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify caps list and exit if problems caps_check_list(arg_caps_list, NULL); return 0; } // hostname if (strncmp(ptr, "hostname ", 9) == 0) { cfg.hostname = ptr + 9; return 0; } // hosts-file if (strncmp(ptr, "hosts-file ", 11) == 0) { cfg.hosts_file = fs_check_hosts_file(ptr + 11); return 0; } // dns if (strncmp(ptr, "dns ", 4) == 0) { uint32_t dns; if (atoip(ptr + 4, &dns)) { fprintf(stderr, "Error: invalid DNS server IP address\n"); return 1; } if (cfg.dns1 == 0) cfg.dns1 = dns; else if (cfg.dns2 == 0) cfg.dns2 = dns; else if (cfg.dns3 == 0) cfg.dns3 = dns; else { fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); return 1; } return 0; } // cpu affinity if (strncmp(ptr, "cpu ", 4) == 0) { read_cpu_list(ptr + 4); return 0; } // nice value if (strncmp(ptr, "nice ", 4) == 0) { cfg.nice = atoi(ptr + 5); if (getuid() != 0 &&cfg.nice < 0) cfg.nice = 0; arg_nice = 1; return 0; } // cgroup if (strncmp(ptr, "cgroup ", 7) == 0) { set_cgroup(ptr + 7); return 0; } // writable-etc if (strcmp(ptr, "writable-etc") == 0) { if (cfg.etc_private_keep) { fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n"); exit(1); } arg_writable_etc = 1; return 0; } if (strcmp(ptr, "machine-id") == 0) { arg_machineid = 1; return 0; } // writable-var if (strcmp(ptr, "writable-var") == 0) { arg_writable_var = 1; return 0; } if (strcmp(ptr, "writable-var-log") == 0) { arg_writable_var_log = 1; return 0; } // private directory if (strncmp(ptr, "private ", 8) == 0) { cfg.home_private = ptr + 8; fs_check_private_dir(); arg_private = 1; return 0; } if (strcmp(ptr, "x11 none") == 0) { arg_x11_block = 1; return 0; } if (strcmp(ptr, "x11 xephyr") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start_xephyr(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11 xorg") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) arg_x11_xorg = 1; else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11 xpra") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start_xpra(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11 xvfb") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start_xvfb(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } if (strcmp(ptr, "x11") == 0) { #ifdef HAVE_X11 if (checkcfg(CFG_X11)) { char *x11env = getenv("FIREJAIL_X11"); if (x11env && strcmp(x11env, "yes") == 0) { return 0; } else { // start x11 x11_start(cfg.original_argc, cfg.original_argv); exit(0); } } else warning_feature_disabled("x11"); #endif return 0; } // private /etc list of files and directories if (strncmp(ptr, "private-etc ", 12) == 0) { if (arg_writable_etc) { fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); exit(1); } if (cfg.etc_private_keep) { if ( asprintf(&cfg.etc_private_keep, "%s,%s", cfg.etc_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.etc_private_keep = ptr + 12; } arg_private_etc = 1; return 0; } // private /opt list of files and directories if (strncmp(ptr, "private-opt ", 12) == 0) { if (cfg.opt_private_keep) { if ( asprintf(&cfg.opt_private_keep, "%s,%s", cfg.opt_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.opt_private_keep = ptr + 12; } arg_private_opt = 1; return 0; } // private /srv list of files and directories if (strncmp(ptr, "private-srv ", 12) == 0) { if (cfg.srv_private_keep) { if ( asprintf(&cfg.srv_private_keep, "%s,%s", cfg.srv_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.srv_private_keep = ptr + 12; } arg_private_srv = 1; return 0; } // private /bin list of files if (strncmp(ptr, "private-bin ", 12) == 0) { if (cfg.bin_private_keep) { if ( asprintf(&cfg.bin_private_keep, "%s,%s", cfg.bin_private_keep, ptr + 12) < 0 ) errExit("asprintf"); } else { cfg.bin_private_keep = ptr + 12; } arg_private_bin = 1; return 0; } #ifdef HAVE_OVERLAYFS if (strncmp(ptr, "overlay-named ", 14) == 0) { if (checkcfg(CFG_OVERLAYFS)) { if (cfg.chrootdir) { fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n"); exit(1); } struct stat s; if (stat("/proc/sys/kernel/grsecurity", &s) == 0) { fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n"); exit(1); } arg_overlay = 1; arg_overlay_keep = 1; arg_overlay_reuse = 1; char *subdirname = ptr + 14; if (subdirname == '\0') { fprintf(stderr, "Error: invalid overlay option\n"); exit(1); } // check name invalid_filename(subdirname); if (strstr(subdirname, "..") || strstr(subdirname, "/")) { fprintf(stderr, "Error: invalid overlay name\n"); exit(1); } cfg.overlay_dir = fs_check_overlay_dir(subdirname, arg_overlay_reuse); } return 0; } else if (strcmp(ptr, "overlay-tmpfs") == 0) { if (checkcfg(CFG_OVERLAYFS)) { if (cfg.chrootdir) { fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n"); exit(1); } struct stat s; if (stat("/proc/sys/kernel/grsecurity", &s) == 0) { fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n"); exit(1); } arg_overlay = 1; return 0; } } else if (strcmp(ptr, "overlay") == 0) { if (checkcfg(CFG_OVERLAYFS)) { if (cfg.chrootdir) { fprintf(stderr, "Error: --overlay and --chroot options are mutually exclusive\n"); exit(1); } struct stat s; if (stat("/proc/sys/kernel/grsecurity", &s) == 0) { fprintf(stderr, "Error: --overlay option is not available on Grsecurity systems\n"); exit(1); } arg_overlay = 1; arg_overlay_keep = 1; char *subdirname; if (asprintf(&subdirname, "%d", getpid()) == -1) errExit("asprintf"); cfg.overlay_dir = fs_check_overlay_dir(subdirname, arg_overlay_reuse); free(subdirname); return 0; } } #endif // filesystem bind if (strncmp(ptr, "bind ", 5) == 0) { #ifdef HAVE_BIND if (checkcfg(CFG_BIND)) { if (getuid() != 0) { fprintf(stderr, "Error: --bind option is available only if running as root\n"); exit(1); } // extract two directories char *dname1 = ptr + 5; char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories if (dname2 == NULL) { fprintf(stderr, "Error: missing second directory for bind\n"); exit(1); } // check directories invalid_filename(dname1); invalid_filename(dname2); if (strstr(dname1, "..") || strstr(dname2, "..")) { fprintf(stderr, "Error: invalid file name.\n"); exit(1); } if (is_link(dname1) || is_link(dname2)) { fprintf(stderr, "Symbolic links are not allowed for bind command\n"); exit(1); } // insert comma back *(dname2 - 1) = ','; return 1; } else warning_feature_disabled("bind"); #endif return 0; } // rlimit if (strncmp(ptr, "rlimit", 6) == 0) { if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { check_unsigned(ptr + 14, "Error: invalid rlimit in profile file: "); sscanf(ptr + 14, "%llu", &cfg.rlimit_nofile); arg_rlimit_nofile = 1; } else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { check_unsigned(ptr + 13, "Error: invalid rlimit in profile file: "); sscanf(ptr + 13, "%llu", &cfg.rlimit_nproc); arg_rlimit_nproc = 1; } else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { check_unsigned(ptr + 13, "Error: invalid rlimit in profile file: "); sscanf(ptr + 13, "%llu", &cfg.rlimit_fsize); arg_rlimit_fsize = 1; } else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { check_unsigned(ptr + 18, "Error: invalid rlimit in profile file: "); sscanf(ptr + 18, "%llu", &cfg.rlimit_sigpending); arg_rlimit_sigpending = 1; } else { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } return 0; } if (strncmp(ptr, "join-or-start ", 14) == 0) { // try to join by name only pid_t pid; if (!name2pid(ptr + 14, &pid)) { if (!cfg.shell && !arg_shell_none) cfg.shell = guess_shell(); // find first non-option arg int i; for (i = 1; i < cfg.original_argc && strncmp(cfg.original_argv[i], "--", 2) != 0; i++); join(pid, cfg.original_argc,cfg.original_argv, i + 1); exit(0); } // set sandbox name and start normally cfg.name = ptr + 14; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } // rest of filesystem if (strncmp(ptr, "blacklist ", 10) == 0) ptr += 10; else if (strncmp(ptr, "blacklist-nolog ", 16) == 0) ptr += 16; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "whitelist ", 10) == 0) { #ifdef HAVE_WHITELIST if (checkcfg(CFG_WHITELIST)) { arg_whitelist = 1; ptr += 10; } else return 0; #else return 0; #endif } else if (strncmp(ptr, "nowhitelist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "read-write ", 11) == 0) ptr += 11; else if (strncmp(ptr, "noexec ", 7) == 0) ptr += 7; else if (strncmp(ptr, "tmpfs ", 6) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n"); exit(1); } ptr += 6; } else { if (lineno == 0) fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } // some characters just don't belong in filenames invalid_filename(ptr); if (strstr(ptr, "..")) { if (lineno == 0) fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } return 1; }
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(); } //**************************** // netfilter //**************************** if (arg_netfilter && any_bridge_configured()) { // assuming by default the client filter netfilter(arg_netfilter_file); } //**************************** // trace pre-install //**************************** if (arg_trace) fs_trace_preload(); //**************************** // configure filesystem //**************************** #ifdef HAVE_CHROOT if (cfg.chrootdir) { fs_chroot(cfg.chrootdir); // 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; if (arg_seccomp_list_drop) { free(arg_seccomp_list_drop); arg_seccomp_list_drop = NULL; } if (arg_seccomp_list_keep) { free(arg_seccomp_list_keep); arg_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; printf("Dropping all Linux capabilities and enforcing default seccomp filter\n"); } //**************************** // trace pre-install, this time inside chroot //**************************** if (arg_trace) 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); } //**************************** // apply the profile file //**************************** if (cfg.profile) fs_blacklist(cfg.homedir); //**************************** // private mode //**************************** if (arg_private) { if (cfg.home_private) // --private= fs_private_homedir(); else if (cfg.home_private_keep) // --private-home= fs_private_home_list(); else // --private fs_private(); } if (arg_private_dev) fs_private_dev(); if (arg_private_etc) fs_private_etc_list(); //**************************** // install trace //**************************** if (arg_trace) fs_trace(); //**************************** // update /proc, /dev, /boot directorymy //**************************** fs_proc_sys_dev_boot(); //**************************** // 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()) { // 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"); } if (arg_debug) printf("Network namespace enabled\n"); } // if any dns server is configured, it is time to set it now fs_resolvconf(); // print network configuration if (any_bridge_configured() || cfg.defaultgw || cfg.dns1) { printf("\n"); if (any_bridge_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"); } //**************************** // start executable //**************************** 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 // fix qt 4.8 if (setenv("QT_X11_NO_MITSHM", "1", 1) < 0) errExit("setenv"); if (setenv("container", "firejail", 1) < 0) // LXC sets container=lxc, errExit("setenv"); if (arg_zsh && setenv("SHELL", "/usr/bin/zsh", 1) < 0) errExit("setenv"); if (arg_csh && setenv("SHELL", "/bin/csh", 1) < 0) errExit("setenv"); if (cfg.shell && setenv("SHELL", cfg.shell, 1) < 0) errExit("setenv"); // set prompt color to green //export PS1='\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] ' if (setenv("PROMPT_COMMAND", "export PS1=\"\\[\\e[1;32m\\][\\u@\\h \\W]\\$\\[\\e[0m\\] \"", 1) < 0) errExit("setenv"); // set user-supplied environment variables env_apply(); // set capabilities if (!arg_noroot) set_caps(); // set rlimits set_rlimits(); // set seccomp #ifdef HAVE_SECCOMP // if a keep list is available, disregard the drop list if (arg_seccomp == 1) { if (arg_seccomp_list_keep) seccomp_filter_keep(); // this will also save the fmyilter to MNT_DIR/seccomp file else seccomp_filter_drop(); // this will also save the filter to MNT_DIR/seccomp file } #endif // set cpu affinity if (cfg.cpus) { save_cpu(); // save cpu affinity mask to MNT_DIR/cpu file set_cpu_affinity(); } // save cgroup in MNT_DIR/cgroup 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("User namespace (noroot) installed\n"); } //**************************************** // start the program without using a shell //**************************************** if (arg_shell_none) { if (arg_debug) { int i; for (i = cfg.original_program_index; i < cfg.original_argc; i++) { if (cfg.original_argv[i] == NULL) break; printf("execvp argument %d: %s\n", i - cfg.original_program_index, cfg.original_argv[i]); } } if (!arg_command) printf("Child process initialized\n"); execvp(cfg.original_argv[cfg.original_program_index], &cfg.original_argv[cfg.original_program_index]); } //**************************************** // start the program using a shell //**************************************** else { // choose the shell requested by the user, or use bash as default char *sh; if (cfg.shell) sh = cfg.shell; else if (arg_zsh) sh = "/usr/bin/zsh"; else if (arg_csh) sh = "/bin/csh"; else sh = "/bin/bash"; char *arg[5]; int index = 0; arg[index++] = sh; arg[index++] = "-c"; assert(cfg.command_line); if (arg_debug) printf("Starting %s\n", cfg.command_line); if (arg_doubledash) arg[index++] = "--"; arg[index++] = cfg.command_line; arg[index] = NULL; assert(index < 5); if (arg_debug) { char *msg; if (asprintf(&msg, "sandbox %d, execvp into %s", sandbox_pid, cfg.command_line) == -1) errExit("asprintf"); logmsg(msg); free(msg); } if (arg_debug) { int i; for (i = 0; i < 5; i++) { if (arg[i] == NULL) break; printf("execvp argument %d: %s\n", i, arg[i]); } } if (!arg_command) printf("Child process initialized\n"); execvp(sh, arg); } perror("execvp"); return 0; }