// find the first child for this parent; return 1 if error int find_child(pid_t parent, pid_t *child) { EUID_ASSERT(); *child = 0; // use it to flag a found child DIR *dir; EUID_ROOT(); // grsecurity fix if (!(dir = opendir("/proc"))) { // sleep 2 seconds and try again sleep(2); if (!(dir = opendir("/proc"))) { fprintf(stderr, "Error: cannot open /proc directory\n"); exit(1); } } struct dirent *entry; char *end; while (*child == 0 && (entry = readdir(dir))) { pid_t pid = strtol(entry->d_name, &end, 10); if (end == entry->d_name || *end) continue; if (pid == parent) continue; // open stat file char *file; if (asprintf(&file, "/proc/%u/status", pid) == -1) { perror("asprintf"); exit(1); } FILE *fp = fopen(file, "r"); if (!fp) { free(file); continue; } // look for firejail executable name char buf[BUFLEN]; while (fgets(buf, BUFLEN - 1, fp)) { if (strncmp(buf, "PPid:", 5) == 0) { char *ptr = buf + 5; while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { ptr++; } if (*ptr == '\0') { fprintf(stderr, "Error: cannot read /proc file\n"); exit(1); } if (parent == atoi(ptr)) *child = pid; break; // stop reading the file } } fclose(fp); free(file); } closedir(dir); EUID_USER(); return (*child)? 0:1; // 0 = found, 1 = not found }
static uint64_t extract_caps(int pid) { EUID_ASSERT(); char *file; if (asprintf(&file, "/proc/%d/status", pid) == -1) errExit("asprintf"); EUID_ROOT(); // grsecurity FILE *fp = fopen(file, "r"); EUID_USER(); // grsecurity if (!fp) goto errexit; char buf[MAXBUF]; while (fgets(buf, MAXBUF, fp)) { if (strncmp(buf, "CapBnd:\t", 8) == 0) { char *ptr = buf + 8; unsigned long long val; sscanf(ptr, "%llx", &val); free(file); fclose(fp); return val; } } fclose(fp); errexit: free(file); fprintf(stderr, "Error: cannot read caps configuration\n"); exit(1); }
void cpu_print_filter(pid_t pid) { EUID_ASSERT(); // if the pid is that of a firejail process, use the pid of the first child process EUID_ROOT(); // grsecurity char *comm = pid_proc_comm(pid); EUID_USER(); // grsecurity if (comm) { if (strcmp(comm, "firejail") == 0) { pid_t child; if (find_child(pid, &child) == 0) { pid = child; } } 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 denied.\n"); exit(1); } } print_cpu(pid); exit(0); }
static void print_cpu(int pid) { char *file; if (asprintf(&file, "/proc/%d/status", pid) == -1) { errExit("asprintf"); exit(1); } EUID_ROOT(); // grsecurity FILE *fp = fopen(file, "r"); EUID_USER(); // grsecurity if (!fp) { printf(" Error: cannot open %s\n", file); free(file); return; } #define MAXBUF 4096 char buf[MAXBUF]; while (fgets(buf, MAXBUF, fp)) { if (strncmp(buf, "Cpus_allowed_list:", 18) == 0) { printf(" %s", buf); fflush(0); free(file); fclose(fp); return; } } fclose(fp); free(file); }
pid_t switch_to_child(pid_t pid) { EUID_ROOT(); errno = 0; char *comm = pid_proc_comm(pid); if (!comm) { if (errno == ENOENT) { fprintf(stderr, "Error: cannot find process with pid %d\n", pid); exit(1); } else { fprintf(stderr, "Error: cannot read /proc file\n"); exit(1); } } EUID_USER(); if (strcmp(comm, "firejail") == 0) { pid_t child; if (find_child(pid, &child) == 1) { fprintf(stderr, "Error: no valid sandbox\n"); exit(1); } fmessage("Switching to pid %u, the first child process inside the sandbox\n", (unsigned) child); pid = child; } free(comm); return pid; }
static void extract_x11_display(pid_t pid) { char *fname; if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) errExit("asprintf"); FILE *fp = fopen(fname, "r"); free(fname); if (!fp) return; if (1 != fscanf(fp, "%u", &display)) { fprintf(stderr, "Error: cannot read X11 display file\n"); fclose(fp); return; } fclose(fp); // check display range if (display < X11_DISPLAY_START || display > X11_DISPLAY_END) { fprintf(stderr, "Error: invalid X11 display range\n"); return; } // store the display number for join process in /run/firejail/x11 EUID_ROOT(); set_x11_run_file(getpid(), display); EUID_USER(); }
void net_check_cfg(void) { EUID_ASSERT(); int net_configured = 0; if (cfg.bridge0.configured) net_configured++; if (cfg.bridge1.configured) net_configured++; if (cfg.bridge2.configured) net_configured++; if (cfg.bridge3.configured) net_configured++; int if_configured = 0; if (cfg.interface0.configured) if_configured++; if (cfg.interface1.configured) if_configured++; if (cfg.interface2.configured) if_configured++; if (cfg.interface3.configured) if_configured++; // --defaultgw requires a network or an interface if (cfg.defaultgw && net_configured == 0 && if_configured == 0) { fprintf(stderr, "Error: option --defaultgw requires at least one network or one interface to be configured\n"); exit(1); } if (net_configured == 0) // nothing to check return; // --net=none if (arg_nonetwork && net_configured) { fprintf(stderr, "Error: --net and --net=none are mutually exclusive\n"); exit(1); } // check default gateway address or assign one assert(cfg.bridge0.configured); if (cfg.defaultgw) check_default_gw(cfg.defaultgw); else { // first network is a regular bridge if (cfg.bridge0.macvlan == 0) cfg.defaultgw = cfg.bridge0.ip; // first network is a mac device else { // get the host default gw EUID_ROOT(); // rise permissions for grsecurity // Error fopen:network_get_defaultgw(479): Permission denied uint32_t gw = network_get_defaultgw(); EUID_USER(); // check the gateway is network range if (in_netrange(gw, cfg.bridge0.ip, cfg.bridge0.mask)) gw = 0; cfg.defaultgw = gw; } } }
// --protocol.print void protocol_print_filter(pid_t pid) { EUID_ASSERT(); (void) pid; #ifdef SYS_socket // 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; } } 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 denied.\n"); exit(1); } } // find the seccomp filter EUID_ROOT(); char *fname; if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_PROTOCOL_CFG) == -1) errExit("asprintf"); struct stat s; if (stat(fname, &s) == -1) { printf("Cannot access seccomp filter.\n"); exit(1); } // read and print the filter protocol_filter_load(fname); free(fname); if (cfg.protocol) printf("%s\n", cfg.protocol); exit(0); #else fprintf(stderr, "Warning: --protocol not supported on this platform\n"); return; #endif }
uid_t pid_get_uid(pid_t pid) { EUID_ASSERT(); uid_t rv = 0; // open status file char *file; if (asprintf(&file, "/proc/%u/status", pid) == -1) { perror("asprintf"); exit(1); } EUID_ROOT(); // grsecurity fix FILE *fp = fopen(file, "r"); if (!fp) { free(file); fprintf(stderr, "Error: cannot open /proc file\n"); exit(1); } // extract uid static const int PIDS_BUFLEN = 1024; char buf[PIDS_BUFLEN]; while (fgets(buf, PIDS_BUFLEN - 1, fp)) { if (strncmp(buf, "Uid:", 4) == 0) { char *ptr = buf + 5; while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { ptr++; } if (*ptr == '\0') break; rv = atoi(ptr); break; // break regardless! } } fclose(fp); free(file); EUID_USER(); // grsecurity fix if (rv == 0) { fprintf(stderr, "Error: cannot read /proc file\n"); exit(1); } return rv; }
void seccomp_print_filter(pid_t pid) { EUID_ASSERT(); // 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; } } 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 denied.\n"); exit(1); } } // find the seccomp filter EUID_ROOT(); char *fname; if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_SECCOMP_CFG) == -1) errExit("asprintf"); struct stat s; if (stat(fname, &s) == -1) { printf("Cannot access seccomp filter.\n"); exit(1); } // read and print the filter - run this as root, the user doesn't have access sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FSECCOMP, "print", fname); free(fname); exit(0); }
void caps_print(void) { EUID_ASSERT(); int i; int elems = sizeof(capslist) / sizeof(capslist[0]); // check current caps supported by the kernel int cnt = 0; unsigned long cap; EUID_ROOT(); // grsecurity fix for (cap=0; cap <= 63; cap++) { int code = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0); if (code == 0) cnt++; } EUID_USER(); printf("Your kernel supports %d capabilities.\n", cnt); for (i = 0; i < elems; i++) { printf("%d\t- %s\n", capslist[i].nr, capslist[i].name); } }
void net_dns_print(pid_t pid) { EUID_ASSERT(); // drop privileges - will not be able to read /etc/resolv.conf for --noroot option // 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; } } free(comm); } char *fname; EUID_ROOT(); if (asprintf(&fname, "/proc/%d/root/etc/resolv.conf", pid) == -1) errExit("asprintf"); // access /etc/resolv.conf FILE *fp = fopen(fname, "r"); if (!fp) { fprintf(stderr, "Error: cannot access /etc/resolv.conf\n"); exit(1); } char buf[MAXBUF]; while (fgets(buf, MAXBUF, fp)) printf("%s", buf); printf("\n"); fclose(fp); free(fname); exit(0); }
// check for X11 abstract sockets static int x11_abstract_sockets_present(void) { char *path; EUID_ROOT(); // grsecurity fix FILE *fp = fopen("/proc/net/unix", "r"); EUID_USER(); if (!fp) errExit("fopen"); while (fscanf(fp, "%*s %*s %*s %*s %*s %*s %*s %ms\n", &path) != EOF) { if (path && strncmp(path, "@/tmp/.X11-unix/", 16) == 0) { free(path); fclose(fp); return 1; } } free(path); fclose(fp); return 0; }
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; 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")) exit(1); if (join_namespace(pid, "net")) exit(1); if (join_namespace(pid, "pid")) exit(1); if (join_namespace(pid, "uts")) exit(1); if (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 // set protocol filter if (getuid() != 0) protocol_filter_load(RUN_PROTOCOL_CFG); if (cfg.protocol) { // not available for uid 0 protocol_filter(); } // set seccomp filter if (apply_seccomp == 1) // not available for uid 0 seccomp_set(); #endif // fix qt 4.8 if (setenv("QT_X11_NO_MITSHM", "1", 1) < 0) errExit("setenv"); if (setenv("container", "firejail", 1) < 0) // LXC sets container=lxc, errExit("setenv"); // 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 prompt color to green char *prompt = getenv("FIREJAIL_PROMPT"); if (prompt && strcmp(prompt, "yes") == 0) { //export PS1='\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] ' if (setenv("PROMPT_COMMAND", "export PS1=\"\\[\\e[1;32m\\][\\u@\\h \\W]\\$\\[\\e[0m\\] \"", 1) < 0) errExit("setenv"); } // set nice if (arg_nice) { errno = 0; int rv = nice(cfg.nice); (void) rv; if (errno) { fprintf(stderr, "Warning: cannot set nice value\n"); errno = 0; } } // run cmdline trough shell if (cfg.command_line == NULL) { // if the sandbox was started with --shell=none, it is possible we don't have a shell // inside the sandbox if (cfg.shell == NULL) { cfg.shell = guess_shell(); if (!cfg.shell) { fprintf(stderr, "Error: no POSIX shell found, please use --shell command line option\n"); exit(1); } } struct stat s; if (stat(cfg.shell, &s) == -1) { fprintf(stderr, "Error: %s shell not found inside the sandbox\n", cfg.shell); exit(1); } 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); }
void appimage_set(const char *appimage) { assert(appimage); assert(devloop == NULL); // don't call this twice! EUID_ASSERT(); #ifdef LOOP_CTL_GET_FREE // test for older kernels; this definition is found in /usr/include/linux/loop.h // check appimage file invalid_filename(appimage); if (access(appimage, R_OK) == -1) { fprintf(stderr, "Error: cannot access AppImage file\n"); exit(1); } // get appimage type and ELF size // a value of 0 means we are dealing with a type1 appimage long unsigned int size = appimage2_size(appimage); if (arg_debug) printf("AppImage ELF size %lu\n", size); // open appimage file /* coverity[toctou] */ int ffd = open(appimage, O_RDONLY|O_CLOEXEC); if (ffd == -1) { fprintf(stderr, "Error: cannot open AppImage file\n"); exit(1); } // find or allocate a free loop device to use EUID_ROOT(); int cfd = open("/dev/loop-control", O_RDWR); if (cfd == -1) { fprintf(stderr, "Error: /dev/loop-control interface is not supported by your kernel\n"); exit(1); } int devnr = ioctl(cfd, LOOP_CTL_GET_FREE); if (devnr == -1) { fprintf(stderr, "Error: cannot allocate a new loopback device\n"); exit(1); } close(cfd); if (asprintf(&devloop, "/dev/loop%d", devnr) == -1) errExit("asprintf"); int lfd = open(devloop, O_RDONLY); if (lfd == -1) { fprintf(stderr, "Error: cannot open %s\n", devloop); exit(1); } if (ioctl(lfd, LOOP_SET_FD, ffd) == -1) { fprintf(stderr, "Error: cannot configure the loopback device\n"); exit(1); } if (size) { struct loop_info64 info; memset(&info, 0, sizeof(struct loop_info64)); info.lo_offset = size; if (ioctl(lfd, LOOP_SET_STATUS64, &info) == -1) errExit("configure appimage offset"); } close(lfd); close(ffd); EUID_USER(); // creates appimage mount point perms 0700 if (asprintf(&mntdir, "%s/.appimage-%u", RUN_FIREJAIL_APPIMAGE_DIR, getpid()) == -1) errExit("asprintf"); EUID_ROOT(); mkdir_attr(mntdir, 0700, getuid(), getgid()); EUID_USER(); // mount char *mode; if (asprintf(&mode, "mode=700,uid=%d,gid=%d", getuid(), getgid()) == -1) errExit("asprintf"); EUID_ROOT(); if (size == 0) { if (mount(devloop, mntdir, "iso9660",MS_MGC_VAL|MS_RDONLY, mode) < 0) errExit("mounting appimage"); } else { if (mount(devloop, mntdir, "squashfs",MS_MGC_VAL|MS_RDONLY, mode) < 0) errExit("mounting appimage"); } if (arg_debug) printf("appimage mounted on %s\n", mntdir); EUID_USER(); // set environment if (setenv("APPIMAGE", appimage, 1) < 0) errExit("setenv"); if (mntdir && setenv("APPDIR", mntdir, 1) < 0) errExit("setenv"); // build new command line if (asprintf(&cfg.command_line, "%s/AppRun", mntdir) == -1) errExit("asprintf"); free(mode); #ifdef HAVE_GCOV __gcov_flush(); #endif #else fprintf(stderr, "Error: /dev/loop-control interface is not supported by your kernel\n"); exit(1); #endif }
void join(pid_t pid, int argc, char **argv, int index) { EUID_ASSERT(); char *homedir = cfg.homedir; extract_command(argc, argv, index); // 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; 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")) exit(1); if (join_namespace(pid, "net")) exit(1); if (join_namespace(pid, "pid")) exit(1); if (join_namespace(pid, "uts")) exit(1); if (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 // set protocol filter if (getuid() != 0) protocol_filter_load(RUN_PROTOCOL_CFG); if (cfg.protocol) { // not available for uid 0 protocol_filter(); } // set seccomp filter if (apply_seccomp == 1) // not available for uid 0 seccomp_set(); #endif // fix qt 4.8 if (setenv("QT_X11_NO_MITSHM", "1", 1) < 0) errExit("setenv"); if (setenv("container", "firejail", 1) < 0) // LXC sets container=lxc, errExit("setenv"); // 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); } else drop_privs(arg_nogroups); // nogroups not available for uid 0 // set prompt color to green //export PS1='\[\e[1;32m\][\u@\h \W]\$\[\e[0m\] ' if (setenv("PROMPT_COMMAND", "export PS1=\"\\[\\e[1;32m\\][\\u@\\h \\W]\\$\\[\\e[0m\\] \"", 1) < 0) errExit("setenv"); // run cmdline trough /bin/bash if (cfg.command_line == NULL) { struct stat s; // replace the process with a shell if (stat("/bin/bash", &s) == 0) execlp("/bin/bash", "/bin/bash", NULL); else if (stat("/usr/bin/zsh", &s) == 0) execlp("/usr/bin/zsh", "/usr/bin/zsh", NULL); else if (stat("/bin/csh", &s) == 0) execlp("/bin/csh", "/bin/csh", NULL); else if (stat("/bin/sh", &s) == 0) execlp("/bin/sh", "/bin/sh", NULL); // no shell found, print an error and exit fprintf(stderr, "Error: no POSIX shell found\n"); sleep(5); exit(1); } else { // run the command supplied by the user 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) { if (chdir(cfg.homedir) < 0) errExit("chdir"); } } } char *arg[5]; arg[0] = "/bin/bash"; arg[1] = "-c"; if (arg_debug) printf("Starting %s\n", cfg.command_line); if (!arg_doubledash) { arg[2] = cfg.command_line; arg[3] = NULL; } else { arg[2] = "--"; arg[3] = cfg.command_line; arg[4] = NULL; } execvp("/bin/bash", arg); } // it will never get here!!! } // wait for the child to finish waitpid(child, NULL, 0); exit(0); }
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; 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) { fprintf(stderr, "Warning: 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); }
// load IBUS env variables void env_ibus_load(void) { // check ~/.config/ibus/bus directory char *dirname; if (asprintf(&dirname, "%s/.config/ibus/bus", cfg.homedir) == -1) errExit("asprintf"); struct stat s; if (stat(dirname, &s) == -1) return; // find the file /* coverity[toctou] */ DIR *dir = opendir(dirname); if (!dir) { free(dirname); return; } struct dirent *entry; while ((entry = readdir(dir)) != NULL) { // check the file name ends in "unix-0" char *ptr = strstr(entry->d_name, "unix-0"); if (!ptr) continue; if (strlen(ptr) != 6) continue; // open the file char *fname; if (asprintf(&fname, "%s/%s", dirname, entry->d_name) == -1) errExit("asprintf"); FILE *fp = fopen(fname, "r"); free(fname); if (!fp) continue; // read the file const int maxline = 4096; char buf[maxline]; while (fgets(buf, maxline, fp)) { if (strncmp(buf, "IBUS_", 5) != 0) continue; char *ptr = strchr(buf, '='); if (!ptr) continue; ptr = strchr(buf, '\n'); if (ptr) *ptr = '\0'; if (arg_debug) printf("%s\n", buf); EUID_USER(); env_store(buf, SETENV); EUID_ROOT(); } fclose(fp); } free(dirname); closedir(dir); }
void shut(pid_t pid) { EUID_ASSERT(); EUID_ROOT(); char *comm = pid_proc_comm(pid); EUID_USER(); if (comm) { if (strcmp(comm, "firejail") != 0) { fprintf(stderr, "Error: this is not a firejail sandbox\n"); exit(1); } free(comm); } else errExit("/proc/PID/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 shutdown a sandbox created by a different user.\n"); exit(1); } } printf("Sending SIGTERM to %u\n", pid); kill(pid, SIGTERM); // wait for not more than 11 seconds int monsec = 11; char *monfile; if (asprintf(&monfile, "/proc/%d/cmdline", pid) == -1) errExit("asprintf"); int killdone = 0; while (monsec) { sleep(1); monsec--; FILE *fp = fopen(monfile, "r"); if (!fp) { killdone = 1; break; } char c; size_t count = fread(&c, 1, 1, fp); fclose(fp); if (count == 0) { // all done killdone = 1; break; } } free(monfile); // force SIGKILL if (!killdone) { // kill the process and its child pid_t child; if (find_child(pid, &child) == 0) { printf("Sending SIGKILL to %u\n", child); kill(child, SIGKILL); } printf("Sending SIGKILL to %u\n", pid); kill(pid, SIGKILL); } EUID_ROOT(); delete_run_files(pid); }
static int monitor_application(pid_t app_pid) { monitored_pid = app_pid; signal (SIGTERM, sandbox_handler); EUID_USER(); int status = 0; while (monitored_pid) { usleep(20000); char *msg; if (asprintf(&msg, "monitoring pid %d\n", monitored_pid) == -1) errExit("asprintf"); logmsg(msg); if (arg_debug) printf("%s\n", msg); free(msg); pid_t rv; do { rv = waitpid(-1, &status, 0); if (rv == -1) break; } while(rv != monitored_pid); if (arg_debug) printf("Sandbox monitor: waitpid %u retval %d status %d\n", monitored_pid, rv, status); DIR *dir; if (!(dir = opendir("/proc"))) { // sleep 2 seconds and try again sleep(2); if (!(dir = opendir("/proc"))) { fprintf(stderr, "Error: cannot open /proc directory\n"); exit(1); } } struct dirent *entry; monitored_pid = 0; while ((entry = readdir(dir)) != NULL) { unsigned pid; if (sscanf(entry->d_name, "%u", &pid) != 1) continue; if (pid == 1) continue; // todo: make this generic // Dillo browser leaves a dpid process running, we need to shut it down if (strcmp(cfg.command_name, "dillo") == 0) { char *pidname = pid_proc_comm(pid); if (pidname && strcmp(pidname, "dpid") == 0) break; free(pidname); } monitored_pid = pid; break; } closedir(dir); if (monitored_pid != 0 && arg_debug) printf("Sandbox monitor: monitoring %u\n", monitored_pid); } // return the latest exit status. return status; #if 0 // todo: find a way to shut down interfaces before closing the namespace // the problem is we don't have enough privileges to shutdown interfaces in this moment // shut down bridge/macvlan interfaces if (any_bridge_configured()) { if (cfg.bridge0.configured) { printf("Shutting down %s\n", cfg.bridge0.devsandbox); net_if_down( cfg.bridge0.devsandbox); } if (cfg.bridge1.configured) { printf("Shutting down %s\n", cfg.bridge1.devsandbox); net_if_down( cfg.bridge1.devsandbox); } if (cfg.bridge2.configured) { printf("Shutting down %s\n", cfg.bridge2.devsandbox); net_if_down( cfg.bridge2.devsandbox); } if (cfg.bridge3.configured) { printf("Shutting down %s\n", cfg.bridge3.devsandbox); net_if_down( cfg.bridge3.devsandbox); } usleep(20000); // 20 ms sleep } #endif }
void join(pid_t pid, int argc, char **argv, int index) { EUID_ASSERT(); pid_t parent = pid; // in case the pid is that of a firejail process, use the pid of the first child process pid = switch_to_child(pid); // now check if the pid belongs to a firejail sandbox if (invalid_sandbox(pid)) { fprintf(stderr, "Error: no valid sandbox\n"); exit(1); } // 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); } } extract_x11_display(parent); EUID_ROOT(); // in user mode set caps seccomp, cpu, cgroup, etc if (getuid() != 0) { extract_nonewprivs(pid); // redundant on Linux >= 4.10; duplicated in function extract_caps extract_caps(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); // set umask, also uid 0 extract_umask(pid); // 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) { // drop discretionary access control capabilities for root sandboxes caps_drop_dac_override(); // 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); } EUID_USER(); if (chdir("/") < 0) errExit("chdir"); if (cfg.homedir) { struct stat s; if (stat(cfg.homedir, &s) == 0) { /* coverity[toctou] */ if (chdir(cfg.homedir) < 0) errExit("chdir"); } } // set caps filter EUID_ROOT(); if (apply_caps == 1) // not available for uid 0 caps_set(caps); #ifdef HAVE_SECCOMP if (getuid() != 0) seccomp_load_file_list(); #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); } // set nonewprivs if (arg_nonewprivs == 1) { // not available for uid 0 int rv = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); if (arg_debug && rv == 0) printf("NO_NEW_PRIVS set\n"); } EUID_USER(); 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"); } } } // drop privileges drop_privs(arg_nogroups); // kill the child in case the parent died prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); extract_command(argc, argv, index); if (cfg.command_line == NULL) { assert(cfg.shell); cfg.command_line = cfg.shell; cfg.window_title = cfg.shell; } if (arg_debug) printf("Extracted command #%s#\n", cfg.command_line); // set cpu affinity if (cfg.cpus) // not available for uid 0 set_cpu_affinity(); // set nice value if (arg_nice) set_nice(cfg.nice); // add x11 display if (display) { char *display_str; if (asprintf(&display_str, ":%d", display) == -1) errExit("asprintf"); setenv("DISPLAY", display_str, 1); free(display_str); } start_application(0, NULL); // it will never get here!!! } int status = 0; //***************************** // following code is signal-safe install_handler(); // wait for the child to finish waitpid(child, &status, 0); // restore default signal action signal(SIGTERM, SIG_DFL); // end of signal-safe code //***************************** flush_stdin(); if (WIFEXITED(status)) { status = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { status = WTERMSIG(status); } else { status = 0; } exit(status); }
//*********************************** // command execution //*********************************** void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, int up) { EUID_ASSERT(); //************************ // verify sandbox //************************ EUID_ROOT(); char *comm = pid_proc_comm(pid); EUID_USER(); if (!comm) { fprintf(stderr, "Error: cannot find sandbox\n"); exit(1); } // check for firejail sandbox if (strcmp(comm, "firejail") != 0) { fprintf(stderr, "Error: cannot find sandbox\n"); exit(1); } free(comm); // check network namespace char *name; if (asprintf(&name, "/run/firejail/network/%d-netmap", pid) == -1) errExit("asprintf"); struct stat s; if (stat(name, &s) == -1) { fprintf(stderr, "Error: the sandbox doesn't use a new network namespace\n"); exit(1); } //************************ // join the network namespace //************************ pid_t child; if (find_child(pid, &child) == -1) { fprintf(stderr, "Error: cannot join the network namespace\n"); exit(1); } EUID_ROOT(); if (join_namespace(child, "net")) { fprintf(stderr, "Error: cannot join the network namespace\n"); exit(1); } // set run file if (strcmp(command, "set") == 0) bandwidth_set(pid, dev, down, up); else if (strcmp(command, "clear") == 0) bandwidth_remove(pid, dev); //************************ // build command //************************ char *devname = NULL; if (dev) { // read network map file char *fname; if (asprintf(&fname, "%s/%d-netmap", RUN_FIREJAIL_NETWORK_DIR, (int) pid) == -1) errExit("asprintf"); FILE *fp = fopen(fname, "r"); if (!fp) { fprintf(stderr, "Error: cannot read network map file %s\n", fname); exit(1); } char buf[1024]; int len = strlen(dev); while (fgets(buf, 1024, fp)) { // remove '\n' char *ptr = strchr(buf, '\n'); if (ptr) *ptr = '\0'; if (*buf == '\0') break; if (strncmp(buf, dev, len) == 0 && buf[len] == ':') { devname = strdup(buf + len + 1); if (!devname) errExit("strdup"); // check device in namespace if (if_nametoindex(devname) == 0) { fprintf(stderr, "Error: cannot find network device %s\n", devname); exit(1); } break; } } free(fname); fclose(fp); } // build fshaper.sh command char *cmd = NULL; if (devname) { if (strcmp(command, "set") == 0) { if (asprintf(&cmd, "%s/firejail/fshaper.sh --%s %s %d %d", LIBDIR, command, devname, down, up) == -1) errExit("asprintf"); } else { if (asprintf(&cmd, "%s/firejail/fshaper.sh --%s %s", LIBDIR, command, devname) == -1) errExit("asprintf"); } } else { if (asprintf(&cmd, "%s/firejail/fshaper.sh --%s", LIBDIR, command) == -1) errExit("asprintf"); } assert(cmd); // wipe out environment variables environ = NULL; //************************ // build command //************************ // elevate privileges if (setreuid(0, 0)) errExit("setreuid"); if (setregid(0, 0)) errExit("setregid"); char *arg[4]; arg[0] = "/bin/sh"; arg[1] = "-c"; arg[2] = cmd; arg[3] = NULL; clearenv(); execvp(arg[0], arg); // it will never get here errExit("execvp"); }
int restricted_shell(const char *user) { EUID_ASSERT(); assert(user); // open profile file: char *fname; if (asprintf(&fname, "%s/login.users", SYSCONFDIR) == -1) errExit("asprintf"); FILE *fp = fopen(fname, "r"); free(fname); if (fp == NULL) return 0; int lineno = 0; char buf[MAX_READ]; while (fgets(buf, MAX_READ, fp)) { lineno++; // remove empty spaces at the beginning of the line char *ptr = buf; while (*ptr == ' ' || *ptr == '\t') { ptr++; } if (*ptr == '\n' || *ptr == '#') continue; // parse line char *usr = ptr; char *args = strchr(usr, ':'); if (args == NULL) { fprintf(stderr, "Error: users.conf line %d\n", lineno); exit(1); } *args = '\0'; args++; ptr = strchr(args, '\n'); if (ptr) *ptr = '\0'; // if nothing follows, continue char *ptr2 = args; int found = 0; while (*ptr2 != '\0') { if (*ptr2 != ' ' && *ptr2 != '\t') { found = 1; break; } ptr2++; } if (!found) continue; // process user if (strcmp(user, usr) == 0) { // extract program arguments fullargv[0] = "firejail"; int i; ptr = args; for (i = 1; i < MAX_ARGS; i++) { // skip blanks while (*ptr == ' ' || *ptr == '\t') ptr++; fullargv[i] = ptr; #ifdef DEBUG_RESTRICTED_SHELL {EUID_ROOT(); FILE *fp = fopen("/firelog", "a"); if (fp) { fprintf(fp, "i %d ptr #%s#\n", i, fullargv[i]); fclose(fp); } EUID_USER();} #endif if (*ptr != '\0') { // go to the end of the word while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0') ptr++; *ptr ='\0'; fullargv[i] = strdup(fullargv[i]); if (fullargv[i] == NULL) errExit("strdup"); ptr++; while (*ptr == ' ' || *ptr == '\t') ptr++; if (*ptr != '\0') continue; } fullargv[i] = strdup(fullargv[i]); fclose(fp); return i + 1; } fprintf(stderr, "Error: too many program arguments in users.conf line %d\n", lineno); exit(1); } } fclose(fp); return 0; }