Exploit-DB #41022
Description: Firejail - Local Privilege Escalation
Type: local
Platform: linux
Date: 2017-01-09
Author: Daniel Hodson

Exploit:
# 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.\n");
                        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\n", 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\n", fname);
                        exit(1);
                }

                // list directory contents
                struct stat s;
                if (stat(fname, &s) == -1) {
                        fprintf(stderr, "Error: Cannot access %s\n", fname);
                        exit(1);
                }
                if (S_ISDIR(s.st_mode)) {
                        char *rp = realpath(fname, NULL);
                        if (!rp) {
                                fprintf(stderr, "Error: Cannot access %s\n", fname);
                                exit(1);
                        }
                        if (arg_debug)
                                printf("realpath %s\n", 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\n", fname);
                                exit(1);
                        }
                        if (arg_debug)
                                printf("realpath %s\n", rp);
                        char *split = strrchr(rp, '/');
                        if (split) {
                                *split = '\0';
                                char *rp2 = split + 1;
                                if (arg_debug)
                                        printf("path %s, file %s\n", rp, rp2);
                                print_file_or_dir(rp, rp2, 1);
                        }
                        free(rp);
                }
        }

        // get file from sandbox and store it in the current directory
        else if (op == SANDBOX_FS_GET) {
                // check source file (sandbox)
                char *src_fname;
                if (asprintf(&src_fname, "%s%s", rootdir, fname) == -1)
                        errExit("asprintf");
                EUID_ROOT();
                struct stat s;
                if (stat(src_fname, &s) == -1) {
                        fprintf(stderr, "Error: Cannot access %s\n", fname);
                        exit(1);
                }


                // try to open the source file - we need to chroot
                pid_t child = fork();
                if (child < 0)
                        errExit("fork");
                if (child == 0) {
                        // chroot
                        if (chroot(rootdir) < 0)
                                errExit("chroot");
                        if (chdir("/") < 0)
                                errExit("chdir");

                        // drop privileges
                        drop_privs(0);

                        // try to read the file
                        if (ac