static void mkdir_recursive(char *path) { char *subdir = NULL; struct stat s; if (chdir("/")) { fprintf(stderr, "Error: can't chdir to /"); return; } subdir = strtok(path, "/"); while(subdir) { if (stat(subdir, &s) == -1) { /* coverity[toctou] */ if (mkdir(subdir, 0700) == -1) { fwarning("cannot create %s directory\n", subdir); return; } } else if (!S_ISDIR(s.st_mode)) { fwarning("'%s exists, but is not a directory\n", subdir); return; } if (chdir(subdir)) { fprintf(stderr, "Error: can't chdir to %s", subdir); return; } subdir = strtok(NULL, "/"); } }
void fs_var_lock(void) { if (is_dir("/var/lock")) { if (arg_debug) printf("Mounting tmpfs on /var/lock\n"); if (mount("tmpfs", "/var/lock", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=1777,gid=0") < 0) errExit("mounting /lock"); fs_logger("tmpfs /var/lock"); } else { char *lnk = realpath("/var/lock", NULL); if (lnk) { if (!is_dir(lnk)) { // create directory mkdir_attr(lnk, S_IRWXU|S_IRWXG|S_IRWXO, 0, 0); } if (arg_debug) printf("Mounting tmpfs on %s on behalf of /var/lock\n", lnk); if (mount("tmpfs", lnk, "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=1777,gid=0") < 0) errExit("mounting /var/lock"); free(lnk); fs_logger("tmpfs /var/lock"); } else { fwarning("/var/lock not mounted\n"); dbg_test_dir("/var/lock"); } } }
void netns_mounts(const char *nsname) { char *etcdir = netns_etc_dir(nsname); char *netns_name, *etc_name; struct dirent *entry; DIR *dir; dir = opendir(etcdir); if (!dir) { free(etcdir); return; } while ((entry = readdir(dir))) { if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) continue; if (asprintf(&netns_name, "%s/%s", etcdir, entry->d_name) < 0 || asprintf(&etc_name, "/etc/%s", entry->d_name) < 0) errExit("asprintf"); if (mount(netns_name, etc_name, "none", MS_BIND, 0) < 0) { fwarning("bind %s -> %s failed: %s\n", netns_name, etc_name, strerror(errno)); } free(netns_name); free(etc_name); } closedir(dir); free(etcdir); }
void fs_dev_disable_sound(void) { unsigned i = 0; while (dev[i].dev_fname != NULL) { if (dev[i].type == DEV_SOUND) disable_file_or_dir(dev[i].dev_fname); i++; } // disable all jack sockets in /dev/shm glob_t globbuf; int globerr = glob("/dev/shm/jack*", GLOB_NOSORT, NULL, &globbuf); if (globerr) return; for (i = 0; i < globbuf.gl_pathc; i++) { char *path = globbuf.gl_pathv[i]; assert(path); if (is_link(path)) { fwarning("skipping nosound for %s because it is a symbolic link\n", path); continue; } disable_file_or_dir(path); } globfree(&globbuf); }
static void mount_dev_shm(void) { mkdir_attr("/dev/shm", 01777, 0, 0); int rv = mount(RUN_DEV_DIR "/shm", "/dev/shm", "none", MS_BIND, "mode=01777,gid=0"); if (rv == -1) { fwarning("cannot mount the old /dev/shm in private-dev\n"); dbg_test_dir(RUN_DEV_DIR "/shm"); empty_dev_shm(); return; } }
void dbus_session_disable(void) { if (!checkcfg(CFG_DBUS)) { fwarning("D-Bus handling is disabled in Firejail configuration file\n"); return; } char *path; if (asprintf(&path, "/run/user/%d/bus", getuid()) == -1) errExit("asprintf"); char *env_var; if (asprintf(&env_var, "DBUS_SESSION_BUS_ADDRESS=unix:path=%s", path) == -1) errExit("asprintf"); // set a new environment variable: DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/<UID>/bus if (setenv("DBUS_SESSION_BUS_ADDRESS", env_var, 1) == -1) { fprintf(stderr, "Error: cannot modify DBUS_SESSION_BUS_ADDRESS required by --nodbus\n"); exit(1); } // blacklist the path disable_file_or_dir(path); free(path); free(env_var); // look for a possible abstract unix socket // --net=none if (arg_nonetwork) return; // --net=eth0 if (any_bridge_configured()) return; // --protocol=unix #ifdef HAVE_SECCOMP if (cfg.protocol && !strstr(cfg.protocol, "unix")) return; #endif fwarning("An abstract unix socket for session D-BUS might still be available. Use --net or remove unix from --protocol set.\n"); }
// configure bridge structure // - extract ip address and mask from the bridge interface void net_configure_bridge(Bridge *br, char *dev_name) { assert(br); assert(dev_name); br->dev = dev_name; // check the bridge device exists char sysbridge[30 + strlen(br->dev)]; sprintf(sysbridge, "/sys/class/net/%s/bridge", br->dev); struct stat s; int rv = stat(sysbridge, &s); if (rv == 0) { // this is a bridge device br->macvlan = 0; } else { // is this a regular Ethernet interface if (if_nametoindex(br->dev) > 0) { br->macvlan = 1; char *newname; if (asprintf(&newname, "%s-%u", br->devsandbox, getpid()) == -1) errExit("asprintf"); br->devsandbox = newname; } else { fprintf(stderr, "Error: cannot find network device %s\n", br->dev); exit(1); } } // allow unconfigured interfaces if (net_get_if_addr(br->dev, &br->ip, &br->mask, br->mac, &br->mtu)) { fwarning("the network interface %s is not configured\n", br->dev); br->configured = 1; br->arg_ip_none = 1; return; } if (arg_debug) { if (br->macvlan == 0) printf("Bridge device %s at %d.%d.%d.%d/%d\n", br->dev, PRINT_IP(br->ip), mask2bits(br->mask)); else printf("macvlan parent device %s at %d.%d.%d.%d/%d\n", br->dev, PRINT_IP(br->ip), mask2bits(br->mask)); } uint32_t range = ~br->mask + 1; // the number of potential addresses // this software is not supported for /31 networks if (range < 4) { fprintf(stderr, "Error: the software is not supported for /31 networks\n"); exit(1); } br->configured = 1; }
static void sanitize_home(void) { assert(getuid() != 0); // this code works only for regular users if (arg_debug) printf("Cleaning /home directory\n"); struct stat s; if (stat(cfg.homedir, &s) == -1) { // cannot find home directory, just return fwarning("cannot find home directory\n"); return; } if (mkdir(RUN_WHITELIST_HOME_DIR, 0755) == -1) errExit("mkdir"); // keep a copy of the user home directory if (mount(cfg.homedir, RUN_WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs in the new home if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0) errExit("mount tmpfs"); fs_logger("tmpfs /home"); // create user home directory if (mkdir(cfg.homedir, 0755) == -1) { if (mkpath_as_root(cfg.homedir)) errExit("mkpath"); if (mkdir(cfg.homedir, 0755) == -1) errExit("mkdir"); } fs_logger2("mkdir", cfg.homedir); // set mode and ownership if (set_perms(cfg.homedir, s.st_uid, s.st_gid, s.st_mode)) errExit("set_perms"); // mount user home directory if (mount(RUN_WHITELIST_HOME_DIR, cfg.homedir, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mask home dir under /run if (mount("tmpfs", RUN_WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0) errExit("mount tmpfs"); fs_logger2("tmpfs", RUN_WHITELIST_HOME_DIR); if (!arg_private) fs_logger2("whitelist", cfg.homedir); }
void fs_var_tmp(void) { struct stat s; if (stat("/var/tmp", &s) == 0) { if (!is_link("/var/tmp")) { if (arg_debug) printf("Mounting tmpfs on /var/tmp\n"); if (mount("tmpfs", "/var/tmp", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=1777,gid=0") < 0) errExit("mounting /var/tmp"); fs_logger("tmpfs /var/tmp"); } } else { fwarning("/var/tmp not mounted\n"); dbg_test_dir("/var/tmp"); } }
void fs_var_utmp(void) { struct stat s; // extract utmp group id gid_t utmp_group = 0; if (stat(UTMP_FILE, &s) == 0) utmp_group = s.st_gid; else { fwarning("cannot find /var/run/utmp\n"); return; } // create a new utmp file if (arg_debug) printf("Create the new utmp file\n"); /* coverity[toctou] */ FILE *fp = fopen(RUN_UTMP_FILE, "w"); if (!fp) errExit("fopen"); // read current utmp struct utmp *u; struct utmp u_boot; setutent(); while ((u = getutent()) != NULL) { if (u->ut_type == BOOT_TIME) { memcpy(&u_boot, u, sizeof(u_boot)); u_boot.ut_tv.tv_sec = (unsigned) time(NULL); } } endutent(); // save new utmp file int rv = fwrite(&u_boot, sizeof(u_boot), 1, fp); (void) rv; SET_PERMS_STREAM(fp, 0, utmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH); fclose(fp); // mount the new utmp file if (arg_debug) printf("Mount the new utmp file\n"); if (mount(RUN_UTMP_FILE, UTMP_FILE, NULL, MS_BIND|MS_NOSUID|MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) errExit("mount bind utmp"); fs_logger("create /var/run/utmp"); }
static void deventry_mount(void) { int i = 0; while (dev[i].dev_fname != NULL) { struct stat s; if (stat(dev[i].run_fname, &s) == 0) { // check device type and subsystem configuration if ((dev[i].type == DEV_SOUND && arg_nosound == 0) || (dev[i].type == DEV_3D && arg_no3d == 0) || (dev[i].type == DEV_VIDEO && arg_novideo == 0) || (dev[i].type == DEV_TV && arg_notv == 0) || (dev[i].type == DEV_DVD && arg_nodvd == 0) || (dev[i].type == DEV_U2F && arg_nou2f == 0)) { int dir = is_dir(dev[i].run_fname); if (arg_debug) printf("mounting %s %s\n", dev[i].run_fname, (dir)? "directory": "file"); if (dir) { mkdir_attr(dev[i].dev_fname, 0755, 0, 0); } else { struct stat s; if (stat(dev[i].run_fname, &s) == -1) { if (arg_debug) fwarning("cannot stat %s file\n", dev[i].run_fname); i++; continue; } FILE *fp = fopen(dev[i].dev_fname, "w"); if (fp) { fprintf(fp, "\n"); SET_PERMS_STREAM(fp, s.st_uid, s.st_gid, s.st_mode); fclose(fp); } } if (mount(dev[i].run_fname, dev[i].dev_fname, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting dev file"); fs_logger2("whitelist", dev[i].dev_fname); } } i++; } }
void fs_var_log(void) { build_list("/var/log"); // note: /var/log is not created here, if it does not exist, this section fails. // create /var/log if it doesn't exit if (is_dir("/var/log")) { // extract group id for /var/log/wtmp struct stat s; gid_t wtmp_group = 0; if (stat("/var/log/wtmp", &s) == 0) wtmp_group = s.st_gid; // mount a tmpfs on top of /var/log if (arg_debug) printf("Mounting tmpfs on /var/log\n"); if (mount("tmpfs", "/var/log", "tmpfs", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting /var/log"); fs_logger("tmpfs /var/log"); build_dirs(); release_all(); // create an empty /var/log/wtmp file /* coverity[toctou] */ FILE *fp = fopen("/var/log/wtmp", "w"); if (fp) { SET_PERMS_STREAM(fp, 0, wtmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH); fclose(fp); } fs_logger("touch /var/log/wtmp"); // create an empty /var/log/btmp file fp = fopen("/var/log/btmp", "w"); if (fp) { SET_PERMS_STREAM(fp, 0, wtmp_group, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP); fclose(fp); } fs_logger("touch /var/log/btmp"); } else fwarning("cannot hide /var/log directory\n"); }
static void clean_dir(const char *name, int *pidarr, int start_pid, int max_pids) { DIR *dir; if (!(dir = opendir(name))) { fwarning("cannot clean %s directory\n", name); return; // we live to fight another day! } // clean leftover files struct dirent *entry; char *end; while ((entry = readdir(dir)) != NULL) { pid_t pid = strtol(entry->d_name, &end, 10); pid %= max_pids; if (end == entry->d_name || *end) continue; if (pid < start_pid) continue; if (pidarr[pid] == 0) delete_run_files(pid); } closedir(dir); }
// 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; }
static void warning_feature_disabled(const char *feature) { fwarning("%s feature is disabled in Firejail configuration file\n", feature); }
static void sanitize_group(void) { struct stat s; if (stat("/etc/group", &s) == -1) return; assert(gid_min); if (arg_debug) printf("Sanitizing /etc/group, GID_MIN %d\n", gid_min); if (is_link("/etc/group")) { fprintf(stderr, "Error: invalid /etc/group\n"); exit(1); } FILE *fpin = NULL; FILE *fpout = NULL; // open files /* coverity[toctou] */ fpin = fopen("/etc/group", "r"); if (!fpin) goto errout; fpout = fopen(RUN_GROUP_FILE, "w"); if (!fpout) goto errout; // read the file line by line char buf[MAXBUF]; gid_t mygid = getgid(); while (fgets(buf, MAXBUF, fpin)) { // comments and empty lines if (*buf == '\0' || *buf == '#') continue; // sample line: // pulse:x:115:netblue,bingo // drop lines with uid > 1000 and not the current user group char *ptr = buf; // advance to uid while (*ptr != ':' && *ptr != '\0') ptr++; if (*ptr == '\0') goto errout; ptr++; while (*ptr != ':' && *ptr != '\0') ptr++; if (*ptr == '\0') goto errout; ptr++; if (*ptr == '\0') goto errout; // process uid int gid; int rv = sscanf(ptr, "%d:", &gid); if (rv == 0 || gid < 0) goto errout; assert(gid_min); if (gid < gid_min || gid == 65534) { // on Debian platforms 65534 is group nogroup if (copy_line(fpout, buf, ptr)) goto errout; continue; } if ((gid_t) gid != mygid) { continue; // skip line } if (copy_line(fpout, buf, ptr)) goto errout; } fclose(fpin); SET_PERMS_STREAM(fpout, 0, 0, 0644); fclose(fpout); // mount-bind tne new group file if (mount(RUN_GROUP_FILE, "/etc/group", "none", MS_BIND, "mode=400,gid=0") < 0) errExit("mount"); fs_logger("create /etc/group"); return; errout: fwarning("failed to clean up /etc/group\n"); if (fpin) fclose(fpin); if (fpout) fclose(fpout); }
static void sanitize_passwd(void) { struct stat s; if (stat("/etc/passwd", &s) == -1) return; assert(uid_min); if (arg_debug) printf("Sanitizing /etc/passwd, UID_MIN %d\n", uid_min); if (is_link("/etc/passwd")) { fprintf(stderr, "Error: invalid /etc/passwd\n"); exit(1); } FILE *fpin = NULL; FILE *fpout = NULL; // open files /* coverity[toctou] */ fpin = fopen("/etc/passwd", "r"); if (!fpin) goto errout; fpout = fopen(RUN_PASSWD_FILE, "w"); if (!fpout) goto errout; // read the file line by line char buf[MAXBUF]; uid_t myuid = getuid(); while (fgets(buf, MAXBUF, fpin)) { // comments and empty lines if (*buf == '\0' || *buf == '#') continue; // sample line: // www-data:x:33:33:www-data:/var/www:/bin/sh // drop lines with uid > 1000 and not the current user char *ptr = buf; // advance to uid while (*ptr != ':' && *ptr != '\0') ptr++; if (*ptr == '\0') goto errout; char *ptr1 = ptr; ptr++; while (*ptr != ':' && *ptr != '\0') ptr++; if (*ptr == '\0') goto errout; ptr++; if (*ptr == '\0') goto errout; // process uid int uid; int rv = sscanf(ptr, "%d:", &uid); if (rv == 0 || uid < 0) goto errout; assert(uid_min); if (uid < uid_min || uid == 65534) { // on Debian platforms user nobody is 65534 fprintf(fpout, "%s", buf); continue; } if ((uid_t) uid != myuid) { // store user name - necessary to process /etc/group *ptr1 = '\0'; char *user = strdup(buf); if (!user) errExit("malloc"); ulist_add(user); continue; // skip line } fprintf(fpout, "%s", buf); } fclose(fpin); SET_PERMS_STREAM(fpout, 0, 0, 0644); fclose(fpout); // mount-bind tne new password file if (mount(RUN_PASSWD_FILE, "/etc/passwd", "none", MS_BIND, "mode=400,gid=0") < 0) errExit("mount"); fs_logger("create /etc/passwd"); return; errout: fwarning("failed to clean up /etc/passwd\n"); if (fpin) fclose(fpin); if (fpout) fclose(fpout); }
void join(pid_t pid, int argc, char **argv, int index) { EUID_ASSERT(); char *homedir = cfg.homedir; extract_command(argc, argv, index); signal (SIGTERM, signal_handler); // if the pid is that of a firejail process, use the pid of the first child process EUID_ROOT(); char *comm = pid_proc_comm(pid); EUID_USER(); if (comm) { if (strcmp(comm, "firejail") == 0) { pid_t child; if (find_child(pid, &child) == 0) { pid = child; if (!arg_quiet) printf("Switching to pid %u, the first child process inside the sandbox\n", (unsigned) pid); } } free(comm); } // check privileges for non-root users uid_t uid = getuid(); if (uid != 0) { uid_t sandbox_uid = pid_get_uid(pid); if (uid != sandbox_uid) { fprintf(stderr, "Error: permission is denied to join a sandbox created by a different user.\n"); exit(1); } } EUID_ROOT(); // in user mode set caps seccomp, cpu, cgroup, etc if (getuid() != 0) { extract_caps_seccomp(pid); extract_cpu(pid); extract_cgroup(pid); extract_nogroups(pid); extract_user_namespace(pid); } // set cgroup if (cfg.cgroup) // not available for uid 0 set_cgroup(cfg.cgroup); // join namespaces if (arg_join_network) { if (join_namespace(pid, "net")) exit(1); } else if (arg_join_filesystem) { if (join_namespace(pid, "mnt")) exit(1); } else { if (join_namespace(pid, "ipc") || join_namespace(pid, "net") || join_namespace(pid, "pid") || join_namespace(pid, "uts") || join_namespace(pid, "mnt")) exit(1); } pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { // chroot into /proc/PID/root directory char *rootdir; if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) errExit("asprintf"); int rv; if (!arg_join_network) { rv = chroot(rootdir); // this will fail for processes in sandboxes not started with --chroot option if (rv == 0) printf("changing root to %s\n", rootdir); } prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); // kill the child in case the parent died if (chdir("/") < 0) errExit("chdir"); if (homedir) { struct stat s; if (stat(homedir, &s) == 0) { /* coverity[toctou] */ if (chdir(homedir) < 0) errExit("chdir"); } } // set cpu affinity if (cfg.cpus) // not available for uid 0 set_cpu_affinity(); // set caps filter if (apply_caps == 1) // not available for uid 0 caps_set(caps); #ifdef HAVE_SECCOMP // read cfg.protocol from file if (getuid() != 0) protocol_filter_load(RUN_PROTOCOL_CFG); if (cfg.protocol) { // not available for uid 0 seccomp_load(RUN_SECCOMP_PROTOCOL); // install filter } // set seccomp filter if (apply_seccomp == 1) // not available for uid 0 seccomp_load(RUN_SECCOMP_CFG); #endif // mount user namespace or drop privileges if (arg_noroot) { // not available for uid 0 if (arg_debug) printf("Joining user namespace\n"); if (join_namespace(1, "user")) exit(1); // user namespace resets capabilities // set caps filter if (apply_caps == 1) // not available for uid 0 caps_set(caps); } else drop_privs(arg_nogroups); // nogroups not available for uid 0 // set nice if (arg_nice) { errno = 0; int rv = nice(cfg.nice); (void) rv; if (errno) { fwarning("cannot set nice value\n"); errno = 0; } } env_defaults(); if (cfg.command_line == NULL) { assert(cfg.shell); cfg.command_line = cfg.shell; cfg.window_title = cfg.shell; } 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"); } } } start_application(); // it will never get here!!! } // wait for the child to finish waitpid(child, NULL, 0); flush_stdin(); exit(0); }