Firejail Privilege Escalation
Posted on 12 January 2017
# firejail advisory for TOCTOU in --get and --put (local root) Releasing a brief advisory/writeup about a local root privesc found in firejail that we reported back in Nov, 2016. This is in response to a recent [thread](http://seclists.org/oss-sec/2017/q1/20) on oss-sec where people seem interested in details of firejail security issues. This particular vulnerability was fixed in commit [e152e2d](https://github.com/netblue30/firejail/commit/e152e2d067e17be33c7e82ce438c8ae740af6a66) but no CVE was assigned. ## Vulnerability This is a TOCTOU (race condition) bug when testing access permissions with access() and then calling copy_file(). At the time of discovery, it was clear the code suffered from many insecure coding constructs like this and much more -- but there was no guideline around making security related bug reports (other than using the public issue tracker). ### Code: src/firejail/ls.c ~~~ void sandboxfs(int op, pid_t pid, const char *path) { 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. "); exit(1); } } // full path or file in current directory? char *fname; if (*path == '/') { fname = strdup(path); if (!fname) errExit("strdup"); } else if (*path == '~') { if (asprintf(&fname, "%s%s", cfg.homedir, path + 1) == -1) errExit("asprintf"); } else { fprintf(stderr, "Error: Cannot access %s ", path); exit(1); } // sandbox root directory char *rootdir; if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) errExit("asprintf"); if (op == SANDBOX_FS_LS) { EUID_ROOT(); // chroot if (chroot(rootdir) < 0) errExit("chroot"); if (chdir("/") < 0) errExit("chdir"); // access chek is performed with the real UID if (access(fname, R_OK) == -1) { fprintf(stderr, "Error: Cannot access %s ", fname); exit(1); } // list directory contents struct stat s; if (stat(fname, &s) == -1) { fprintf(stderr, "Error: Cannot access %s ", fname); exit(1); } if (S_ISDIR(s.st_mode)) { char *rp = realpath(fname, NULL); if (!rp) { fprintf(stderr, "Error: Cannot access %s ", fname); exit(1); } if (arg_debug) printf("realpath %s ", rp); char *dir; if (asprintf(&dir, "%s/", rp) == -1) errExit("asprintf"); print_directory(dir); free(rp); free(dir); } else { char *rp = realpath(fname, NULL); if (!rp) { fprintf(stderr, "Error: Cannot access %s ", fname); exit(1); } if (arg_debug) printf("realpath %s ", rp); char *split = strrchr(rp, '/'); if (split) { *split = '