//*********************************** // 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; } } if (!found) continue; // process user if (strcmp(user, usr) == 0) { restricted_user = strdup(user); // extract program arguments fullargv[0] = "firejail"; int i; ptr = args; for (i = 1; i < MAX_ARGS; i++) { fullargv[i] = ptr; while (*ptr != ' ' && *ptr != '\t' && *ptr != '\0') ptr++; if (*ptr != '\0') { *ptr ='\0'; fullargv[i] = strdup(fullargv[i]); if (fullargv[i] == NULL) { fprintf(stderr, "Error: cannot allocate memory\n"); exit(1); } 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; }
//$ Xephyr -ac -br -noreset -screen 800x600 :22 & //$ DISPLAY=:22 firejail --net=eth0 --blacklist=/tmp/.X11-unix/x0 firefox void x11_start_xephyr(int argc, char **argv) { EUID_ASSERT(); int i; struct stat s; pid_t jail = 0; pid_t server = 0; setenv("FIREJAIL_X11", "yes", 1); // unfortunately, xephyr does a number of weird things when started by root user!!! if (getuid() == 0) { fprintf(stderr, "Error: X11 sandboxing is not available when running as root\n"); exit(1); } drop_privs(0); // check xephyr if (x11_check_xephyr() == 0) { fprintf(stderr, "\nError: Xephyr program was not found in /usr/bin directory, please install it:\n"); fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xserver-xephyr\n"); fprintf(stderr, " Arch: sudo pacman -S xorg-server-xephyr\n"); exit(0); } int display = random_display_number(); char *display_str; if (asprintf(&display_str, ":%d", display) == -1) errExit("asprintf"); assert(xephyr_screen); char *server_argv[256] = { "Xephyr", "-ac", "-br", "-noreset", "-screen", xephyr_screen }; // rest initialyzed to NULL unsigned pos = 0; while (server_argv[pos] != NULL) pos++; if (checkcfg(CFG_XEPHYR_WINDOW_TITLE)) { server_argv[pos++] = "-title"; server_argv[pos++] = "firejail x11 sandbox"; } assert(xephyr_extra_params); // should be "" if empty // parse xephyr_extra_params // very basic quoting support char *temp = strdup(xephyr_extra_params); if (*xephyr_extra_params != '\0') { if (!temp) errExit("strdup"); bool dquote = false; bool squote = false; for (i = 0; i < (int) strlen(xephyr_extra_params); i++) { if (temp[i] == '\"') { dquote = !dquote; if (dquote) temp[i] = '\0'; // replace closing quote by \0 } if (temp[i] == '\'') { squote = !squote; if (squote) temp[i] = '\0'; // replace closing quote by \0 } if (!dquote && !squote && temp[i] == ' ') temp[i] = '\0'; if (dquote && squote) { fprintf(stderr, "Error: mixed quoting found while parsing xephyr_extra_params\n"); exit(1); } } if (dquote) { fprintf(stderr, "Error: unclosed quote found while parsing xephyr_extra_params\n"); exit(1); } for (i = 0; i < (int) strlen(xephyr_extra_params)-1; i++) { if (pos >= (sizeof(server_argv)/sizeof(*server_argv)) - 2) { fprintf(stderr, "Error: arg count limit exceeded while parsing xephyr_extra_params\n"); exit(1); } if (temp[i] == '\0' && (temp[i+1] == '\"' || temp[i+1] == '\'')) server_argv[pos++] = temp + i + 2; else if (temp[i] == '\0' && temp[i+1] != '\0') server_argv[pos++] = temp + i + 1; } } server_argv[pos++] = display_str; server_argv[pos++] = NULL; assert(pos < (sizeof(server_argv)/sizeof(*server_argv))); // no overrun assert(server_argv[pos-1] == NULL); // last element is null if (arg_debug) { size_t i = 0; printf("xephyr server:"); while (server_argv[i]!=NULL) { printf(" \"%s\"", server_argv[i]); i++; } putchar('\n'); } // remove --x11 arg char *jail_argv[argc+2]; int j = 0; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "--x11") == 0) continue; if (strcmp(argv[i], "--x11=xpra") == 0) continue; if (strcmp(argv[i], "--x11=xephyr") == 0) continue; jail_argv[j] = argv[i]; j++; } jail_argv[j] = NULL; assert(j < argc+2); // no overrun if (arg_debug) { size_t i = 0; printf("xephyr client:"); while (jail_argv[i]!=NULL) { printf(" \"%s\"", jail_argv[i]); i++; } putchar('\n'); } server = fork(); if (server < 0) errExit("fork"); if (server == 0) { if (arg_debug) printf("Starting xephyr...\n"); // running without privileges - see drop_privs call above assert(getenv("LD_PRELOAD") == NULL); execvp(server_argv[0], server_argv); perror("execvp"); _exit(1); } if (arg_debug) printf("xephyr server pid %d\n", server); // check X11 socket char *fname; if (asprintf(&fname, "/tmp/.X11-unix/X%d", display) == -1) errExit("asprintf"); int n = 0; // wait for x11 server to start while (++n < 10) { sleep(1); if (stat(fname, &s) == 0) break; }; if (n == 10) { fprintf(stderr, "Error: failed to start xephyr\n"); exit(1); } free(fname); if (arg_debug) { printf("X11 sockets: "); fflush(0); int rv = system("ls /tmp/.X11-unix"); (void) rv; } setenv("DISPLAY", display_str, 1); // run attach command jail = fork(); if (jail < 0) errExit("fork"); if (jail == 0) { if (!arg_quiet) printf("\n*** Attaching to Xephyr display %d ***\n\n", display); // running without privileges - see drop_privs call above assert(getenv("LD_PRELOAD") == NULL); execvp(jail_argv[0], jail_argv); perror("execvp"); _exit(1); } // cleanup free(display_str); free(temp); // wait for either server or jail termination pid_t pid = wait(NULL); // see which process terminated and kill other if (pid == server) { kill(jail, SIGTERM); } else if (pid == jail) { kill(server, SIGTERM); } // without this closing Xephyr window may mess your terminal: // "monitoring" process will release terminal before // jail process ends and releases terminal wait(NULL); // fulneral exit(0); }
void x11_start_xpra(int argc, char **argv) { EUID_ASSERT(); int i; struct stat s; pid_t client = 0; pid_t server = 0; setenv("FIREJAIL_X11", "yes", 1); // unfortunately, xpra does a number of weird things when started by root user!!! if (getuid() == 0) { fprintf(stderr, "Error: X11 sandboxing is not available when running as root\n"); exit(1); } drop_privs(0); // check xpra if (x11_check_xpra() == 0) { fprintf(stderr, "\nError: Xpra program was not found in /usr/bin directory, please install it:\n"); fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xpra\n"); exit(0); } int display = random_display_number(); char *display_str; if (asprintf(&display_str, ":%d", display) == -1) errExit("asprintf"); // build the start command char *server_argv[] = { "xpra", "start", display_str, "--no-daemon", NULL }; int fd_null = -1; if (arg_quiet) { fd_null = open("/dev/null", O_RDWR); if (fd_null == -1) errExit("open"); } // start server = fork(); if (server < 0) errExit("fork"); if (server == 0) { if (arg_debug) printf("Starting xpra...\n"); if (arg_quiet && fd_null != -1) { dup2(fd_null,0); dup2(fd_null,1); dup2(fd_null,2); } // running without privileges - see drop_privs call above assert(getenv("LD_PRELOAD") == NULL); execvp(server_argv[0], server_argv); perror("execvp"); _exit(1); } // add a small delay, on some systems it takes some time for the server to start sleep(1); // check X11 socket char *fname; if (asprintf(&fname, "/tmp/.X11-unix/X%d", display) == -1) errExit("asprintf"); int n = 0; // wait for x11 server to start while (++n < 10) { sleep(1); if (stat(fname, &s) == 0) break; } if (n == 10) { fprintf(stderr, "Error: failed to start xpra\n"); exit(1); } free(fname); if (arg_debug) { printf("X11 sockets: "); fflush(0); int rv = system("ls /tmp/.X11-unix"); (void) rv; } // build attach command char *attach_argv[] = { "xpra", "--title=\"firejail x11 sandbox\"", "attach", display_str, NULL }; // run attach command client = fork(); if (client < 0) errExit("fork"); if (client == 0) { if (arg_quiet && fd_null != -1) { dup2(fd_null,0); dup2(fd_null,1); dup2(fd_null,2); } if (!arg_quiet) printf("\n*** Attaching to xpra display %d ***\n\n", display); // running without privileges - see drop_privs call above assert(getenv("LD_PRELOAD") == NULL); execvp(attach_argv[0], attach_argv); perror("execvp"); _exit(1); } setenv("DISPLAY", display_str, 1); // build jail command char *firejail_argv[argc+2]; int pos = 0; for (i = 0; i < argc; i++) { if (strcmp(argv[i], "--x11") == 0) continue; if (strcmp(argv[i], "--x11=xpra") == 0) continue; if (strcmp(argv[i], "--x11=xephyr") == 0) continue; firejail_argv[pos] = argv[i]; pos++; } firejail_argv[pos] = NULL; assert(pos < (argc+2)); assert(!firejail_argv[pos]); // start jail pid_t jail = fork(); if (jail < 0) errExit("fork"); if (jail == 0) { // running without privileges - see drop_privs call above assert(getenv("LD_PRELOAD") == NULL); if (firejail_argv[0]) // shut up llvm scan-build execvp(firejail_argv[0], firejail_argv); perror("execvp"); exit(1); } if (!arg_quiet) printf("Xpra server pid %d, xpra client pid %d, jail %d\n", server, client, jail); sleep(1); // let jail start // wait for jail or server to end while (1) { pid_t pid = wait(NULL); if (pid == jail) { char *stop_argv[] = { "xpra", "stop", display_str, NULL }; pid_t stop = fork(); if (stop < 0) errExit("fork"); if (stop == 0) { if (arg_quiet && fd_null != -1) { dup2(fd_null,0); dup2(fd_null,1); dup2(fd_null,2); } // running without privileges - see drop_privs call above assert(getenv("LD_PRELOAD") == NULL); execvp(stop_argv[0], stop_argv); perror("execvp"); _exit(1); } // wait for xpra server to stop, 10 seconds limit while (++n < 10) { sleep(1); pid = waitpid(server, NULL, WNOHANG); if (pid == server) break; } if (arg_debug) { if (n == 10) printf("failed to stop xpra server gratefully\n"); else printf("xpra server successfully stopped in %d secs\n", n); } // kill xpra server and xpra client kill(client, SIGTERM); kill(server, SIGTERM); exit(0); } else if (pid == server) { // kill firejail process kill(jail, SIGTERM); // kill xpra client (should die with server, but...) kill(client, SIGTERM); exit(0); } } }
int checkcfg(int val) { EUID_ASSERT(); assert(val < CFG_MAX); int line = 0; if (!initialized) { // initialize defaults int i; for (i = 0; i < CFG_MAX; i++) cfg_val[i] = 1; // most of them are enabled by default cfg_val[CFG_RESTRICTED_NETWORK] = 0; // disabled by default cfg_val[CFG_FORCE_NONEWPRIVS] = 0; // disabled by default // open configuration file char *fname; if (asprintf(&fname, "%s/firejail.config", SYSCONFDIR) == -1) errExit("asprintf"); FILE *fp = fopen(fname, "r"); if (!fp) { #ifdef HAVE_GLOBALCFG fprintf(stderr, "Warning: Firejail configuration file %s not found\n", fname); exit(1); #else initialized = 1; return cfg_val[val]; #endif } // if the file exists, it should be owned by root struct stat s; if (stat(fname, &s) == -1) errExit("stat"); if (s.st_uid != 0 || s.st_gid != 0) { fprintf(stderr, "Error: configuration file should be owned by root\n"); exit(1); } // read configuration file char buf[MAX_READ]; while (fgets(buf,MAX_READ, fp)) { line++; if (*buf == '#' || *buf == '\n') continue; // parse line char *ptr = line_remove_spaces(buf); if (!ptr) continue; // file transfer if (strncmp(ptr, "file-transfer ", 14) == 0) { if (strcmp(ptr + 14, "yes") == 0) cfg_val[CFG_FILE_TRANSFER] = 1; else if (strcmp(ptr + 14, "no") == 0) cfg_val[CFG_FILE_TRANSFER] = 0; else goto errout; } // x11 else if (strncmp(ptr, "x11 ", 4) == 0) { if (strcmp(ptr + 4, "yes") == 0) cfg_val[CFG_X11] = 1; else if (strcmp(ptr + 4, "no") == 0) cfg_val[CFG_X11] = 0; else goto errout; } // bind else if (strncmp(ptr, "bind ", 5) == 0) { if (strcmp(ptr + 5, "yes") == 0) cfg_val[CFG_BIND] = 1; else if (strcmp(ptr + 5, "no") == 0) cfg_val[CFG_BIND] = 0; else goto errout; } // user namespace else if (strncmp(ptr, "userns ", 7) == 0) { if (strcmp(ptr + 7, "yes") == 0) cfg_val[CFG_USERNS] = 1; else if (strcmp(ptr + 7, "no") == 0) cfg_val[CFG_USERNS] = 0; else goto errout; } // chroot else if (strncmp(ptr, "chroot ", 7) == 0) { if (strcmp(ptr + 7, "yes") == 0) cfg_val[CFG_CHROOT] = 1; else if (strcmp(ptr + 7, "no") == 0) cfg_val[CFG_CHROOT] = 0; else goto errout; } // nonewprivs else if (strncmp(ptr, "force-nonewprivs ", 17) == 0) { if (strcmp(ptr + 17, "yes") == 0) cfg_val[CFG_SECCOMP] = 1; else if (strcmp(ptr + 17, "no") == 0) cfg_val[CFG_SECCOMP] = 0; else goto errout; } // seccomp else if (strncmp(ptr, "seccomp ", 8) == 0) { if (strcmp(ptr + 8, "yes") == 0) cfg_val[CFG_SECCOMP] = 1; else if (strcmp(ptr + 8, "no") == 0) cfg_val[CFG_SECCOMP] = 0; else goto errout; } // whitelist else if (strncmp(ptr, "whitelist ", 10) == 0) { if (strcmp(ptr + 10, "yes") == 0) cfg_val[CFG_WHITELIST] = 1; else if (strcmp(ptr + 10, "no") == 0) cfg_val[CFG_WHITELIST] = 0; else goto errout; } // network else if (strncmp(ptr, "network ", 8) == 0) { if (strcmp(ptr + 8, "yes") == 0) cfg_val[CFG_NETWORK] = 1; else if (strcmp(ptr + 8, "no") == 0) cfg_val[CFG_NETWORK] = 0; else goto errout; } // network else if (strncmp(ptr, "restricted-network ", 19) == 0) { if (strcmp(ptr + 19, "yes") == 0) cfg_val[CFG_RESTRICTED_NETWORK] = 1; else if (strcmp(ptr + 19, "no") == 0) cfg_val[CFG_RESTRICTED_NETWORK] = 0; else goto errout; } // netfilter else if (strncmp(ptr, "netfilter-default ", 18) == 0) { char *fname = ptr + 18; while (*fname == ' ' || *fname == '\t') ptr++; char *end = strchr(fname, ' '); if (end) *end = '\0'; // is the file present? struct stat s; if (stat(fname, &s) == -1) { fprintf(stderr, "Error: netfilter-default file %s not available\n", fname); exit(1); } netfilter_default = strdup(fname); if (!netfilter_default) errExit("strdup"); if (arg_debug) printf("netfilter default file %s\n", fname); } // Xephyr screen size else if (strncmp(ptr, "xephyr-screen ", 14) == 0) { // expecting two numbers and an x between them int n1; int n2; int rv = sscanf(ptr + 14, "%dx%d", &n1, &n2); if (rv != 2) goto errout; if (asprintf(&xephyr_screen, "%dx%d", n1, n2) == -1) errExit("asprintf"); } // xephyr window title else if (strncmp(ptr, "xephyr-window-title ", 20) == 0) { if (strcmp(ptr + 20, "yes") == 0) cfg_val[CFG_XEPHYR_WINDOW_TITLE] = 1; else if (strcmp(ptr + 20, "no") == 0) cfg_val[CFG_XEPHYR_WINDOW_TITLE] = 0; else goto errout; } // Xephyr command extra parameters else if (strncmp(ptr, "xephyr-extra-params ", 19) == 0) { xephyr_extra_params = strdup(ptr + 19); if (!xephyr_extra_params) errExit("strdup"); } else goto errout; free(ptr); } fclose(fp); free(fname); initialized = 1; } return cfg_val[val]; errout: fprintf(stderr, "Error: invalid line %d in firejail configuration file\n", line ); exit(1); }
void x11_start(int argc, char **argv) { EUID_ASSERT(); int i; struct stat s; pid_t client = 0; pid_t server = 0; // check xpra if (stat("/usr/bin/xpra", &s) == -1) { fprintf(stderr, "\nError: Xpra program was not found in /usr/bin directory, please install it:\n"); fprintf(stderr, " Debian/Ubuntu/Mint: sudo apt-get install xpra\n"); exit(0); } int display; int found = 1; for (i = 0; i < 100; i++) { display = rand() % 1024; char *fname; if (asprintf(&fname, "/tmp/.X11-unix/X%d", display) == -1) errExit("asprintf"); if (stat(fname, &s) == -1) { found = 1; break; } } if (!found) { fprintf(stderr, "Error: cannot pick up a random X11 display number, exiting...\n"); exit(1); } // build the start command int len = 50; // xpra start... for (i = 0; i < argc; i++) { len += strlen(argv[i]) + 1; // + ' ' } char *cmd1 = malloc(len + 1); // + '\0' if (!cmd1) errExit("malloc"); sprintf(cmd1, "xpra start :%d --exit-with-children --start-child=\"", display); char *ptr = cmd1 + strlen(cmd1); for (i = 0; i < argc; i++) { if (strcmp(argv[i], "--x11") == 0) continue; ptr += sprintf(ptr, "%s ", argv[i]); } sprintf(ptr, "\""); if (arg_debug) printf("xpra server: %s\n", cmd1); // build the attach command char *cmd2; if (asprintf(&cmd2, "xpra attach :%d", display) == -1) errExit("asprintf"); if (arg_debug) printf("xpra client: %s\n", cmd2); signal(SIGHUP,SIG_IGN); // fix sleep(1`) below server = fork(); if (server < 0) errExit("fork"); if (server == 0) { if (arg_debug) printf("Starting xpra...\n"); char *a[4]; a[0] = "/bin/bash"; a[1] = "-c"; a[2] = cmd1; a[3] = NULL; execvp(a[0], a); perror("execvp"); exit(1); } sleep(1); if (arg_debug) { printf("X11 sockets: "); fflush(0); int rv = system("ls /tmp/.X11-unix"); (void) rv; } // check X11 socket char *fname; if (asprintf(&fname, "/tmp/.X11-unix/X%d", display) == -1) errExit("asprintf"); if (stat(fname, &s) == -1) { fprintf(stderr, "Error: failed to start xpra\n"); exit(1); } // run attach command client = fork(); if (client < 0) errExit("fork"); if (client == 0) { printf("\n*** Attaching to xpra display %d ***\n\n", display); char *a[4]; a[0] = "/bin/bash"; a[1] = "-c"; a[2] = cmd2; a[3] = NULL; execvp(a[0], a); perror("execvp"); exit(1); } sleep(1); if (!arg_quiet) printf("Xpra server pid %d, client pid %d\n", server, client); exit(0); }
void profile_read(const char *fname) { EUID_ASSERT(); // exit program if maximum include level was reached if (include_level > MAX_INCLUDE_LEVEL) { fprintf(stderr, "Error: maximum profile include level was reached\n"); exit(1); } if (strlen(fname) == 0) { fprintf(stderr, "Error: invalid profile file\n"); exit(1); } // open profile file: FILE *fp = fopen(fname, "r"); if (fp == NULL) { fprintf(stderr, "Error: cannot open profile file %s\n", fname); exit(1); } if (!arg_quiet) fprintf(stderr, "Reading profile %s\n", fname); // read the file line by line char buf[MAX_READ + 1]; int lineno = 0; while (fgets(buf, MAX_READ, fp)) { ++lineno; // remove empty space - ptr in allocated memory char *ptr = line_remove_spaces(buf); if (ptr == NULL) continue; // comments if (*ptr == '#' || *ptr == '\0') { free(ptr); continue; } // process include if (strncmp(ptr, "include ", 8) == 0) { include_level++; // extract profile filename and new skip params char *newprofile = ptr + 8; // profile name // expand ${HOME}/ in front of the new profile file char *newprofile2 = expand_home(newprofile, cfg.homedir); // recursivity profile_read((newprofile2)? newprofile2:newprofile); include_level--; if (newprofile2) free(newprofile2); free(ptr); continue; } // verify syntax, exit in case of error if (profile_check_line(ptr, lineno, fname)) profile_add(ptr); // we cannot free ptr here, data is extracted from ptr and linked as a pointer in cfg structure // else { // free(ptr); // } } fclose(fp); }
// check profile line; if line == 0, this was generated from a command line option // return 1 if the command is to be added to the linked list of profile commands // return 0 if the command was already executed inside the function int profile_check_line(char *ptr, int lineno, const char *fname) { EUID_ASSERT(); // check ignore list int i; for (i = 0; i < MAX_PROFILE_IGNORE; i++) { if (cfg.profile_ignore[i] == NULL) break; if (strncmp(ptr, cfg.profile_ignore[i], strlen(cfg.profile_ignore[i])) == 0) return 0; // ignore line } if (strncmp(ptr, "ignore ", 7) == 0) { char *str = strdup(ptr + 7); if (*str == '\0') { fprintf(stderr, "Error: invalid ignore option\n"); exit(1); } // find an empty entry in profile_ignore array int j; for (j = 0; j < MAX_PROFILE_IGNORE; j++) { if (cfg.profile_ignore[j] == NULL) break; } if (j >= MAX_PROFILE_IGNORE) { fprintf(stderr, "Error: maximum %d --ignore options are permitted\n", MAX_PROFILE_IGNORE); exit(1); } // ... and configure it else cfg.profile_ignore[j] = str; return 0; } // mkdir if (strncmp(ptr, "mkdir ", 6) == 0) { fs_mkdir(ptr + 6); return 0; } // sandbox name else if (strncmp(ptr, "name ", 5) == 0) { cfg.name = ptr + 5; if (strlen(cfg.name) == 0) { fprintf(stderr, "Error: invalid sandbox name\n"); exit(1); } return 0; } else if (strcmp(ptr, "ipc-namespace") == 0) { arg_ipc = 1; return 0; } // seccomp, caps, private, user namespace else if (strcmp(ptr, "noroot") == 0) { #if HAVE_USERNS if (checkcfg(CFG_USERNS)) check_user_namespace(); else fprintf(stderr, "Warning: user namespace feature is disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "seccomp") == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) arg_seccomp = 1; else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "caps") == 0) { arg_caps_default_filter = 1; return 0; } else if (strcmp(ptr, "caps.drop all") == 0) { arg_caps_drop_all = 1; return 0; } else if (strcmp(ptr, "shell none") == 0) { arg_shell_none = 1; return 0; } else if (strcmp(ptr, "tracelog") == 0) { arg_tracelog = 1; return 0; } else if (strcmp(ptr, "private") == 0) { arg_private = 1; return 0; } else if (strcmp(ptr, "private-dev") == 0) { arg_private_dev = 1; return 0; } else if (strcmp(ptr, "private-tmp") == 0) { arg_private_tmp = 1; return 0; } else if (strcmp(ptr, "nogroups") == 0) { arg_nogroups = 1; return 0; } else if (strcmp(ptr, "nosound") == 0) { arg_nosound = 1; arg_private_dev = 1; return 0; } else if (strcmp(ptr, "netfilter") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) arg_netfilter = 1; else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "netfilter ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter = 1; arg_netfilter_file = strdup(ptr + 10); if (!arg_netfilter_file) errExit("strdup"); check_netfilter_file(arg_netfilter_file); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "netfilter6 ", 11) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_netfilter6 = 1; arg_netfilter6_file = strdup(ptr + 11); if (!arg_netfilter6_file) errExit("strdup"); check_netfilter_file(arg_netfilter6_file); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strcmp(ptr, "net none") == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { arg_nonetwork = 1; cfg.bridge0.configured = 0; cfg.bridge1.configured = 0; cfg.bridge2.configured = 0; cfg.bridge3.configured = 0; cfg.interface0.configured = 0; cfg.interface1.configured = 0; cfg.interface2.configured = 0; cfg.interface3.configured = 0; } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "net ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { #ifdef HAVE_NETWORK_RESTRICTED // compile time restricted networking if (getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } #endif // run time restricted networking if (checkcfg(CFG_RESTRICTED_NETWORK) && getuid() != 0) { fprintf(stderr, "Error: only \"net none\" is allowed to non-root users\n"); exit(1); } if (strcmp(ptr + 4, "lo") == 0) { fprintf(stderr, "Error: cannot attach to lo device\n"); exit(1); } Bridge *br; if (cfg.bridge0.configured == 0) br = &cfg.bridge0; else if (cfg.bridge1.configured == 0) br = &cfg.bridge1; else if (cfg.bridge2.configured == 0) br = &cfg.bridge2; else if (cfg.bridge3.configured == 0) br = &cfg.bridge3; else { fprintf(stderr, "Error: maximum 4 network devices are allowed\n"); exit(1); } net_configure_bridge(br, ptr + 4); } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "iprange ", 8) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->iprange_start || br->iprange_end) { fprintf(stderr, "Error: cannot configure the IP range twice for the same interface\n"); exit(1); } // parse option arguments char *firstip = ptr + 8; char *secondip = firstip; while (*secondip != '\0') { if (*secondip == ',') break; secondip++; } if (*secondip == '\0') { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } *secondip = '\0'; secondip++; // check addresses if (atoip(firstip, &br->iprange_start) || atoip(secondip, &br->iprange_end) || br->iprange_start >= br->iprange_end) { fprintf(stderr, "Error: invalid IP range\n"); exit(1); } if (in_netrange(br->iprange_start, br->ip, br->mask) || in_netrange(br->iprange_end, br->ip, br->mask)) { fprintf(stderr, "Error: IP range addresses not in network range\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } // from here else if (strncmp(ptr, "mac ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (mac_not_zero(br->macsandbox)) { fprintf(stderr, "Error: cannot configure the MAC address twice for the same interface\n"); exit(1); } // read the address if (atomac(ptr + 4, br->macsandbox)) { fprintf(stderr, "Error: invalid MAC address\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "mtu ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (sscanf(ptr + 4, "%d", &br->mtu) != 1 || br->mtu < 576 || br->mtu > 9198) { fprintf(stderr, "Error: invalid mtu value\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "ip ", 3) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ipsandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined if (strcmp(ptr + 3, "none") == 0) br->arg_ip_none = 1; else { if (atoip(ptr + 3, &br->ipsandbox)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "ip6 ", 4) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { Bridge *br = last_bridge_configured(); if (br == NULL) { fprintf(stderr, "Error: no network device configured\n"); exit(1); } if (br->arg_ip_none || br->ip6sandbox) { fprintf(stderr, "Error: cannot configure the IP address twice for the same interface\n"); exit(1); } // configure this IP address for the last bridge defined // todo: verify ipv6 syntax br->ip6sandbox = ptr + 4; // if (atoip(argv[i] + 5, &br->ipsandbox)) { // fprintf(stderr, "Error: invalid IP address\n"); // exit(1); // } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } else if (strncmp(ptr, "defaultgw ", 10) == 0) { #ifdef HAVE_NETWORK if (checkcfg(CFG_NETWORK)) { if (atoip(ptr + 10, &cfg.defaultgw)) { fprintf(stderr, "Error: invalid IP address\n"); exit(1); } } else fprintf(stderr, "Warning: networking features are disabled in Firejail configuration file\n"); #endif return 0; } if (strncmp(ptr, "protocol ", 9) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) protocol_store(ptr + 9); else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } if (strncmp(ptr, "env ", 4) == 0) { env_store(ptr + 4); return 0; } // seccomp drop list on top of default list if (strncmp(ptr, "seccomp ", 8) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list = strdup(ptr + 8); if (!cfg.seccomp_list) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // seccomp drop list without default list if (strncmp(ptr, "seccomp.drop ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_drop = strdup(ptr + 13); if (!cfg.seccomp_list_drop) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // seccomp keep list if (strncmp(ptr, "seccomp.keep ", 13) == 0) { #ifdef HAVE_SECCOMP if (checkcfg(CFG_SECCOMP)) { arg_seccomp = 1; cfg.seccomp_list_keep= strdup(ptr + 13); if (!cfg.seccomp_list_keep) errExit("strdup"); } else fprintf(stderr, "Warning: user seccomp feature is disabled in Firejail configuration file\n"); #endif return 0; } // caps drop list if (strncmp(ptr, "caps.drop ", 10) == 0) { arg_caps_drop = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // caps keep list if (strncmp(ptr, "caps.keep ", 10) == 0) { arg_caps_keep = 1; arg_caps_list = strdup(ptr + 10); if (!arg_caps_list) errExit("strdup"); // verify seccomp list and exit if problems if (caps_check_list(arg_caps_list, NULL)) exit(1); return 0; } // hostname if (strncmp(ptr, "hostname ", 9) == 0) { cfg.hostname = ptr + 9; return 0; } // dns if (strncmp(ptr, "dns ", 4) == 0) { uint32_t dns; if (atoip(ptr + 4, &dns)) { fprintf(stderr, "Error: invalid DNS server IP address\n"); return 1; } if (cfg.dns1 == 0) cfg.dns1 = dns; else if (cfg.dns2 == 0) cfg.dns2 = dns; else if (cfg.dns3 == 0) cfg.dns3 = dns; else { fprintf(stderr, "Error: up to 3 DNS servers can be specified\n"); return 1; } return 0; } // cpu affinity if (strncmp(ptr, "cpu ", 4) == 0) { read_cpu_list(ptr + 4); return 0; } // nice value if (strncmp(ptr, "nice ", 4) == 0) { cfg.nice = atoi(ptr + 5); if (getuid() != 0 &&cfg.nice < 0) cfg.nice = 0; arg_nice = 1; return 0; } // cgroup if (strncmp(ptr, "cgroup ", 7) == 0) { set_cgroup(ptr + 7); return 0; } // writable-etc if (strcmp(ptr, "writable-etc") == 0) { if (cfg.etc_private_keep) { fprintf(stderr, "Error: private-etc and writable-etc are mutually exclusive\n"); exit(1); } arg_writable_etc = 1; return 0; } // writable-var if (strcmp(ptr, "writable-var") == 0) { arg_writable_var = 1; return 0; } // private directory if (strncmp(ptr, "private ", 8) == 0) { cfg.home_private = ptr + 8; fs_check_private_dir(); arg_private = 1; return 0; } // private /etc list of files and directories if (strncmp(ptr, "private-etc ", 12) == 0) { if (arg_writable_etc) { fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n"); exit(1); } cfg.etc_private_keep = ptr + 12; fs_check_etc_list(); arg_private_etc = 1; return 0; } // private /bin list of files if (strncmp(ptr, "private-bin ", 12) == 0) { cfg.bin_private_keep = ptr + 12; arg_private_bin = 1; fs_check_bin_list(); return 0; } // filesystem bind if (strncmp(ptr, "bind ", 5) == 0) { #ifdef HAVE_BIND if (checkcfg(CFG_BIND)) { if (getuid() != 0) { fprintf(stderr, "Error: --bind option is available only if running as root\n"); exit(1); } // extract two directories char *dname1 = ptr + 5; char *dname2 = split_comma(dname1); // this inserts a '0 to separate the two dierctories if (dname2 == NULL) { fprintf(stderr, "Error: missing second directory for bind\n"); exit(1); } // check directories invalid_filename(dname1); invalid_filename(dname2); if (strstr(dname1, "..") || strstr(dname2, "..")) { fprintf(stderr, "Error: invalid file name.\n"); exit(1); } if (is_link(dname1) || is_link(dname2)) { fprintf(stderr, "Symbolic links are not allowed for bind command\n"); exit(1); } // insert comma back *(dname2 - 1) = ','; return 1; } else { fprintf(stderr, "Warning: bind feature is disabled in Firejail configuration file\n"); return 0; } #else return 0; #endif } // rlimit if (strncmp(ptr, "rlimit", 6) == 0) { if (strncmp(ptr, "rlimit-nofile ", 14) == 0) { ptr += 14; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nofile); arg_rlimit_nofile = 1; } else if (strncmp(ptr, "rlimit-nproc ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_nproc); arg_rlimit_nproc = 1; } else if (strncmp(ptr, "rlimit-fsize ", 13) == 0) { ptr += 13; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_fsize); arg_rlimit_fsize = 1; } else if (strncmp(ptr, "rlimit-sigpending ", 18) == 0) { ptr += 18; if (not_unsigned(ptr)) { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } sscanf(ptr, "%u", &cfg.rlimit_sigpending); arg_rlimit_sigpending = 1; } else { fprintf(stderr, "Invalid rlimit option on line %d\n", lineno); exit(1); } return 0; } // read-write if (strncmp(ptr, "read-write ", 11) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: read-write command is available only for root user\n"); exit(1); } fs_rdwr_add(ptr + 11); return 0; } // rest of filesystem if (strncmp(ptr, "blacklist ", 10) == 0) ptr += 10; else if (strncmp(ptr, "blacklist-nolog ", 16) == 0) ptr += 16; else if (strncmp(ptr, "noblacklist ", 12) == 0) ptr += 12; else if (strncmp(ptr, "whitelist ", 10) == 0) { arg_whitelist = 1; ptr += 10; } else if (strncmp(ptr, "read-only ", 10) == 0) ptr += 10; else if (strncmp(ptr, "tmpfs ", 6) == 0) { if (getuid() != 0) { fprintf(stderr, "Error: tmpfs available only when running the sandbox as root\n"); exit(1); } ptr += 6; } else { if (lineno == 0) fprintf(stderr, "Error: \"%s\" as a command line option is invalid\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } // some characters just don't belong in filenames invalid_filename(ptr); if (strstr(ptr, "..")) { if (lineno == 0) fprintf(stderr, "Error: \"%s\" is an invalid filename\n", ptr); else if (fname != NULL) fprintf(stderr, "Error: line %d in %s is invalid\n", lineno, fname); else fprintf(stderr, "Error: line %d in the custom profile is invalid\n", lineno); exit(1); } return 1; }
void run_symlink(int argc, char **argv) { EUID_ASSERT(); char *program = strrchr(argv[0], '/'); if (program) program += 1; else program = argv[0]; if (strcmp(program, "firejail") == 0) return; // find the real program // probably the first entry returend by "which -a" is a symlink - use the second entry! char *p = getenv("PATH"); if (!p) { fprintf(stderr, "Error: PATH environment variable not set\n"); exit(1); } char *path = strdup(p); if (!path) errExit("strdup"); char *selfpath = realpath("/proc/self/exe", NULL); if (!selfpath) errExit("realpath"); // look in path for our program char *tok = strtok(path, ":"); int found = 0; while (tok) { char *name; if (asprintf(&name, "%s/%s", tok, program) == -1) errExit("asprintf"); struct stat s; if (stat(name, &s) == 0) { char* rp = realpath(name, NULL); if (!rp) errExit("realpath"); if (strcmp(selfpath, rp) != 0) { program = strdup(name); found = 1; free(rp); break; } free(rp); } free(name); tok = strtok(NULL, ":"); } if (!found) { fprintf(stderr, "Error: cannot find the program in the path\n"); exit(1); } free(selfpath); // start the argv[0] program in a new sandbox char *firejail; if (asprintf(&firejail, "%s/bin/firejail", PREFIX) == -1) errExit("asprintf"); printf("Redirecting symlink to %s\n", program); // run command char *a[3 + argc]; a[0] = firejail; a[1] = program; int i; for (i = 0; i < (argc - 1); i++) a[i + 2] = argv[i + 1]; a[i + 2] = NULL; execvp(a[0], a); perror("execvp"); exit(1); }