static int store_xauthority(void) { // put a copy of .Xauthority in XAUTHORITY_FILE fs_build_mnt_dir(); char *src; char *dest = RUN_XAUTHORITY_FILE; if (asprintf(&src, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); struct stat s; if (stat(src, &s) == 0) { if (is_link(src)) { fprintf(stderr, "Error: invalid .Xauthority file\n"); exit(1); } int rv = copy_file(src, dest); if (rv) { fprintf(stderr, "Warning: cannot transfer .Xauthority in private home directory\n"); return 0; } return 1; // file copied } return 0; }
void fs_private_etc_list(void) { char *private_list = cfg.etc_private_keep; assert(private_list); struct stat s; if (stat("/etc", &s) == -1) { fprintf(stderr, "Error: cannot find user /etc directory\n"); exit(1); } // create /tmp/firejail/mnt/etc directory fs_build_mnt_dir(); int rv = mkdir(RUN_ETC_DIR, 0755); if (rv == -1) errExit("mkdir"); if (chown(RUN_ETC_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_ETC_DIR, 0755) < 0) errExit("chmod"); // copy the list of files in the new etc directory // using a new child process without root privileges fs_logger_print(); // save the current log pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { if (arg_debug) printf("Copying files in the new etc directory:\n"); // elevate privileges - files in the new /etc directory belong to root if (setreuid(0, 0) < 0) errExit("setreuid"); if (setregid(0, 0) < 0) errExit("setregid"); // copy the list of files in the new home directory char *dlist = strdup(private_list); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); duplicate(ptr); while ((ptr = strtok(NULL, ",")) != NULL) duplicate(ptr); free(dlist); fs_logger_print(); exit(0); } // wait for the child to finish waitpid(child, NULL, 0); if (arg_debug) printf("Mount-bind %s on top of /etc\n", RUN_ETC_DIR); if (mount(RUN_ETC_DIR, "/etc", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); fs_logger("mount /etc"); }
static int store_asoundrc(void) { // put a copy of .Xauthority in XAUTHORITY_FILE fs_build_mnt_dir(); char *src; char *dest = RUN_ASOUNDRC_FILE; if (asprintf(&src, "%s/.asoundrc", cfg.homedir) == -1) errExit("asprintf"); struct stat s; if (stat(src, &s) == 0) { if (is_link(src)) { // make sure the real path of the file is inside the home directory char* rp = realpath(src, NULL); if (!rp) { fprintf(stderr, "Error: Cannot access %s\n", src); exit(1); } if (strncmp(rp, cfg.homedir, strlen(cfg.homedir)) != 0) { fprintf(stderr, "Error: .asoundrc is a symbolic link pointing to a file outside home directory\n"); exit(1); } free(rp); } int rv = copy_file(src, dest); if (rv) { fprintf(stderr, "Warning: cannot transfer .asoundrc in private home directory\n"); return 0; } return 1; // file copied } return 0; }
// grab a copy of cp command void fs_build_cp_command(void) { struct stat s; fs_build_mnt_dir(); if (stat(RUN_CP_COMMAND, &s)) { char* fname = realpath("/bin/cp", NULL); if (fname == NULL) { fprintf(stderr, "Error: /bin/cp not found\n"); exit(1); } if (stat(fname, &s)) { fprintf(stderr, "Error: /bin/cp not found\n"); exit(1); } if (is_link(fname)) { fprintf(stderr, "Error: invalid /bin/cp file\n"); exit(1); } int rv = copy_file(fname, RUN_CP_COMMAND); if (rv) { fprintf(stderr, "Error: cannot access /bin/cp\n"); exit(1); } /* coverity[toctou] */ if (chown(RUN_CP_COMMAND, 0, 0)) errExit("chown"); if (chmod(RUN_CP_COMMAND, 0755)) errExit("chmod"); free(fname); } }
void fs_trace(void) { // create /tmp/firejail/mnt directory fs_build_mnt_dir(); // create the new ld.so.preload file and mount-bind it if (arg_debug) printf("Create the new ld.so.preload file\n"); char *preload; if (asprintf(&preload, "%s/ld.so.preload", MNT_DIR) == -1) errExit("asprintf"); FILE *fp = fopen(preload, "w"); if (!fp) errExit("fopen"); fprintf(fp, "%s/lib/firejail/libtrace.so\n", PREFIX); fclose(fp); if (chown(preload, 0, 0) < 0) errExit("chown"); if (chmod(preload, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH ) < 0) errExit("chmod"); // mount the new preload file if (arg_debug) printf("Mount the new ld.so.preload file\n"); if (mount(preload, "/etc/ld.so.preload", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind ls.so.preload"); }
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 fprintf(stderr, "Warning: cannot find home directory\n"); return; } fs_build_mnt_dir(); 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 | MS_REC, "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 (chown(cfg.homedir, s.st_uid, s.st_gid) == -1) errExit("chown"); if (chmod(cfg.homedir, s.st_mode) == -1) errExit("chmod"); // 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 | MS_REC, "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_utmp(void) { struct stat s; // extract utmp group id gid_t utmp_group = 0; if (stat("/var/run/utmp", &s) == 0) utmp_group = s.st_gid; else { fprintf(stderr, "Warning: cannot find /var/run/utmp\n"); return; } // create /tmp/firejail/mnt directory fs_build_mnt_dir(); // create a new utmp file if (arg_debug) printf("Create the new utmp file\n"); char *utmp; if (asprintf(&utmp, "%s/utmp", MNT_DIR) == -1) errExit("asprintf"); FILE *fp = fopen(utmp, "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 fwrite(&u_boot, sizeof(u_boot), 1, fp); fclose(fp); if (chown(utmp, 0, utmp_group) < 0) errExit("chown"); if (chmod(utmp, S_IRUSR | S_IWRITE | S_IRGRP | S_IWGRP | S_IROTH ) < 0) errExit("chmod"); // mount the new utmp file if (arg_debug) printf("Mount the new utmp file\n"); if (mount(utmp, "/var/run/utmp", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind utmp"); }
void fs_mount_xauthority(void) { // put a copy of .Xauthority in MNT_DIR fs_build_mnt_dir(); char *src; char *dest; if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); if (asprintf(&src, "%s/.Xauthority", MNT_DIR) == -1) errExit("asprintf"); // bind-mount the file on top of /etc/hostname if (mount(src, "dest", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind ~/.Xauthority"); }
// disable shm in pulseaudio void pulseaudio_init(void) { struct stat s; // do we have pulseaudio in the system? if (stat("/etc/pulse/client.conf", &s) == -1) return; // create the new user pulseaudio directory fs_build_mnt_dir(); char *pulsedir; if (asprintf(&pulsedir, "%s/pulse", MNT_DIR) == -1) errExit("asprintf"); int rv = mkdir(pulsedir, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(pulsedir, getuid(), getgid()) < 0) errExit("chown"); if (chmod(pulsedir, 0700) < 0) errExit("chmod"); // create the new client.conf file char *pulsecfg = NULL; if (asprintf(&pulsecfg, "%s/client.conf", pulsedir) == -1) errExit("asprintf"); if (copy_file("/etc/pulse/client.conf", pulsecfg)) errExit("copy_file"); FILE *fp = fopen(pulsecfg, "a+"); if (!fp) errExit("fopen"); fprintf(fp, "%s", "\nenable-shm = no\n"); fclose(fp); if (chmod(pulsecfg, 0644) == -1) errExit("chmod"); if (chown(pulsecfg, getuid(), getgid()) == -1) errExit("chown"); // set environment if (setenv("PULSE_CLIENTCONFIG", pulsecfg, 1) < 0) errExit("setenv"); free(pulsecfg); free(pulsedir); }
void protocol_filter_save(void) { // save protocol filter configuration in PROTOCOL_CFG fs_build_mnt_dir(); FILE *fp = fopen(RUN_PROTOCOL_CFG, "w"); if (!fp) errExit("fopen"); fprintf(fp, "%s\n", cfg.protocol); fclose(fp); if (chmod(RUN_PROTOCOL_CFG, 0600) < 0) errExit("chmod"); if (chown(RUN_PROTOCOL_CFG, 0, 0) < 0) errExit("chown"); }
// save seccomp filter in /tmp/firejail/mnt/seccomp static void write_seccomp_file(void) { fs_build_mnt_dir(); assert(sfilter); int fd = open(RUN_SECCOMP_CFG, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); if (fd == -1) errExit("open"); if (arg_debug) printf("Save seccomp filter, size %u bytes\n", (unsigned) (sfilter_index * sizeof(struct sock_filter))); errno = 0; ssize_t sz = write(fd, sfilter, sfilter_index * sizeof(struct sock_filter)); if (sz != (ssize_t)(sfilter_index * sizeof(struct sock_filter))) { fprintf(stderr, "Error: cannot save seccomp filter\n"); exit(1); } close(fd); if (chown(RUN_SECCOMP_CFG, 0, 0) < 0) errExit("chown"); }
static int store_xauthority(void) { // put a copy of .Xauthority in MNT_DIR fs_build_mnt_dir(); char *src; char *dest; if (asprintf(&src, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); if (asprintf(&dest, "%s/.Xauthority", MNT_DIR) == -1) errExit("asprintf"); struct stat s; if (stat(src, &s) == 0) { int rv = copy_file(src, dest); if (rv) { exechelp_logerrv("firejail", FIREJAIL_WARNING, "Error: cannot transfer .Xauthority in private home directory\n"); return 0; } return 1; // file copied } return 0; }
static int store_xauthority(void) { // put a copy of .Xauthority in MNT_DIR fs_build_mnt_dir(); char *src; char *dest; if (asprintf(&src, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); if (asprintf(&dest, "%s/.Xauthority", MNT_DIR) == -1) errExit("asprintf"); struct stat s; if (stat(src, &s) == 0) { int rv = copy_file(src, dest); if (rv) { fprintf(stderr, "Warning: cannot transfer .Xauthority in private home directory\n"); return 0; } return 1; // file copied } return 0; }
static void copy_xauthority(void) { // put a copy of .Xauthority in MNT_DIR fs_build_mnt_dir(); char *src; char *dest; if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); if (asprintf(&src, "%s/.Xauthority", MNT_DIR) == -1) errExit("asprintf"); int rv = copy_file(src, dest); if (rv) exechelp_logerrv("firejail", FIREJAIL_WARNING, "Error: cannot transfer .Xauthority in private home directory\n"); // set permissions and ownership if (chown(dest, getuid(), getgid()) < 0) errExit("chown"); if (chmod(dest, S_IRUSR | S_IWUSR) < 0) errExit("chmod"); // delete the temporary file unlink(src); }
void fs_copy_xauthority(void) { // put a copy of .Xauthority in MNT_DIR fs_build_mnt_dir(); char *src; char *dest; if (asprintf(&dest, "%s/.Xauthority", cfg.homedir) == -1) errExit("asprintf"); if (asprintf(&src, "%s/.Xauthority", MNT_DIR) == -1) errExit("asprintf"); int rv = copy_file(src, dest); if (rv) fprintf(stderr, "Warning: cannot transfer .Xauthority in private home directory\n"); // set permissions and ownership if (chown(dest, getuid(), getgid()) < 0) errExit("chown"); if (chmod(dest, S_IRUSR | S_IWUSR) < 0) errExit("chmod"); // delete the temporary file unlink(src); }
// save seccomp filter in /tmp/firejail/mnt/seccomp static void write_seccomp_file(void) { fs_build_mnt_dir(); assert(sfilter); char *fname; if (asprintf(&fname, "%s/seccomp", MNT_DIR) == -1) errExit("asprintf"); int fd = open(fname, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); if (fd == -1) errExit("open"); if (arg_debug) printf("Save seccomp filter, size %lu bytes\n", sfilter_index * sizeof(struct sock_filter)); errno = 0; ssize_t sz = write(fd, sfilter, sfilter_index * sizeof(struct sock_filter)); if (sz != (ssize_t)(sfilter_index * sizeof(struct sock_filter))) { exechelp_logerrv("firejail", FIREJAIL_ERROR, "Error: cannot save seccomp filter\n"); exit(1); } close(fd); if (chown(fname, 0, 0) < 0) errExit("chown"); free(fname); }
// grab a copy of cp command void fs_build_cp_command(void) { struct stat s; fs_build_mnt_dir(); if (stat(CP_COMMAND, &s)) { char* fname = realpath("/bin/cp", NULL); if (fname == NULL) { fprintf(stderr, "Error: /bin/cp not found\n"); exit(1); } if (stat(fname, &s)) { fprintf(stderr, "Error: /bin/cp not found\n"); exit(1); } int rv = copy_file(fname, CP_COMMAND); if (rv) { fprintf(stderr, "Error: cannot access /bin/cp\n"); exit(1); } if (chmod(CP_COMMAND, 0755)) errExit("chmod"); free(fname); } }
void fs_overlayfs(void) { // check kernel version struct utsname u; int rv = uname(&u); if (rv != 0) errExit("uname"); int major; int minor; if (2 != sscanf(u.release, "%d.%d", &major, &minor)) { fprintf(stderr, "Error: cannot extract Linux kernel version: %s\n", u.version); exit(1); } if (arg_debug) printf("Linux kernel version %d.%d\n", major, minor); int oldkernel = 0; if (major < 3) { fprintf(stderr, "Error: minimum kernel version required 3.x\n"); exit(1); } if (major == 3 && minor < 18) oldkernel = 1; // build overlay directories fs_build_mnt_dir(); char *oroot; if(asprintf(&oroot, "%s/oroot", RUN_MNT_DIR) == -1) errExit("asprintf"); if (mkdir(oroot, 0755)) errExit("mkdir"); if (chown(oroot, 0, 0) < 0) errExit("chown"); if (chmod(oroot, 0755) < 0) errExit("chmod"); char *basedir = RUN_MNT_DIR; if (arg_overlay_keep) { // set base for working and diff directories basedir = cfg.overlay_dir; if (mkdir(basedir, 0755) != 0) { fprintf(stderr, "Error: cannot create overlay directory\n"); exit(1); } } char *odiff; if(asprintf(&odiff, "%s/odiff", basedir) == -1) errExit("asprintf"); if (mkdir(odiff, 0755)) errExit("mkdir"); if (chown(odiff, 0, 0) < 0) errExit("chown"); if (chmod(odiff, 0755) < 0) errExit("chmod"); char *owork; if(asprintf(&owork, "%s/owork", basedir) == -1) errExit("asprintf"); if (mkdir(owork, 0755)) errExit("mkdir"); if (chown(owork, 0, 0) < 0) errExit("chown"); if (chmod(owork, 0755) < 0) errExit("chmod"); // mount overlayfs if (arg_debug) printf("Mounting OverlayFS\n"); char *option; if (oldkernel) { // old Ubuntu/OpenSUSE kernels if (arg_overlay_keep) { fprintf(stderr, "Error: option --overlay= not available for kernels older than 3.18\n"); exit(1); } if (asprintf(&option, "lowerdir=/,upperdir=%s", odiff) == -1) errExit("asprintf"); if (mount("overlayfs", oroot, "overlayfs", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); } else { // kernel 3.18 or newer if (asprintf(&option, "lowerdir=/,upperdir=%s,workdir=%s", odiff, owork) == -1) errExit("asprintf"); //printf("option #%s#\n", option); if (mount("overlay", oroot, "overlay", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); } printf("OverlayFS configured in %s directory\n", basedir); // mount-bind dev directory if (arg_debug) printf("Mounting /dev\n"); char *dev; if (asprintf(&dev, "%s/dev", oroot) == -1) errExit("asprintf"); if (mount("/dev", dev, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev"); // chroot in the new filesystem if (chroot(oroot) == -1) errExit("chroot"); // update /var directory in order to support multiple sandboxes running on the same root directory if (!arg_private_dev) fs_dev_shm(); fs_var_lock(); fs_var_tmp(); fs_var_log(); fs_var_lib(); fs_var_cache(); fs_var_utmp(); // don't leak user information restrict_users(); disable_firejail_config(); // cleanup and exit free(option); free(oroot); free(odiff); }
void fs_overlayfs(void) { // check kernel version struct utsname u; int rv = uname(&u); if (rv != 0) errExit("uname"); int major; int minor; if (2 != sscanf(u.release, "%d.%d", &major, &minor)) { fprintf(stderr, "Error: cannot extract Linux kernel version: %s\n", u.version); exit(1); } if (arg_debug) printf("Linux kernel version %d.%d\n", major, minor); int oldkernel = 0; if (major < 3) { fprintf(stderr, "Error: minimum kernel version required 3.x\n"); exit(1); } if (major == 3 && minor < 18) oldkernel = 1; // build overlay directories fs_build_mnt_dir(); char *oroot; if(asprintf(&oroot, "%s/oroot", RUN_MNT_DIR) == -1) errExit("asprintf"); if (mkdir(oroot, 0755)) errExit("mkdir"); if (chown(oroot, 0, 0) < 0) errExit("chown"); if (chmod(oroot, 0755) < 0) errExit("chmod"); char *basedir = RUN_MNT_DIR; if (arg_overlay_keep) { // set base for working and diff directories basedir = cfg.overlay_dir; if (mkdir(basedir, 0755) != 0) { fprintf(stderr, "Error: cannot create overlay directory\n"); exit(1); } } char *odiff; if(asprintf(&odiff, "%s/odiff", basedir) == -1) errExit("asprintf"); if (mkdir(odiff, 0755)) errExit("mkdir"); if (chown(odiff, 0, 0) < 0) errExit("chown"); if (chmod(odiff, 0755) < 0) errExit("chmod"); char *owork; if(asprintf(&owork, "%s/owork", basedir) == -1) errExit("asprintf"); if (mkdir(owork, 0755)) errExit("mkdir"); if (chown(owork, 0, 0) < 0) errExit("chown"); if (chmod(owork, 0755) < 0) errExit("chmod"); // mount overlayfs if (arg_debug) printf("Mounting OverlayFS\n"); char *option; if (oldkernel) { // old Ubuntu/OpenSUSE kernels if (arg_overlay_keep) { fprintf(stderr, "Error: option --overlay= not available for kernels older than 3.18\n"); exit(1); } if (asprintf(&option, "lowerdir=/,upperdir=%s", odiff) == -1) errExit("asprintf"); if (mount("overlayfs", oroot, "overlayfs", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); } else { // kernel 3.18 or newer if (asprintf(&option, "lowerdir=/,upperdir=%s,workdir=%s", odiff, owork) == -1) errExit("asprintf"); if (mount("overlay", oroot, "overlay", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); //*************************** // issue #263 start code // My setup has a separate mount point for /home. When the overlay is mounted, // the overlay does not contain the original /home contents. // I added code to create a second overlay for /home if the overlay home dir is empty and this seems to work // @dshmgh, Jan 2016 { char *overlayhome; struct stat s; char *hroot; char *hdiff; char *hwork; // dons add debug if (arg_debug) printf ("DEBUG: chroot dirs are oroot %s odiff %s owork %s\n",oroot,odiff,owork); // BEFORE NEXT, WE NEED TO TEST IF /home has any contents or do we need to mount it? // must create var for oroot/cfg.homedir if (asprintf(&overlayhome,"%s%s",oroot,cfg.homedir) == -1) errExit("asprintf"); if (arg_debug) printf ("DEBUG: overlayhome var holds ##%s##\n",overlayhome); // if no homedir in overlay -- create another overlay for /home if (stat(overlayhome, &s) == -1) { if(asprintf(&hroot, "%s/oroot/home", RUN_MNT_DIR) == -1) errExit("asprintf"); if(asprintf(&hdiff, "%s/hdiff", basedir) == -1) errExit("asprintf"); if (mkdir(hdiff, S_IRWXU | S_IRWXG | S_IRWXO)) errExit("mkdir"); if (chown(hdiff, 0, 0) < 0) errExit("chown"); if (chmod(hdiff, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) errExit("chmod"); if(asprintf(&hwork, "%s/hwork", basedir) == -1) errExit("asprintf"); if (mkdir(hwork, S_IRWXU | S_IRWXG | S_IRWXO)) errExit("mkdir"); if (chown(hwork, 0, 0) < 0) errExit("chown"); if (chmod(hwork, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) errExit("chmod"); // no homedir in overlay so now mount another overlay for /home if (asprintf(&option, "lowerdir=/home,upperdir=%s,workdir=%s", hdiff, hwork) == -1) errExit("asprintf"); if (mount("overlay", hroot, "overlay", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs for mounted home directory"); printf("OverlayFS for /home configured in %s directory\n", basedir); } // stat(overlayhome) free(overlayhome); } // issue #263 end code //*************************** } printf("OverlayFS configured in %s directory\n", basedir); // mount-bind dev directory if (arg_debug) printf("Mounting /dev\n"); char *dev; if (asprintf(&dev, "%s/dev", oroot) == -1) errExit("asprintf"); if (mount("/dev", dev, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev"); fs_logger("whitelist /dev"); // mount-bind run directory if (arg_debug) printf("Mounting /run\n"); char *run; if (asprintf(&run, "%s/run", oroot) == -1) errExit("asprintf"); if (mount("/run", run, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /run"); fs_logger("whitelist /run"); // chroot in the new filesystem if (chroot(oroot) == -1) errExit("chroot"); // update /var directory in order to support multiple sandboxes running on the same root directory if (!arg_private_dev) fs_dev_shm(); fs_var_lock(); fs_var_tmp(); fs_var_log(); fs_var_lib(); fs_var_cache(); fs_var_utmp(); // don't leak user information restrict_users(); // when starting as root, firejail config is not disabled; // this mode could be used to install and test new software by chaining // firejail sandboxes (firejail --force) if (getuid() != 0) disable_firejail_config(); else fprintf(stderr, "Warning: masking /etc/firejail disabled when starting the sandbox as root\n"); // cleanup and exit free(option); free(oroot); free(odiff); }
// whitelist for /home/user directory void fs_whitelist(void) { char *homedir = cfg.homedir; assert(homedir); ProfileEntry *entry = cfg.profile; if (!entry) return; char *new_name = NULL; int home_dir = 0; // /home/user directory flag int tmp_dir = 0; // /tmp directory flag int media_dir = 0; // /media directory flag int var_dir = 0; // /var directory flag int dev_dir = 0; // /dev directory flag int opt_dir = 0; // /opt directory flag // verify whitelist files, extract symbolic links, etc. while (entry) { // handle only whitelist commands if (strncmp(entry->data, "whitelist ", 10)) { entry = entry->next; continue; } // resolve ${DOWNLOADS} if (strcmp(entry->data + 10, "${DOWNLOADS}") == 0) { char *tmp = resolve_downloads(); if (tmp) entry->data = tmp; else { *entry->data = '\0'; fprintf(stderr, "***\n"); fprintf(stderr, "*** Warning: cannot whitelist Downloads directory\n"); fprintf(stderr, "*** \tAny file saved will be lost when the sandbox is closed.\n"); fprintf(stderr, "*** \tPlease create a proper Downloads directory for your application.\n"); fprintf(stderr, "***\n"); continue; } } // replace ~/ or ${HOME} into /home/username new_name = expand_home(entry->data + 10, cfg.homedir); assert(new_name); if (arg_debug) fprintf(stderr, "Debug %d: new_name #%s#\n", __LINE__, new_name); // extract the absolute path of the file // realpath function will fail with ENOENT if the file is not found char *fname = realpath(new_name, NULL); if (!fname) { // file not found, blank the entry in the list and continue if (arg_debug || arg_debug_whitelists) { printf("Removed whitelist path: %s\n", entry->data); printf("\texpanded: %s\n", new_name); printf("\treal path: (null)\n"); printf("\t");fflush(0); perror("realpath"); } *entry->data = '\0'; continue; } // valid path referenced to filesystem root if (*new_name != '/') { if (arg_debug) fprintf(stderr, "Debug %d: \n", __LINE__); goto errexit; } // check for supported directories if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { // whitelisting home directory is disabled if --private or --private-home option is present if (arg_private) { if (arg_debug || arg_debug_whitelists) printf("Removed whitelist path %s, --private option is present\n", entry->data); *entry->data = '\0'; continue; } entry->home_dir = 1; home_dir = 1; // both path and absolute path are under /home if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0) { if (arg_debug) fprintf(stderr, "Debug %d: fname #%s#, cfg.homedir #%s#\n", __LINE__, fname, cfg.homedir); goto errexit; } } else if (strncmp(new_name, "/tmp/", 5) == 0) { entry->tmp_dir = 1; tmp_dir = 1; // both path and absolute path are under /tmp if (strncmp(fname, "/tmp/", 5) != 0) { if (arg_debug) fprintf(stderr, "Debug %d: fname #%s#\n", __LINE__, fname); goto errexit; } } else if (strncmp(new_name, "/media/", 7) == 0) { entry->media_dir = 1; media_dir = 1; // both path and absolute path are under /media if (strncmp(fname, "/media/", 7) != 0) { if (arg_debug) fprintf(stderr, "Debug %d: fname #%s#\n", __LINE__, fname); goto errexit; } } else if (strncmp(new_name, "/var/", 5) == 0) { entry->var_dir = 1; var_dir = 1; // both path and absolute path are under /var if (strncmp(fname, "/var/", 5) != 0) { if (arg_debug) fprintf(stderr, "Debug %d: fname #%s#\n", __LINE__, fname); goto errexit; } } else if (strncmp(new_name, "/dev/", 5) == 0) { entry->dev_dir = 1; dev_dir = 1; // both path and absolute path are under /dev if (strncmp(fname, "/dev/", 5) != 0) { if (arg_debug) fprintf(stderr, "Debug %d: fname #%s#\n", __LINE__, fname); goto errexit; } } else if (strncmp(new_name, "/opt/", 5) == 0) { entry->opt_dir = 1; opt_dir = 1; // both path and absolute path are under /dev if (strncmp(fname, "/opt/", 5) != 0) { if (arg_debug) fprintf(stderr, "Debug %d: fname #%s#\n", __LINE__, fname); goto errexit; } } else { if (arg_debug) fprintf(stderr, "Debug %d: \n", __LINE__); goto errexit; } // mark symbolic links if (is_link(new_name)) entry->link = new_name; else { free(new_name); new_name = NULL; } // change file name in entry->data if (strcmp(fname, entry->data + 10) != 0) { char *newdata; if (asprintf(&newdata, "whitelist %s", fname) == -1) errExit("asprintf"); entry->data = newdata; if (arg_debug || arg_debug_whitelists) printf("Replaced whitelist path: %s\n", entry->data); } free(fname); entry = entry->next; } // create mount points fs_build_mnt_dir(); // /home/user if (home_dir) { // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR int rv = mkdir(RUN_WHITELIST_HOME_USER_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(RUN_WHITELIST_HOME_USER_DIR, getuid(), getgid()) < 0) errExit("chown"); if (chmod(RUN_WHITELIST_HOME_USER_DIR, 0755) < 0) errExit("chmod"); if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount a tmpfs and initialize /home/user fs_private(); } // /tmp mountpoint if (tmp_dir) { // keep a copy of real /tmp directory in WHITELIST_TMP_DIR int rv = mkdir(RUN_WHITELIST_TMP_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(RUN_WHITELIST_TMP_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_WHITELIST_TMP_DIR, 0777) < 0) errExit("chmod"); if (mount("/tmp", RUN_WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /tmp if (arg_debug || arg_debug_whitelists) printf("Mounting tmpfs on /tmp directory\n"); if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) errExit("mounting tmpfs on /tmp"); fs_logger("mount tmpfs on /tmp"); } // /media mountpoint if (media_dir) { // keep a copy of real /media directory in RUN_WHITELIST_MEDIA_DIR int rv = mkdir(RUN_WHITELIST_MEDIA_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(RUN_WHITELIST_MEDIA_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_WHITELIST_MEDIA_DIR, 0755) < 0) errExit("chmod"); if (mount("/media", RUN_WHITELIST_MEDIA_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /media if (arg_debug || arg_debug_whitelists) printf("Mounting tmpfs on /media directory\n"); if (mount("tmpfs", "/media", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /media"); fs_logger("mount tmpfs on /media"); } // /var mountpoint if (var_dir) { // keep a copy of real /var directory in RUN_WHITELIST_VAR_DIR int rv = mkdir(RUN_WHITELIST_VAR_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(RUN_WHITELIST_VAR_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_WHITELIST_VAR_DIR, 0755) < 0) errExit("chmod"); if (mount("/var", RUN_WHITELIST_VAR_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /var if (arg_debug || arg_debug_whitelists) printf("Mounting tmpfs on /var directory\n"); if (mount("tmpfs", "/var", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /var"); fs_logger("mount tmpfs on /var"); } // /dev mountpoint if (dev_dir) { // keep a copy of real /dev directory in RUN_WHITELIST_DEV_DIR int rv = mkdir(RUN_WHITELIST_DEV_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(RUN_WHITELIST_DEV_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_WHITELIST_DEV_DIR, 0755) < 0) errExit("chmod"); if (mount("/dev", RUN_WHITELIST_DEV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /dev if (arg_debug || arg_debug_whitelists) printf("Mounting tmpfs on /dev directory\n"); if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /dev"); fs_logger("mount tmpfs on /dev"); } // /opt mountpoint if (opt_dir) { // keep a copy of real /opt directory in RUN_WHITELIST_DEV_DIR int rv = mkdir(RUN_WHITELIST_OPT_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(RUN_WHITELIST_OPT_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_WHITELIST_OPT_DIR, 0755) < 0) errExit("chmod"); if (mount("/opt", RUN_WHITELIST_OPT_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /opt if (arg_debug || arg_debug_whitelists) printf("Mounting tmpfs on /opt directory\n"); if (mount("tmpfs", "/opt", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /opt"); fs_logger("mount tmpfs on /opt"); } // go through profile rules again, and interpret whitelist commands entry = cfg.profile; while (entry) { // handle only whitelist commands if (strncmp(entry->data, "whitelist ", 10)) { entry = entry->next; continue; } //printf("here %d#%s#\n", __LINE__, entry->data); // whitelist the real file whitelist_path(entry); // create the link if any if (entry->link) { // if the link is already there, do not bother struct stat s; if (stat(entry->link, &s) != 0) { // create the path if necessary mkpath(entry->link, s.st_mode); int rv = symlink(entry->data + 10, entry->link); if (rv) fprintf(stderr, "Warning cannot create symbolic link %s\n", entry->link); else if (arg_debug || arg_debug_whitelists) printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10); } } entry = entry->next; } // mask the real home directory, currently mounted on RUN_WHITELIST_HOME_DIR if (home_dir) { if (mount("tmpfs", RUN_WHITELIST_HOME_USER_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mount tmpfs"); } // mask the real /tmp directory, currently mounted on RUN_WHITELIST_TMP_DIR if (tmp_dir) { if (mount("tmpfs", RUN_WHITELIST_TMP_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mount tmpfs"); } if (new_name) free(new_name); return; errexit: fprintf(stderr, "Error: invalid whitelist path %s\n", new_name); exit(1); }
void fs_private_dev(void){ // install a new /dev directory if (arg_debug) printf("Mounting tmpfs on /dev\n"); // create DRI_DIR fs_build_mnt_dir(); // keep a copy of dev directory if (mkdir(RUN_DEV_DIR, 0755) == -1) errExit("mkdir"); if (chmod(RUN_DEV_DIR, 0755) == -1) errExit("chmod"); ASSERT_PERMS(RUN_DEV_DIR, 0, 0, 0755); if (mount("/dev", RUN_DEV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/dri"); // create DEVLOG_FILE int have_devlog = 0; struct stat s; if (stat("/dev/log", &s) == 0) { have_devlog = 1; FILE *fp = fopen(RUN_DEVLOG_FILE, "w"); if (!fp) have_devlog = 0; else { fprintf(fp, "\n"); fclose(fp); if (mount("/dev/log", RUN_DEVLOG_FILE, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/log"); } } // mount tmpfs on top of /dev if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting /dev"); fs_logger("tmpfs /dev"); deventry_mount(); // bring back /dev/log if (have_devlog) { FILE *fp = fopen("/dev/log", "w"); if (fp) { fprintf(fp, "\n"); fclose(fp); if (mount(RUN_DEVLOG_FILE, "/dev/log", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/log"); fs_logger("clone /dev/log"); } } if (mount(RUN_RO_DIR, RUN_DEV_DIR, "none", MS_BIND, "mode=400,gid=0") < 0) errExit("disable /dev/snd"); // create /dev/shm if (arg_debug) printf("Create /dev/shm directory\n"); if (mkdir("/dev/shm", 01777) == -1) errExit("mkdir"); // mkdir sets only the file permission bits if (chmod("/dev/shm", 01777) < 0) errExit("chmod"); ASSERT_PERMS("/dev/shm", 0, 0, 01777); fs_logger("mkdir /dev/shm"); // create devices create_char_dev("/dev/zero", 0666, 1, 5); // mknod -m 666 /dev/zero c 1 5 fs_logger("mknod /dev/zero"); create_char_dev("/dev/null", 0666, 1, 3); // mknod -m 666 /dev/null c 1 3 fs_logger("mknod /dev/null"); create_char_dev("/dev/full", 0666, 1, 7); // mknod -m 666 /dev/full c 1 7 fs_logger("mknod /dev/full"); create_char_dev("/dev/random", 0666, 1, 8); // Mknod -m 666 /dev/random c 1 8 fs_logger("mknod /dev/random"); create_char_dev("/dev/urandom", 0666, 1, 9); // mknod -m 666 /dev/urandom c 1 9 fs_logger("mknod /dev/urandom"); create_char_dev("/dev/tty", 0666, 5, 0); // mknod -m 666 /dev/tty c 5 0 fs_logger("mknod /dev/tty"); #if 0 create_dev("/dev/tty0", "mknod -m 666 /dev/tty0 c 4 0"); create_dev("/dev/console", "mknod -m 622 /dev/console c 5 1"); #endif // pseudo-terminal if (mkdir("/dev/pts", 0755) == -1) errExit("mkdir"); if (chmod("/dev/pts", 0755) == -1) errExit("chmod"); ASSERT_PERMS("/dev/pts", 0, 0, 0755); fs_logger("mkdir /dev/pts"); create_char_dev("/dev/pts/ptmx", 0666, 5, 2); //"mknod -m 666 /dev/pts/ptmx c 5 2"); fs_logger("mknod /dev/pts/ptmx"); create_link("/dev/pts/ptmx", "/dev/ptmx"); // code before github issue #351 // mount -vt devpts -o newinstance -o ptmxmode=0666 devpts //dev/pts // if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, "newinstance,ptmxmode=0666") < 0) // errExit("mounting /dev/pts"); // mount /dev/pts gid_t ttygid = get_tty_gid(); char *data; if (asprintf(&data, "newinstance,gid=%d,mode=620,ptmxmode=0666", (int) ttygid) == -1) errExit("asprintf"); if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, data) < 0) errExit("mounting /dev/pts"); free(data); fs_logger("clone /dev/pts"); #if 0 // stdin, stdout, stderr create_link("/proc/self/fd", "/dev/fd"); create_link("/proc/self/fd/0", "/dev/stdin"); create_link("/proc/self/fd/1", "/dev/stdout"); create_link("/proc/self/fd/2", "/dev/stderr"); #endif }
void fs_private_bin_list(void) { char *private_list = cfg.bin_private_keep; assert(private_list); // create /tmp/firejail/mnt/bin directory fs_build_mnt_dir(); int rv = mkdir(RUN_BIN_DIR, 0755); if (rv == -1) errExit("mkdir"); if (chown(RUN_BIN_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_BIN_DIR, 0755) < 0) errExit("chmod"); // copy the list of files in the new etc directory // using a new child process without root privileges fs_logger_print(); // save the current log pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { if (arg_debug) printf("Copying files in the new home:\n"); // elevate privileges - files in the new /bin directory belong to root if (setreuid(0, 0) < 0) errExit("setreuid"); if (setregid(0, 0) < 0) errExit("setregid"); // copy the list of files in the new home directory char *dlist = strdup(private_list); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); duplicate(ptr); while ((ptr = strtok(NULL, ",")) != NULL) duplicate(ptr); free(dlist); fs_logger_print(); exit(0); } // wait for the child to finish waitpid(child, NULL, 0); // mount-bind int i = 0; while (paths[i]) { struct stat s; if (stat(paths[i], &s) == 0) { if (arg_debug) printf("Mount-bind %s on top of %s\n", RUN_BIN_DIR, paths[i]); if (mount(RUN_BIN_DIR, paths[i], NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); fs_logger2("tmpfs", paths[i]); fs_logger2("mount", paths[i]); } i++; } // log cloned files char *dlist = strdup(private_list); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); while (ptr) { i = 0; while (paths[i]) { struct stat s; if (stat(paths[i], &s) == 0) { char *fname; if (asprintf(&fname, "%s/%s", paths[i], ptr) == -1) errExit("asprintf"); fs_logger2("clone", fname); free(fname); } i++; } ptr = strtok(NULL, ","); } free(dlist); }
// whitelist for /home/user directory void fs_whitelist(void) { char *homedir = cfg.homedir; assert(homedir); ProfileEntry *entry = cfg.profile; if (!entry) return; char *new_name = NULL; int home_dir = 0; // /home/user directory flag int tmp_dir = 0; // /tmp directory flag int media_dir = 0; // /media directory flag int var_dir = 0; // /var directory flag int dev_dir = 0; // /dev directory flag // verify whitelist files, extract symbolic links, etc. while (entry) { // handle only whitelist commands if (strncmp(entry->data, "whitelist ", 10)) { entry = entry->next; continue; } // replace ~/ or ${HOME} into /home/username new_name = expand_home(entry->data + 10, cfg.homedir); assert(new_name); // extract the absolute path of the file // realpath function will fail with ENOENT if the file is not found char *fname = realpath(new_name, NULL); if (!fname) { // file not found, blank the entry in the list and continue if (arg_debug) printf("Removed whitelist path: %s\n", entry->data); *entry->data = '\0'; continue; } // valid path referenced to filesystem root if (*new_name != '/') goto errexit; // check for supported directories if (strncmp(new_name, cfg.homedir, strlen(cfg.homedir)) == 0) { entry->home_dir = 1; home_dir = 1; // both path and absolute path are under /home if (strncmp(fname, cfg.homedir, strlen(cfg.homedir)) != 0) goto errexit; } else if (strncmp(new_name, "/tmp/", 5) == 0) { entry->tmp_dir = 1; tmp_dir = 1; // both path and absolute path are under /tmp if (strncmp(fname, "/tmp/", 5) != 0) goto errexit; } else if (strncmp(new_name, "/media/", 7) == 0) { entry->media_dir = 1; media_dir = 1; // both path and absolute path are under /media if (strncmp(fname, "/media/", 7) != 0) goto errexit; } else if (strncmp(new_name, "/var/", 5) == 0) { entry->var_dir = 1; var_dir = 1; // both path and absolute path are under /var if (strncmp(fname, "/var/", 5) != 0) goto errexit; } else if (strncmp(new_name, "/dev/", 5) == 0) { entry->dev_dir = 1; dev_dir = 1; // both path and absolute path are under /dev if (strncmp(fname, "/dev/", 5) != 0) goto errexit; } else goto errexit; // mark symbolic links if (is_link(new_name)) entry->link = new_name; else free(new_name); // change file name in entry->data if (strcmp(fname, entry->data + 10) != 0) { char *newdata; if (asprintf(&newdata, "whitelist %s", fname) == -1) errExit("asprintf"); entry->data = newdata; if (arg_debug) printf("Replaced whitelist path: %s\n", entry->data); } free(fname); entry = entry->next; } // create mount points fs_build_mnt_dir(); // /home/user if (home_dir) { // keep a copy of real home dir in WHITELIST_HOME_DIR int rv = mkdir(WHITELIST_HOME_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(WHITELIST_HOME_DIR, getuid(), getgid()) < 0) errExit("chown"); if (chmod(WHITELIST_HOME_DIR, 0755) < 0) errExit("chmod"); if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount a tmpfs and initialize /home/user fs_private(); } // /tmp mountpoint if (tmp_dir) { // keep a copy of real /tmp directory in WHITELIST_TMP_DIR int rv = mkdir(WHITELIST_TMP_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(WHITELIST_TMP_DIR, 0, 0) < 0) errExit("chown"); if (chmod(WHITELIST_TMP_DIR, 0777) < 0) errExit("chmod"); if (mount("/tmp", WHITELIST_TMP_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /tmp if (arg_debug) printf("Mounting tmpfs on /tmp directory\n"); if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) errExit("mounting tmpfs on /tmp"); } // /media mountpoint if (media_dir) { // keep a copy of real /media directory in WHITELIST_MEDIA_DIR int rv = mkdir(WHITELIST_MEDIA_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(WHITELIST_MEDIA_DIR, 0, 0) < 0) errExit("chown"); if (chmod(WHITELIST_MEDIA_DIR, 0755) < 0) errExit("chmod"); if (mount("/media", WHITELIST_MEDIA_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /media if (arg_debug) printf("Mounting tmpfs on /media directory\n"); if (mount("tmpfs", "/media", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /media"); } // /media mountpoint if (var_dir) { // keep a copy of real /var directory in WHITELIST_VAR_DIR int rv = mkdir(WHITELIST_VAR_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(WHITELIST_VAR_DIR, 0, 0) < 0) errExit("chown"); if (chmod(WHITELIST_VAR_DIR, 0755) < 0) errExit("chmod"); if (mount("/var", WHITELIST_VAR_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /var if (arg_debug) printf("Mounting tmpfs on /var directory\n"); if (mount("tmpfs", "/var", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /var"); } // /dev mountpoint if (dev_dir) { // keep a copy of real /dev directory in WHITELIST_DEV_DIR int rv = mkdir(WHITELIST_DEV_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(WHITELIST_DEV_DIR, 0, 0) < 0) errExit("chown"); if (chmod(WHITELIST_DEV_DIR, 0755) < 0) errExit("chmod"); if (mount("/dev", WHITELIST_DEV_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // mount tmpfs on /var if (arg_debug) printf("Mounting tmpfs on /dev directory\n"); if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting tmpfs on /dev"); } // go through profile rules again, and interpret whitelist commands entry = cfg.profile; while (entry) { // handle only whitelist commands if (strncmp(entry->data, "whitelist ", 10)) { entry = entry->next; continue; } //printf("here %d#%s#\n", __LINE__, entry->data); // whitelist the real file whitelist_path(entry); // create the link if any if (entry->link) { // if the link is already there, do not bother struct stat s; if (stat(entry->link, &s) != 0) { int rv = symlink(entry->data + 10, entry->link); if (rv) fprintf(stderr, "Warning cannot create symbolic link %s\n", entry->link); else if (arg_debug) printf("Created symbolic link %s -> %s\n", entry->link, entry->data + 10); } } entry = entry->next; } // mask the real home directory, currently mounted on WHITELIST_HOME_DIR if (home_dir) { if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mount tmpfs"); } // mask the real /tmp directory, currently mounted on WHITELIST_TMP_DIR if (tmp_dir) { if (mount("tmpfs", WHITELIST_TMP_DIR, "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mount tmpfs"); } return; errexit: fprintf(stderr, "Error: invalid whitelist path %s\n", new_name); exit(1); }
static void sanitize_passwd(void) { struct stat s; if (stat("/etc/passwd", &s) == -1) return; 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; fs_build_mnt_dir(); // 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; if (uid < UID_MIN) { 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); fclose(fpout); if (chown(RUN_PASSWD_FILE, 0, 0) == -1) errExit("chown"); if (chmod(RUN_PASSWD_FILE, 0644) == -1) errExit("chmod"); // 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: fprintf(stderr, "Warning: failed to clean up /etc/passwd\n"); if (fpin) fclose(fpin); if (fpout) fclose(fpout); }
void fs_overlayfs(void) { // check kernel version struct utsname u; int rv = uname(&u); if (rv != 0) errExit("uname"); int major; int minor; if (2 != sscanf(u.release, "%d.%d", &major, &minor)) { fprintf(stderr, "Error: cannot extract Linux kernel version: %s\n", u.version); exit(1); } if (arg_debug) printf("Linux kernel version %d.%d\n", major, minor); int oldkernel = 0; if (major < 3) { fprintf(stderr, "Error: minimum kernel version required 3.x\n"); exit(1); } if (major == 3 && minor < 18) oldkernel = 1; // build overlay directories fs_build_mnt_dir(); char *oroot; if(asprintf(&oroot, "%s/oroot", MNT_DIR) == -1) errExit("asprintf"); if (mkdir(oroot, S_IRWXU | S_IRWXG | S_IRWXO)) errExit("mkdir"); if (chown(oroot, 0, 0) < 0) errExit("chown"); if (chmod(oroot, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) errExit("chmod"); char *odiff; if(asprintf(&odiff, "%s/odiff", MNT_DIR) == -1) errExit("asprintf"); if (mkdir(odiff, S_IRWXU | S_IRWXG | S_IRWXO)) errExit("mkdir"); if (chown(odiff, 0, 0) < 0) errExit("chown"); if (chmod(odiff, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) errExit("chmod"); char *owork; if(asprintf(&owork, "%s/owork", MNT_DIR) == -1) errExit("asprintf"); if (mkdir(owork, S_IRWXU | S_IRWXG | S_IRWXO)) errExit("mkdir"); if (chown(owork, 0, 0) < 0) errExit("chown"); if (chmod(owork, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) < 0) errExit("chmod"); // mount overlayfs if (arg_debug) printf("Mounting OverlayFS\n"); char *option; if (oldkernel) { // old Ubuntu/OpenSUSE kernels if (asprintf(&option, "lowerdir=/,upperdir=%s", odiff) == -1) errExit("asprintf"); if (mount("overlayfs", oroot, "overlayfs", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); } else { // kernel 3.18 or newer if (asprintf(&option, "lowerdir=/,upperdir=%s,workdir=%s", odiff, owork) == -1) errExit("asprintf"); if (mount("overlay", oroot, "overlay", MS_MGC_VAL, option) < 0) errExit("mounting overlayfs"); } // mount-bind dev directory if (arg_debug) printf("Mounting /dev\n"); char *dev; if (asprintf(&dev, "%s/dev", oroot) == -1) errExit("asprintf"); if (mount("/dev", dev, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev"); // chroot in the new filesystem if (chroot(oroot) == -1) errExit("chroot"); // update /var directory in order to support multiple sandboxes running on the same root directory if (!arg_private_dev) fs_dev_shm(); fs_var_lock(); fs_var_tmp(); fs_var_log(); fs_var_lib(); fs_var_cache(); fs_var_utmp(); // only in user mode if (getuid()) sanitize_home(); // cleanup and exit free(option); free(oroot); free(odiff); }
// disable shm in pulseaudio void pulseaudio_init(void) { struct stat s; // do we have pulseaudio in the system? if (stat("/etc/pulse/client.conf", &s) == -1) return; // create the new user pulseaudio directory fs_build_mnt_dir(); int rv = mkdir(RUN_PULSE_DIR, 0700); (void) rv; // in --chroot mode the directory can already be there if (chown(RUN_PULSE_DIR, getuid(), getgid()) < 0) errExit("chown"); if (chmod(RUN_PULSE_DIR, 0700) < 0) errExit("chmod"); // create the new client.conf file char *pulsecfg = NULL; if (asprintf(&pulsecfg, "%s/client.conf", RUN_PULSE_DIR) == -1) errExit("asprintf"); if (copy_file("/etc/pulse/client.conf", pulsecfg)) errExit("copy_file"); FILE *fp = fopen(pulsecfg, "a+"); if (!fp) errExit("fopen"); fprintf(fp, "%s", "\nenable-shm = no\n"); fclose(fp); if (chmod(pulsecfg, 0644) == -1) errExit("chmod"); if (chown(pulsecfg, getuid(), getgid()) == -1) errExit("chown"); // create ~/.config/pulse directory if not present char *dir1; if (asprintf(&dir1, "%s/.config", cfg.homedir) == -1) errExit("asprintf"); if (stat(dir1, &s) == -1) { int rv = mkdir(dir1, 0755); if (rv == 0) { rv = chown(dir1, getuid(), getgid()); (void) rv; rv = chmod(dir1, 0755); (void) rv; } } free(dir1); if (asprintf(&dir1, "%s/.config/pulse", cfg.homedir) == -1) errExit("asprintf"); if (stat(dir1, &s) == -1) { int rv = mkdir(dir1, 0700); if (rv == 0) { rv = chown(dir1, getuid(), getgid()); (void) rv; rv = chmod(dir1, 0700); (void) rv; } } free(dir1); // if we have ~/.config/pulse mount the new directory, else set environment variable char *homeusercfg; if (asprintf(&homeusercfg, "%s/.config/pulse", cfg.homedir) == -1) errExit("asprintf"); if (stat(homeusercfg, &s) == 0) { if (mount(RUN_PULSE_DIR, homeusercfg, "none", MS_BIND, NULL) < 0) errExit("mount pulseaudio"); fs_logger2("tmpfs", homeusercfg); } else { // set environment if (setenv("PULSE_CLIENTCONFIG", pulsecfg, 1) < 0) errExit("setenv"); } free(pulsecfg); free(homeusercfg); }
// private mode (--private-home=list): // mount homedir on top of /home/user, // tmpfs on top of /root in nonroot mode, // tmpfs on top of /tmp in root mode, // set skel files, // restore .Xauthority void fs_private_home_list(void) { char *homedir = cfg.homedir; char *private_list = cfg.home_private_keep; assert(homedir); assert(private_list); int xflag = store_xauthority(); uid_t u = getuid(); gid_t g = getgid(); struct stat s; if (stat(homedir, &s) == -1) { exechelp_logerrv("firejail", FIREJAIL_ERROR, "Error: cannot find user home directory\n"); exit(1); } // create /tmp/firejail/mnt/home directory fs_build_mnt_dir(); int rv = mkdir(HOME_DIR, S_IRWXU | S_IRWXG | S_IRWXO); if (rv == -1) errExit("mkdir"); if (chown(HOME_DIR, u, g) < 0) errExit("chown"); if (chmod(HOME_DIR, 0755) < 0) errExit("chmod"); // copy the list of files in the new home directory // using a new child process without root privileges pid_t child = fork(); if (child < 0) errExit("fork"); if (child == 0) { if (arg_debug) printf("Copying files in the new home:\n"); // drop privileges if (setgroups(0, NULL) < 0) errExit("setgroups"); if (setgid(getgid()) < 0) errExit("setgid/getgid"); if (setuid(getuid()) < 0) errExit("setuid/getuid"); // copy the list of files in the new home directory char *dlist = strdup(cfg.home_private_keep); if (!dlist) errExit("strdup"); char *ptr = strtok(dlist, ","); duplicate(ptr); while ((ptr = strtok(NULL, ",")) != NULL) duplicate(ptr); free(dlist); exit(0); } // wait for the child to finish waitpid(child, NULL, 0); // mount bind private_homedir on top of homedir char *newhome; if (asprintf(&newhome, "%s%s", HOME_DIR, cfg.homedir) == -1) errExit("asprintf"); if (arg_debug) printf("Mount-bind %s on top of %s\n", newhome, homedir); if (mount(newhome, homedir, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mount bind"); // preserve mode and ownership // if (chown(homedir, s.st_uid, s.st_gid) == -1) // errExit("mount-bind chown"); // if (chmod(homedir, s.st_mode) == -1) // errExit("mount-bind chmod"); if (u != 0) { // mask /root if (arg_debug) printf("Mounting a new /root directory\n"); if (mount("tmpfs", "/root", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=700,gid=0") < 0) errExit("mounting home directory"); } else { // mask /home if (arg_debug) printf("Mounting a new /home directory\n"); if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) errExit("mounting home directory"); // mask /tmp only in root mode; KDE keeps all kind of sockets in /tmp! if (arg_debug) printf("Mounting a new /tmp directory\n"); if (mount("tmpfs", "/tmp", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) errExit("mounting tmp directory"); } skel(homedir, u, g); if (xflag) copy_xauthority(); }
void fs_private_dev(void){ int rv; // install a new /dev directory if (arg_debug) printf("Mounting tmpfs on /dev\n"); int have_dri = 0; struct stat s; if (stat("/dev/dri", &s) == 0) have_dri = 1; // create DRI_DIR fs_build_mnt_dir(); if (have_dri) { /* coverity[toctou] */ rv = mkdir(RUN_DRI_DIR, 0755); if (rv == -1) errExit("mkdir"); if (chown(RUN_DRI_DIR, 0, 0) < 0) errExit("chown"); if (chmod(RUN_DRI_DIR, 0755) < 0) errExit("chmod"); // keep a copy of /dev/dri under DRI_DIR if (mount("/dev/dri", RUN_DRI_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/dri"); } // restore /dev/log int have_devlog = 0; if (stat("/dev/log", &s) == 0) { have_devlog = 1; FILE *fp = fopen(RUN_DEVLOG_FILE, "w"); if (!fp) have_devlog = 0; else { fprintf(fp, "\n"); fclose(fp); if (mount("/dev/log", RUN_DEVLOG_FILE, NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/log"); } } // mount tmpfs on top of /dev if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME | MS_REC, "mode=777,gid=0") < 0) errExit("mounting /dev"); fs_logger("mount tmpfs on /dev"); // bring back /dev/log if (have_devlog) { FILE *fp = fopen("/dev/log", "w"); if (fp) { fprintf(fp, "\n"); fclose(fp); if (mount(RUN_DEVLOG_FILE, "/dev/log", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/log"); fs_logger("clone /dev/log"); } } // bring back the /dev/dri directory if (have_dri) { /* coverity[toctou] */ rv = mkdir("/dev/dri", 0755); if (rv == -1) errExit("mkdir"); if (chown("/dev/dri", 0, 0) < 0) errExit("chown"); if (chmod("/dev/dri",0755) < 0) errExit("chmod"); if (mount(RUN_DRI_DIR, "/dev/dri", NULL, MS_BIND|MS_REC, NULL) < 0) errExit("mounting /dev/dri"); fs_logger("clone /dev/dri"); } // create /dev/shm if (arg_debug) printf("Create /dev/shm directory\n"); rv = mkdir("/dev/shm", 0777); if (rv == -1) errExit("mkdir"); if (chown("/dev/shm", 0, 0) < 0) errExit("chown"); if (chmod("/dev/shm", 0777) < 0) errExit("chmod"); fs_logger("mkdir /dev/shm"); // create devices create_char_dev("/dev/zero", 0666, 1, 5); // mknod -m 666 /dev/zero c 1 5 fs_logger("mknod /dev/zero"); create_char_dev("/dev/null", 0666, 1, 3); // mknod -m 666 /dev/null c 1 3 fs_logger("mknod /dev/null"); create_char_dev("/dev/full", 0666, 1, 7); // mknod -m 666 /dev/full c 1 7 fs_logger("mknod /dev/full"); create_char_dev("/dev/random", 0666, 1, 8); // Mknod -m 666 /dev/random c 1 8 fs_logger("mknod /dev/random"); create_char_dev("/dev/urandom", 0666, 1, 9); // mknod -m 666 /dev/urandom c 1 9 fs_logger("mknod /dev/urandom"); create_char_dev("/dev/tty", 0666, 5, 0); // mknod -m 666 /dev/tty c 5 0 fs_logger("mknod /dev/tty"); #if 0 create_dev("/dev/tty0", "mknod -m 666 /dev/tty0 c 4 0"); create_dev("/dev/console", "mknod -m 622 /dev/console c 5 1"); #endif // pseudo-terminal rv = mkdir("/dev/pts", 0755); if (rv == -1) errExit("mkdir"); if (chown("/dev/pts", 0, 0) < 0) errExit("chown"); if (chmod("/dev/pts", 0755) < 0) errExit("chmod"); fs_logger("mkdir /dev/pts"); create_char_dev("/dev/pts/ptmx", 0666, 5, 2); //"mknod -m 666 /dev/pts/ptmx c 5 2"); fs_logger("mknod /dev/pts/ptmx"); create_link("/dev/pts/ptmx", "/dev/ptmx"); // mount -vt devpts -o newinstance -o ptmxmode=0666 devpts //dev/pts if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, "newinstance,ptmxmode=0666") < 0) errExit("mounting /dev/pts"); fs_logger("mount devpts"); #if 0 // stdin, stdout, stderr create_link("/proc/self/fd", "/dev/fd"); create_link("/proc/self/fd/0", "/dev/stdin"); create_link("/proc/self/fd/1", "/dev/stdout"); create_link("/proc/self/fd/2", "/dev/stderr"); #endif }
static void sanitize_group(void) { struct stat s; if (stat("/etc/group", &s) == -1) return; 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; fs_build_mnt_dir(); // 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; if (gid < GID_MIN) { 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); fclose(fpout); if (chown(RUN_GROUP_FILE, 0, 0) == -1) errExit("chown"); if (chmod(RUN_GROUP_FILE, 0644) == -1) errExit("chmod"); // 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: fprintf(stderr, "Warning: failed to clean up /etc/group\n"); if (fpin) fclose(fpin); if (fpout) fclose(fpout); }
static void fs_build_remount_mnt_dir(void) { tmpfs_mounted = 0; fs_build_mnt_dir(); }