From b09fab085fc95024aa41ae02959d126f294b89929e24c8bf033b03fe17014bf4 Mon Sep 17 00:00:00 2001 From: Sebastian Wagner Date: Wed, 8 Jun 2022 21:08:53 +0000 Subject: [PATCH] - fix bsc#1199148 CVE-2022-31214 by adding patch fix-CVE-2022-31214.patch using commits from upstream. OBS-URL: https://build.opensuse.org/package/show/Virtualization/firejail?expand=0&rev=45 --- firejail.changes | 6 + firejail.spec | 3 + fix-CVE-2022-31214.patch | 2244 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 2253 insertions(+) create mode 100644 fix-CVE-2022-31214.patch diff --git a/firejail.changes b/firejail.changes index fe241fa..bc0f38f 100644 --- a/firejail.changes +++ b/firejail.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Jun 8 21:08:03 UTC 2022 - Sebastian Wagner + +- fix bsc#1199148 CVE-2022-31214 by adding patch fix-CVE-2022-31214.patch + using commits from upstream. + ------------------------------------------------------------------- Mon Feb 28 19:38:38 UTC 2022 - Sebastian Wagner diff --git a/firejail.spec b/firejail.spec index c16030a..d129126 100644 --- a/firejail.spec +++ b/firejail.spec @@ -29,6 +29,8 @@ Source1: https://github.com/netblue30/%{name}/releases/download/%{version Source2: %{name}.keyring # PATCH-FIX-UPSTREAM fix-internet-access.patch -- from https://github.com/netblue30/firejail/commit/bb334a8fd4f0911a8dfa1538d02fbd0574b81333.patch Patch0: fix-internet-access.patch +# PATCH-FIX-UPSTREAM fix-CVE-2022-31214.patch -- from https://github.com/netblue30/firejail/commit/27cde3d7d1e4e16d4190932347c7151dc2a84c50 and https://github.com/netblue30/firejail/commit/dab835e7a0eb287822016f5ae4e87f46e1d363e7.patch and https://github.com/netblue30/firejail/commit/1884ea22a90d225950d81c804f1771b42ae55f54 +Patch1: fix-CVE-2022-31214.patch BuildRequires: fdupes BuildRequires: gcc-c++ BuildRequires: libapparmor-devel @@ -68,6 +70,7 @@ Optional dependency offering zsh completion for firejail %setup -q sed -i '1s/^#!\/usr\/bin\/env /#!\/usr\/bin\//' contrib/fj-mkdeb.py contrib/fjclip.py contrib/fjdisplay.py contrib/fjresize.py contrib/sort.py contrib/fix_private-bin.py contrib/jail_prober.py %patch0 -p1 +%patch1 -p1 %build %configure --docdir=%{_docdir}/%{name} \ diff --git a/fix-CVE-2022-31214.patch b/fix-CVE-2022-31214.patch new file mode 100644 index 0000000..b7ef72b --- /dev/null +++ b/fix-CVE-2022-31214.patch @@ -0,0 +1,2244 @@ +From 27cde3d7d1e4e16d4190932347c7151dc2a84c50 Mon Sep 17 00:00:00 2001 +From: smitsohu +Date: Wed, 8 Jun 2022 12:12:04 +0200 +Subject: [PATCH] fixing CVE-2022-31214 + +--- + src/firejail/caps.c | 36 +-- + src/firejail/cgroup.c | 24 -- + src/firejail/cpu.c | 49 +--- + src/firejail/firejail.h | 31 ++- + src/firejail/fs.c | 1 + + src/firejail/fs_logger.c | 20 +- + src/firejail/join.c | 496 ++++++++++++++++++------------------ + src/firejail/ls.c | 32 +-- + src/firejail/main.c | 35 ++- + src/firejail/network_main.c | 45 +--- + src/firejail/preproc.c | 6 + + src/firejail/process.c | 244 ++++++++++++++++++ + src/firejail/protocol.c | 20 +- + src/firejail/run_files.c | 52 ++++ + src/firejail/seccomp.c | 51 ++-- + src/firejail/shutdown.c | 62 +---- + src/firejail/util.c | 157 ++---------- + src/include/common.h | 3 +- + src/include/rundefs.h | 1 + + src/lib/common.c | 77 +++++- + 20 files changed, 781 insertions(+), 661 deletions(-) + create mode 100644 src/firejail/process.c + +diff --git a/src/firejail/caps.c b/src/firejail/caps.c +index c5c06c6751..d88a991327 100644 +--- a/src/firejail/caps.c ++++ b/src/firejail/caps.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -381,34 +382,21 @@ void caps_keep_list(const char *clist) { + } + + #define MAXBUF 4098 +-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, "re"); +- EUID_USER(); // grsecurity +- if (!fp) +- goto errexit; ++static uint64_t extract_caps(ProcessHandle process) { ++ FILE *fp = process_fopen(process, "status"); + + 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; ++ if (sscanf(buf + 8, "%llx", &val) == 1) { ++ fclose(fp); ++ return val; ++ } ++ break; + } + } +- fclose(fp); + +-errexit: +- free(file); + fprintf(stderr, "Error: cannot read caps configuration\n"); + exit(1); + } +@@ -416,13 +404,11 @@ static uint64_t extract_caps(int pid) { + void caps_print_filter(pid_t pid) { + EUID_ASSERT(); + +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // exit if no permission to join the sandbox +- check_join_permission(pid); ++ uint64_t caps = extract_caps(sandbox); ++ unpin_process(sandbox); + +- uint64_t caps = extract_caps(pid); + int i; + uint64_t mask; + int elems = sizeof(capslist) / sizeof(capslist[0]); +diff --git a/src/firejail/cgroup.c b/src/firejail/cgroup.c +index f1e16187fb..c8cb96f98d 100644 +--- a/src/firejail/cgroup.c ++++ b/src/firejail/cgroup.c +@@ -46,30 +46,6 @@ void save_cgroup(void) { + exit(1); + } + +-void load_cgroup(const char *fname) { +- if (!fname) +- return; +- +- FILE *fp = fopen(fname, "re"); +- if (fp) { +- char buf[MAXBUF]; +- if (fgets(buf, MAXBUF, fp)) { +- cfg.cgroup = strdup(buf); +- if (!cfg.cgroup) +- errExit("strdup"); +- } +- else +- goto errout; +- +- fclose(fp); +- return; +- } +-errout: +- fwarning("cannot load control group\n"); +- if (fp) +- fclose(fp); +-} +- + static int is_cgroup_path(const char *fname) { + // path starts with /sys/fs/cgroup + if (strncmp(fname, "/sys/fs/cgroup", 14) != 0) +diff --git a/src/firejail/cpu.c b/src/firejail/cpu.c +index 1ec510456a..917726359b 100644 +--- a/src/firejail/cpu.c ++++ b/src/firejail/cpu.c +@@ -18,6 +18,7 @@ + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "firejail.h" ++#include + #include + #include + #include +@@ -87,22 +88,6 @@ void save_cpu(void) { + } + } + +-void load_cpu(const char *fname) { +- if (!fname) +- return; +- +- FILE *fp = fopen(fname, "re"); +- if (fp) { +- unsigned tmp; +- int rv = fscanf(fp, "%x", &tmp); +- if (rv) +- cfg.cpus = (uint32_t) tmp; +- fclose(fp); +- } +- else +- fwarning("cannot load cpu affinity mask\n"); +-} +- + void set_cpu_affinity(void) { + // set cpu affinity + cpu_set_t mask; +@@ -131,21 +116,8 @@ void set_cpu_affinity(void) { + } + } + +-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, "re"); +- EUID_USER(); // grsecurity +- if (!fp) { +- printf(" Error: cannot open %s\n", file); +- free(file); +- return; +- } ++static void print_cpu(ProcessHandle process) { ++ FILE *fp = process_fopen(process, "status"); + + #define MAXBUF 4096 + char buf[MAXBUF]; +@@ -153,28 +125,21 @@ static void print_cpu(int pid) { + if (strncmp(buf, "Cpus_allowed_list:", 18) == 0) { + printf(" %s", buf); + fflush(0); +- free(file); + fclose(fp); + return; + } + } + fclose(fp); +- free(file); + } + +-// allow any user to run --cpu.print ++// TODO: allow any user to run --cpu.print + void cpu_print_filter(pid_t pid) { + EUID_ASSERT(); + +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // now check if the pid belongs to a firejail sandbox +- if (is_ready_for_join(pid) == false) { +- fprintf(stderr, "Error: no valid sandbox\n"); +- exit(1); +- } ++ print_cpu(sandbox); ++ unpin_process(sandbox); + +- print_cpu(pid); + exit(0); + } +diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h +index 38408b5345..7e1d45c011 100644 +--- a/src/firejail/firejail.h ++++ b/src/firejail/firejail.h +@@ -477,11 +477,29 @@ void top(void); + // usage.c + void usage(void); + ++// process.c ++typedef struct processhandle_instance_t * ProcessHandle; ++ ++ProcessHandle pin_process(pid_t pid); ++void unpin_process(ProcessHandle process); ++pid_t process_get_pid(ProcessHandle process); ++int process_get_fd(ProcessHandle process); ++int process_stat_nofail(ProcessHandle process, const char *fname, struct stat *s); ++int process_stat(ProcessHandle process, const char *fname, struct stat *s); ++int process_open_nofail(ProcessHandle process, const char *fname); ++int process_open(ProcessHandle process, const char *fname); ++FILE *process_fopen(ProcessHandle process, const char *fname); ++int process_join_namespace(ProcessHandle process, char *type); ++void process_send_signal(ProcessHandle process, int signum); ++ProcessHandle pin_parent_process(ProcessHandle process); ++ProcessHandle pin_child_process(ProcessHandle process, pid_t child); ++void process_rootfs_chroot(ProcessHandle process); ++int process_rootfs_stat(ProcessHandle process, const char *fname, struct stat *s); ++int process_rootfs_open(ProcessHandle process, const char *fname); ++ + // join.c ++ProcessHandle pin_sandbox_process(pid_t pid); + void join(pid_t pid, int argc, char **argv, int index) __attribute__((noreturn)); +-bool is_ready_for_join(const pid_t pid); +-void check_join_permission(pid_t pid); +-pid_t switch_to_child(pid_t pid); + + // shutdown.c + void shut(pid_t pid); +@@ -648,13 +666,11 @@ void set_rlimits(void); + // cpu.c + void read_cpu_list(const char *str); + void set_cpu_affinity(void); +-void load_cpu(const char *fname); + void save_cpu(void); + void cpu_print_filter(pid_t pid) __attribute__((noreturn)); + + // cgroup.c + void save_cgroup(void); +-void load_cgroup(const char *fname); + void check_cgroup_file(const char *fname); + void set_cgroup(const char *fname, pid_t pid); + +@@ -862,9 +878,7 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc, + #define PATH_FSECCOMP_MAIN (LIBDIR "/firejail/fseccomp") // when called from main thread + #define PATH_FSECCOMP ( RUN_FIREJAIL_LIB_DIR "/fseccomp") // when called from sandbox thread + +-// FSEC_PRINT is run outside of sandbox by --seccomp.print +-// it is also run from inside the sandbox by --debug; in this case we do an access(filename, X_OK) test first +-#define PATH_FSEC_PRINT (LIBDIR "/firejail/fsec-print") ++#define PATH_FSEC_PRINT (RUN_FIREJAIL_LIB_DIR "/fsec-print") + + #define PATH_FSEC_OPTIMIZE (RUN_FIREJAIL_LIB_DIR "/fsec-optimize") + +@@ -899,6 +913,7 @@ void delete_bandwidth_run_file(pid_t pid); + void set_name_run_file(pid_t pid); + void set_x11_run_file(pid_t pid, int display); + void set_profile_run_file(pid_t pid, const char *fname); ++int set_sandbox_run_file(pid_t pid, pid_t child); + + // dbus.c + int dbus_check_name(const char *name); +diff --git a/src/firejail/fs.c b/src/firejail/fs.c +index c03cd7a12e..f30e56e8d8 100644 +--- a/src/firejail/fs.c ++++ b/src/firejail/fs.c +@@ -834,6 +834,7 @@ void disable_config(void) { + free(fname); + + // disable run time information ++ disable_file(BLACKLIST_FILE, RUN_FIREJAIL_SANDBOX_DIR); + disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR); + disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR); + disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR); +diff --git a/src/firejail/fs_logger.c b/src/firejail/fs_logger.c +index 06f03dac52..8b6c41278e 100644 +--- a/src/firejail/fs_logger.c ++++ b/src/firejail/fs_logger.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #define MAXBUF 4098 + +@@ -120,24 +121,21 @@ void fs_logger_change_owner(void) { + void fs_logger_print_log(pid_t pid) { + EUID_ASSERT(); + +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // exit if no permission to join the sandbox +- check_join_permission(pid); ++ // chroot in the sandbox ++ process_rootfs_chroot(sandbox); ++ unpin_process(sandbox); + +- // print RUN_FSLOGGER_FILE +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_FSLOGGER_FILE) == -1) +- errExit("asprintf"); ++ drop_privs(0); + +- EUID_ROOT(); +- FILE *fp = fopen(fname, "re"); +- free(fname); ++ // print RUN_FSLOGGER_FILE ++ FILE *fp = fopen(RUN_FSLOGGER_FILE, "re"); + if (!fp) { + fprintf(stderr, "Error: Cannot open filesystem log\n"); + exit(1); + } ++ + char buf[MAXBUF]; + while (fgets(buf, MAXBUF, fp)) + printf("%s", buf); +diff --git a/src/firejail/join.c b/src/firejail/join.c +index 5d73f71be2..5acdcb0601 100644 +--- a/src/firejail/join.c ++++ b/src/firejail/join.c +@@ -42,6 +42,7 @@ static uint64_t caps = 0; + static unsigned display = 0; + #define BUFLEN 4096 + ++ + static void signal_handler(int sig){ + flush_stdin(); + +@@ -58,46 +59,6 @@ static void install_handler(void) { + sigaction(SIGTERM, &sga, NULL); + } + +-#ifdef HAVE_APPARMOR +-static void extract_apparmor(pid_t pid) { +- if (checkcfg(CFG_APPARMOR)) { +- EUID_USER(); +- if (aa_is_enabled() == 1) { +- // get pid of next child process +- pid_t child; +- if (find_child(pid, &child) == 1) +- child = pid; // no child, proceed with current pid +- +- // get name of AppArmor profile +- char *fname; +- if (asprintf(&fname, "/proc/%d/attr/current", child) == -1) +- errExit("asprintf"); +- EUID_ROOT(); +- int fd = open(fname, O_RDONLY|O_CLOEXEC); +- EUID_USER(); +- free(fname); +- if (fd == -1) +- goto errexit; +- char buf[BUFLEN]; +- ssize_t rv = read(fd, buf, sizeof(buf) - 1); +- close(fd); +- if (rv < 0) +- goto errexit; +- buf[rv] = '\0'; +- // process confined by Firejail's AppArmor policy? +- if (strncmp(buf, "firejail-default", 16) == 0) +- arg_apparmor = 1; +- } +- EUID_ROOT(); +- } +- return; +- +-errexit: +- fprintf(stderr, "Error: cannot read /proc file\n"); +- exit(1); +-} +-#endif // HAVE_APPARMOR +- + static void extract_x11_display(pid_t pid) { + char *fname; + if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) +@@ -150,191 +111,140 @@ static void extract_command(int argc, char **argv, int index) { + build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, index, true); + } + +-static void extract_nogroups(pid_t pid) { +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_GROUPS_CFG) == -1) +- errExit("asprintf"); ++static int open_shell(void) { ++ EUID_ASSERT(); ++ assert(cfg.shell); + +- struct stat s; +- if (stat(fname, &s) == -1) { +- free(fname); +- return; ++ if (arg_debug) ++ printf("Opening shell %s\n", cfg.shell); ++ // file descriptor will leak if not opened with O_CLOEXEC !! ++ int fd = open(cfg.shell, O_PATH|O_CLOEXEC); ++ if (fd == -1) { ++ fprintf(stderr, "Error: cannot open shell %s\n", cfg.shell); ++ exit(1); + } + +- arg_nogroups = 1; +- free(fname); +-} +- +-static void extract_nonewprivs(pid_t pid) { +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_NONEWPRIVS_CFG) == -1) ++ // file descriptor needs to reach final fexecve ++ if (asprintf(&cfg.keep_fd, "%s,%d", cfg.keep_fd ? cfg.keep_fd : "", fd) == -1) + errExit("asprintf"); + +- struct stat s; +- if (stat(fname, &s) == -1) { +- free(fname); +- return; +- } +- +- arg_nonewprivs = 1; +- free(fname); ++ return fd; + } + +-static void extract_cpu(pid_t pid) { +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_CPU_CFG) == -1) +- errExit("asprintf"); +- ++static void extract_nogroups(ProcessHandle sandbox) { + struct stat s; +- if (stat(fname, &s) == -1) { +- free(fname); +- return; +- } + +- // there is a CPU_CFG file, load it! +- load_cpu(fname); +- free(fname); ++ if (process_rootfs_stat(sandbox, RUN_GROUPS_CFG, &s) == 0) ++ arg_nogroups = 1; + } + +-static void extract_cgroup(pid_t pid) { +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_CGROUP_CFG) == -1) +- errExit("asprintf"); +- ++static void extract_nonewprivs(ProcessHandle sandbox) { + struct stat s; +- if (stat(fname, &s) == -1) { +- free(fname); +- return; +- } + +- // there is a cgroup file CGROUP_CFG, load it! +- load_cgroup(fname); +- free(fname); ++ if (process_rootfs_stat(sandbox, RUN_NONEWPRIVS_CFG, &s) == 0) ++ arg_nonewprivs = 1; + } + +-static void extract_caps(pid_t pid) { +- // open stat file +- char *file; +- if (asprintf(&file, "/proc/%u/status", pid) == -1) { +- perror("asprintf"); +- exit(1); +- } +- FILE *fp = fopen(file, "re"); +- if (!fp) +- goto errexit; ++static void extract_caps(ProcessHandle sandbox) { ++ // open status file ++ FILE *fp = process_fopen(sandbox, "status"); + + char buf[BUFLEN]; +- while (fgets(buf, BUFLEN - 1, fp)) { ++ while (fgets(buf, BUFLEN, fp)) { + if (strncmp(buf, "CapBnd:", 7) == 0) { +- char *ptr = buf + 7; + unsigned long long val; +- if (sscanf(ptr, "%llx", &val) != 1) ++ if (sscanf(buf + 7, "%llx", &val) != 1) + goto errexit; + apply_caps = 1; + caps = val; + } + else if (strncmp(buf, "NoNewPrivs:", 11) == 0) { +- char *ptr = buf + 11; + int val; +- if (sscanf(ptr, "%d", &val) != 1) ++ if (sscanf(buf + 11, "%d", &val) != 1) + goto errexit; + if (val) + arg_nonewprivs = 1; + } + } + fclose(fp); +- free(file); + return; + + errexit: +- fprintf(stderr, "Error: cannot read stat file for process %u\n", pid); ++ fprintf(stderr, "Error: cannot read /proc/%d/status\n", process_get_pid(sandbox)); + exit(1); + } + +-static void extract_user_namespace(pid_t pid) { ++static void extract_user_namespace(ProcessHandle sandbox) { + // test user namespaces available in the kernel +- struct stat s1; +- struct stat s2; +- struct stat s3; +- if (stat("/proc/self/ns/user", &s1) == 0 && +- stat("/proc/self/uid_map", &s2) == 0 && +- stat("/proc/self/gid_map", &s3) == 0); +- else ++ struct stat self_userns; ++ if (stat("/proc/self/ns/user", &self_userns) != 0) + return; + +- // read uid map +- char *uidmap; +- if (asprintf(&uidmap, "/proc/%u/uid_map", pid) == -1) +- errExit("asprintf"); +- FILE *fp = fopen(uidmap, "re"); +- if (!fp) { +- free(uidmap); +- return; +- } ++ // check sandbox user namespace ++ struct stat dest_userns; ++ process_stat(sandbox, "ns/user", &dest_userns); + +- // check uid map +- int u1; +- int u2; +- if (fscanf(fp, "%d %d", &u1, &u2) == 2) { +- if (arg_debug) +- printf("User namespace detected: %s, %d, %d\n", uidmap, u1, u2); +- if (u1 != 0 || u2 != 0) +- arg_noroot = 1; +- } ++ if (dest_userns.st_ino != self_userns.st_ino || ++ dest_userns.st_dev != self_userns.st_dev) ++ arg_noroot = 1; ++} ++ ++static void extract_cpu(ProcessHandle sandbox) { ++ int fd = process_rootfs_open(sandbox, RUN_CPU_CFG); ++ if (fd < 0) ++ return; // not configured ++ ++ FILE *fp = fdopen(fd, "r"); ++ if (!fp) ++ errExit("fdopen"); ++ ++ unsigned tmp; ++ if (fscanf(fp, "%x", &tmp) == 1) ++ cfg.cpus = (uint32_t) tmp; + fclose(fp); +- free(uidmap); + } + +-static void extract_umask(pid_t pid) { +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_UMASK_FILE) == -1) +- errExit("asprintf"); ++static void extract_cgroup(ProcessHandle sandbox) { ++ int fd = process_rootfs_open(sandbox, RUN_CGROUP_CFG); ++ if (fd < 0) ++ return; // not configured + +- FILE *fp = fopen(fname, "re"); +- free(fname); +- if (!fp) { +- fprintf(stderr, "Error: cannot open umask file\n"); +- exit(1); +- } +- if (fscanf(fp, "%3o", &orig_umask) != 1) { +- fprintf(stderr, "Error: cannot read umask\n"); +- exit(1); ++ FILE *fp = fdopen(fd, "r"); ++ if (!fp) ++ errExit("fdopen"); ++ ++ char buf[BUFLEN]; ++ if (fgets(buf, BUFLEN, fp)) { ++ cfg.cgroup = strdup(buf); ++ if (!cfg.cgroup) ++ errExit("strdup"); + } + fclose(fp); + } + +-static int open_shell(void) { +- EUID_ASSERT(); +- assert(cfg.shell); +- +- if (arg_debug) +- printf("Opening shell %s\n", cfg.shell); +- // file descriptor will leak if not opened with O_CLOEXEC !! +- int fd = open(cfg.shell, O_PATH|O_CLOEXEC); +- if (fd == -1) { +- fprintf(stderr, "Error: cannot open shell %s\n", cfg.shell); ++static void extract_umask(ProcessHandle sandbox) { ++ int fd = process_rootfs_open(sandbox, RUN_UMASK_FILE); ++ if (fd < 0) { ++ fprintf(stderr, "Error: cannot open umask file\n"); + exit(1); + } + +- // pass file descriptor through to the final fexecve +- if (asprintf(&cfg.keep_fd, "%s,%d", cfg.keep_fd ? cfg.keep_fd : "", fd) == -1) +- errExit("asprintf"); ++ FILE *fp = fdopen(fd, "r"); ++ if (!fp) ++ errExit("fdopen"); + +- return fd; ++ if (fscanf(fp, "%3o", &orig_umask) != 1) { ++ fprintf(stderr, "Error: cannot read umask\n"); ++ exit(1); ++ } ++ fclose(fp); + } + +-// return false if the sandbox identified by pid is not fully set up yet or if +-// it is no firejail sandbox at all, return true if the sandbox is complete +-bool is_ready_for_join(const pid_t pid) { +- EUID_ASSERT(); ++// returns false if the sandbox is not fully set up yet, ++// or true if the sandbox is complete ++static bool has_join_file(ProcessHandle sandbox) { + // check if a file /run/firejail/mnt/join exists +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_JOIN_FILE) == -1) +- errExit("asprintf"); +- EUID_ROOT(); +- int fd = open(fname, O_RDONLY|O_CLOEXEC); +- EUID_USER(); +- free(fname); ++ int fd = process_rootfs_open(sandbox, RUN_JOIN_FILE); + if (fd == -1) + return false; + struct stat s; +@@ -354,84 +264,183 @@ bool is_ready_for_join(const pid_t pid) { + } + + #define SNOOZE 10000 // sleep interval in microseconds +-void check_join_permission(pid_t pid) { ++static void check_joinable(ProcessHandle sandbox) { + // check if pid belongs to a fully set up firejail sandbox + unsigned long i; +- for (i = SNOOZE; is_ready_for_join(pid) == false; i += SNOOZE) { // give sandbox some time to start up ++ for (i = SNOOZE; has_join_file(sandbox) == false; i += SNOOZE) { // give sandbox some time to start up + if (i > join_timeout) { + fprintf(stderr, "Error: no valid sandbox\n"); + exit(1); + } + usleep(SNOOZE); + } +- // 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); +- } ++} ++ ++static ProcessHandle find_pidns_parent(pid_t pid) { ++ // identify current pid namespace ++ struct stat self_pidns; ++ if (stat("/proc/self/ns/pid", &self_pidns) < 0) ++ errExit("stat"); ++ ++ ProcessHandle process = pin_process(pid); ++ ++ // in case pid is member of a different pid namespace ++ // find parent who created that namespace ++ while (1) { ++ struct stat dest_pidns; ++ process_stat(process, "ns/pid", &dest_pidns); ++ ++ if (dest_pidns.st_ino == self_pidns.st_ino && ++ dest_pidns.st_dev == self_pidns.st_dev) ++ break; // always true for init process ++ ++ // next parent process ++ ProcessHandle next = pin_parent_process(process); ++ unpin_process(process); ++ process = next; + } ++ ++ return process; + } + +-pid_t switch_to_child(pid_t pid) { +- EUID_ASSERT(); +- EUID_ROOT(); +- pid_t rv = pid; +- errno = 0; +- char *comm = pid_proc_comm(pid); +- if (!comm) { +- if (errno == ENOENT) +- fprintf(stderr, "Error: cannot find process with pid %d\n", pid); +- else +- fprintf(stderr, "Error: cannot read /proc file\n"); ++static void check_firejail_comm(ProcessHandle process) { ++ // open /proc/pid/comm ++ // note: comm value is under control of the target process ++ FILE *fp = process_fopen(process, "comm"); ++ ++ char comm[16]; ++ if (fscanf(fp, "%15s", comm) != 1) { ++ fprintf(stderr, "Error: cannot read /proc file\n"); + exit(1); + } +- EUID_USER(); ++ fclose(fp); + +- if (strcmp(comm, "firejail") == 0) { +- if (find_child(pid, &rv) == 1) { +- fprintf(stderr, "Error: no valid sandbox\n"); +- exit(1); +- } +- fmessage("Switching to pid %u, the first child process inside the sandbox\n", (unsigned) rv); ++ if (strcmp(comm, "firejail") != 0) { ++ fprintf(stderr, "Error: no valid sandbox\n"); ++ exit(1); + } +- free(comm); +- return rv; ++ ++ return; + } + ++static void check_firejail_credentials(ProcessHandle process) { ++ // open /proc/pid/status ++ FILE *fp = process_fopen(process, "status"); + ++ uid_t ruid = -1; ++ uid_t suid = -1; ++ char buf[4096]; ++ while (fgets(buf, sizeof(buf), fp)) { ++ if (sscanf(buf, "Uid: %u %*u %u", &ruid, &suid) == 2) ++ break; ++ } ++ fclose(fp); + +-void join(pid_t pid, int argc, char **argv, int index) { ++ // target process should be privileged and owned by the user ++ if (suid != 0) ++ goto errexit; ++ uid_t u = getuid(); ++ if (ruid != u && u != 0) ++ goto errexit; ++ ++ return; ++ ++errexit: ++ fprintf(stderr, "Error: no valid sandbox\n"); ++ exit(1); ++} ++ ++static pid_t read_sandbox_pidfile(pid_t parent) { ++ char *pidfile; ++ if (asprintf(&pidfile, "%s/%d", RUN_FIREJAIL_SANDBOX_DIR, parent) == -1) ++ errExit("asprintf"); ++ ++ // open the pidfile ++ EUID_ROOT(); ++ int pidfile_fd = open(pidfile, O_RDWR|O_CLOEXEC); ++ free(pidfile); ++ EUID_USER(); ++ if (pidfile_fd < 0) ++ goto errexit; ++ ++ // assume pidfile is outdated if parent doesn't hold a lock ++ struct flock pidfile_lock = { ++ .l_type = F_WRLCK, ++ .l_whence = SEEK_SET, ++ .l_start = 0, ++ .l_len = 0, ++ .l_pid = 0, ++ }; ++ if (fcntl(pidfile_fd, F_GETLK, &pidfile_lock) < 0) ++ errExit("fcntl"); ++ if (pidfile_lock.l_type == F_UNLCK) ++ goto errexit; ++ if (pidfile_lock.l_pid != parent) ++ goto errexit; ++ ++ // read pidfile ++ pid_t sandbox; ++ FILE *fp = fdopen(pidfile_fd, "r"); ++ if (!fp) ++ errExit("fdopen"); ++ if (fscanf(fp, "%d", &sandbox) != 1) ++ goto errexit; ++ fclose(fp); ++ ++ return sandbox; ++ ++errexit: ++ fprintf(stderr, "Error: no valid sandbox\n"); ++ exit(1); ++} ++ ++static ProcessHandle switch_to_sandbox(ProcessHandle parent) { ++ // firejail forks many children, identify the sandbox child ++ // using a pidfile created by the sandbox parent ++ pid_t pid = read_sandbox_pidfile(process_get_pid(parent)); ++ ++ // pin the sandbox child ++ fmessage("Switching to pid %d, the first child process inside the sandbox\n", pid); ++ ProcessHandle sandbox = pin_child_process(parent, pid); ++ ++ return sandbox; ++} ++ ++ProcessHandle pin_sandbox_process(pid_t pid) { + 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); ++ ProcessHandle parent = find_pidns_parent(pid); ++ check_firejail_comm(parent); ++ check_firejail_credentials(parent); ++ ++ ProcessHandle sandbox = switch_to_sandbox(parent); ++ check_joinable(sandbox); ++ ++ unpin_process(parent); ++ return sandbox; ++} + +- // exit if no permission to join the sandbox +- check_join_permission(pid); + +- extract_x11_display(parent); ++ ++void join(pid_t pid, int argc, char **argv, int index) { ++ EUID_ASSERT(); ++ ProcessHandle sandbox = pin_sandbox_process(pid); ++ ++ extract_x11_display(pid); + + int shfd = -1; + if (!arg_shell_none) + shfd = open_shell(); + +- 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); +- extract_umask(pid); +-#ifdef HAVE_APPARMOR +- extract_apparmor(pid); +-#endif ++ extract_nonewprivs(sandbox); // redundant on Linux >= 4.10; duplicated in function extract_caps ++ extract_caps(sandbox); ++ extract_cpu(sandbox); ++ extract_cgroup(sandbox); ++ extract_nogroups(sandbox); ++ extract_user_namespace(sandbox); ++ extract_umask(sandbox); + } + + // set cgroup +@@ -439,20 +448,21 @@ void join(pid_t pid, int argc, char **argv, int index) { + set_cgroup(cfg.cgroup, getpid()); + + // join namespaces ++ EUID_ROOT(); + if (arg_join_network) { +- if (join_namespace(pid, "net")) ++ if (process_join_namespace(sandbox, "net")) + exit(1); + } + else if (arg_join_filesystem) { +- if (join_namespace(pid, "mnt")) ++ if (process_join_namespace(sandbox, "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")) ++ if (process_join_namespace(sandbox, "ipc") || ++ process_join_namespace(sandbox, "net") || ++ process_join_namespace(sandbox, "pid") || ++ process_join_namespace(sandbox, "uts") || ++ process_join_namespace(sandbox, "mnt")) + exit(1); + } + +@@ -463,42 +473,25 @@ void join(pid_t pid, int argc, char **argv, int index) { + // 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); +- } ++ // mount namespace doesn't know about --chroot ++ fmessage("Changing root to /proc/%d/root\n", process_get_pid(sandbox)); ++ process_rootfs_chroot(sandbox); + +- 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"); +- } ++ // load seccomp filters ++ if (getuid() != 0) ++ seccomp_load_file_list(); + } + + // set caps filter +- EUID_ROOT(); + if (apply_caps == 1) // not available for uid 0 + caps_set(caps); +- if (getuid() != 0) +- seccomp_load_file_list(); + +- // mount user namespace or drop privileges ++ // user namespace + if (arg_noroot) { // not available for uid 0 + if (arg_debug) + printf("Joining user namespace\n"); +- if (join_namespace(1, "user")) ++ if (process_join_namespace(sandbox, "user")) + exit(1); + + // user namespace resets capabilities +@@ -506,6 +499,8 @@ void join(pid_t pid, int argc, char **argv, int index) { + if (apply_caps == 1) // not available for uid 0 + caps_set(caps); + } ++ EUID_USER(); ++ unpin_process(sandbox); + + // set nonewprivs + if (arg_nonewprivs == 1) { // not available for uid 0 +@@ -514,7 +509,6 @@ void join(pid_t pid, int argc, char **argv, int index) { + printf("NO_NEW_PRIVS set\n"); + } + +- EUID_USER(); + int cwd = 0; + if (cfg.cwd) { + if (chdir(cfg.cwd) == 0) +@@ -579,6 +573,8 @@ void join(pid_t pid, int argc, char **argv, int index) { + __builtin_unreachable(); + } + EUID_USER(); ++ unpin_process(sandbox); ++ + if (shfd != -1) + close(shfd); + +diff --git a/src/firejail/ls.c b/src/firejail/ls.c +index 4156a7b250..5ffd468a43 100644 +--- a/src/firejail/ls.c ++++ b/src/firejail/ls.c +@@ -286,11 +286,7 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { + EUID_ASSERT(); + assert(path1); + +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); +- +- // exit if no permission to join the sandbox +- check_join_permission(pid); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + + // expand paths + char *fname1 = expand_path(path1); +@@ -337,18 +333,9 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { + op = SANDBOX_FS_CAT; + } + +- // sandbox root directory +- char *rootdir; +- if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) +- errExit("asprintf"); +- + if (op == SANDBOX_FS_LS || op == SANDBOX_FS_CAT) { +- EUID_ROOT(); +- // chroot +- if (chroot(rootdir) < 0) +- errExit("chroot"); +- if (chdir("/") < 0) +- errExit("chdir"); ++ // chroot into the sandbox ++ process_rootfs_chroot(sandbox); + + // drop privileges + drop_privs(0); +@@ -410,11 +397,8 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { + if (child < 0) + errExit("fork"); + if (child == 0) { +- // chroot +- if (chroot(rootdir) < 0) +- errExit("chroot"); +- if (chdir("/") < 0) +- errExit("chdir"); ++ // chroot into the sandbox ++ process_rootfs_chroot(sandbox); + + // drop privileges + drop_privs(0); +@@ -442,10 +426,6 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { + EUID_USER(); + } + +- if (fname2) +- free(fname2); +- free(fname1); +- free(rootdir); +- ++ unpin_process(sandbox); + exit(0); + } +diff --git a/src/firejail/main.c b/src/firejail/main.c +index 7344be23a2..7081e067f4 100644 +--- a/src/firejail/main.c ++++ b/src/firejail/main.c +@@ -1011,6 +1011,7 @@ int main(int argc, char **argv, char **envp) { + int prog_index = -1; // index in argv where the program command starts + int lockfd_network = -1; + int lockfd_directory = -1; ++ int lockfd_sandboxfile = -1; + int option_cgroup = 0; + int custom_profile = 0; // custom profile loaded + int arg_caps_cmdline = 0; // caps requested on command line (used to break out of --chroot) +@@ -3119,6 +3120,9 @@ int main(int argc, char **argv, char **envp) { + errExit("clone"); + EUID_USER(); + ++ // sandbox pidfile ++ lockfd_sandboxfile = set_sandbox_run_file(getpid(), child); ++ + if (!arg_command && !arg_quiet) { + fmessage("Parent pid %u, child pid %u\n", sandbox_pid, child); + // print the path of the new log directory +@@ -3301,12 +3305,28 @@ int main(int argc, char **argv, char **envp) { + + // lock netfilter firewall + if (arg_netlock) { +- char *cmd; +- if (asprintf(&cmd, "firejail --netlock=%d&", getpid()) == -1) +- errExit("asprintf"); +- int rv = system(cmd); +- (void) rv; +- free(cmd); ++ pid_t netlock_child = fork(); ++ if (netlock_child < 0) ++ errExit("fork"); ++ if (netlock_child == 0) { ++ close_all(NULL, 0); ++ // drop privileges ++ if (setresgid(-1, getgid(), getgid()) != 0) ++ errExit("setresgid"); ++ if (setresuid(-1, getuid(), getuid()) != 0) ++ errExit("setresuid"); ++ ++ char arg[64]; ++ snprintf(arg, sizeof(arg), "--netlock=%d", getpid()); ++ ++ char *cmd[3]; ++ cmd[0] = BINDIR "/firejail"; ++ cmd[1] = arg; ++ cmd[2] = NULL; ++ execvp(cmd[0], cmd); ++ perror("Cannot start netlock"); ++ _exit(1); ++ } + } + + int status = 0; +@@ -3326,7 +3346,8 @@ int main(int argc, char **argv, char **envp) { + // end of signal-safe code + //***************************** + +- ++ // release lock ++ close(lockfd_sandboxfile); + + if (WIFEXITED(status)){ + myexit(WEXITSTATUS(status)); +diff --git a/src/firejail/network_main.c b/src/firejail/network_main.c +index dd66ecc55c..46ddf269e8 100644 +--- a/src/firejail/network_main.c ++++ b/src/firejail/network_main.c +@@ -271,44 +271,25 @@ void net_check_cfg(void) { + #define MAXBUF 4096 + void net_dns_print(pid_t pid) { + EUID_ASSERT(); +- // drop privileges - will not be able to read /etc/resolv.conf for --noroot option ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); ++ // chroot in the sandbox ++ process_rootfs_chroot(sandbox); ++ unpin_process(sandbox); + +- // exit if no permission to join the sandbox +- check_join_permission(pid); ++ drop_privs(0); + +- EUID_ROOT(); +- if (join_namespace(pid, "mnt")) ++ // read /etc/resolv.conf ++ FILE *fp = fopen("/etc/resolv.conf", "re"); ++ if (!fp) { ++ fprintf(stderr, "Error: cannot read /etc/resolv.conf\n"); + exit(1); +- +- pid_t child = fork(); +- if (child < 0) +- errExit("fork"); +- if (child == 0) { +- caps_drop_all(); +- if (chdir("/") < 0) +- errExit("chdir"); +- +- // access /etc/resolv.conf +- FILE *fp = fopen("/etc/resolv.conf", "re"); +- 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); +- exit(0); + } + +- // wait for the child to finish +- waitpid(child, NULL, 0); +- flush_stdin(); ++ char buf[MAXBUF]; ++ while (fgets(buf, MAXBUF, fp)) ++ printf("%s", buf); ++ + exit(0); + } + +diff --git a/src/firejail/preproc.c b/src/firejail/preproc.c +index 0517f3506a..c117150b88 100644 +--- a/src/firejail/preproc.c ++++ b/src/firejail/preproc.c +@@ -38,6 +38,12 @@ void preproc_build_firejail_dir(void) { + create_empty_dir_as_root(RUN_FIREJAIL_DIR, 0755); + } + ++ // restricted search permission ++ // only root should be able to lock files in this directory ++ if (stat(RUN_FIREJAIL_SANDBOX_DIR, &s)) { ++ create_empty_dir_as_root(RUN_FIREJAIL_SANDBOX_DIR, 0700); ++ } ++ + if (stat(RUN_FIREJAIL_NETWORK_DIR, &s)) { + create_empty_dir_as_root(RUN_FIREJAIL_NETWORK_DIR, 0755); + } +diff --git a/src/firejail/process.c b/src/firejail/process.c +new file mode 100644 +index 0000000000..5adb4f8cc2 +--- /dev/null ++++ b/src/firejail/process.c +@@ -0,0 +1,244 @@ ++/* ++ * Copyright (C) 2014-2022 Firejail Authors ++ * ++ * This file is part of firejail project ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License along ++ * with this program; if not, write to the Free Software Foundation, Inc., ++ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ */ ++#include "firejail.h" ++#include ++#include ++ ++#include ++#ifndef O_PATH ++#define O_PATH 010000000 ++#endif ++ ++#include ++#ifndef __NR_pidfd_send_signal ++#define __NR_pidfd_send_signal 424 ++#endif ++ ++#define BUFLEN 4096 ++ ++struct processhandle_instance_t { ++ pid_t pid; ++ int fd; // file descriptor referring to /proc/[PID] ++}; ++ ++ ++ProcessHandle pin_process(pid_t pid) { ++ EUID_ASSERT(); ++ ++ ProcessHandle rv = malloc(sizeof(struct processhandle_instance_t)); ++ if (!rv) ++ errExit("malloc"); ++ rv->pid = pid; ++ ++ char proc[64]; ++ snprintf(proc, sizeof(proc), "/proc/%d", pid); ++ ++ EUID_ROOT(); ++ int fd = open(proc, O_RDONLY|O_CLOEXEC); ++ EUID_USER(); ++ if (fd < 0) { ++ if (errno == ENOENT) ++ fprintf(stderr, "Error: cannot find process with pid %d\n", pid); ++ else ++ fprintf(stderr, "Error: cannot open %s: %s\n", proc, strerror(errno)); ++ exit(1); ++ } ++ rv->fd = fd; ++ ++ return rv; ++} ++ ++void unpin_process(ProcessHandle process) { ++ close(process->fd); ++ free(process); ++} ++ ++pid_t process_get_pid(ProcessHandle process) { ++ return process->pid; ++} ++ ++int process_get_fd(ProcessHandle process) { ++ return process->fd; ++} ++ ++/********************************************* ++ * access path in proc filesystem ++ *********************************************/ ++ ++int process_stat_nofail(ProcessHandle process, const char *fname, struct stat *s) { ++ EUID_ASSERT(); ++ assert(fname[0] != '/'); ++ ++ EUID_ROOT(); ++ int rv = fstatat(process_get_fd(process), fname, s, 0); ++ EUID_USER(); ++ ++ return rv; ++} ++ ++int process_stat(ProcessHandle process, const char *fname, struct stat *s) { ++ int rv = process_stat_nofail(process, fname, s); ++ if (rv) { ++ fprintf(stderr, "Error: cannot stat /proc/%d/%s: %s\n", process->pid, fname, strerror(errno)); ++ exit(1); ++ } ++ ++ return rv; ++} ++ ++int process_open_nofail(ProcessHandle process, const char *fname) { ++ EUID_ASSERT(); ++ assert(fname[0] != '/'); ++ ++ EUID_ROOT(); ++ int rv = openat(process_get_fd(process), fname, O_RDONLY|O_CLOEXEC); ++ EUID_USER(); ++ ++ return rv; ++} ++ ++int process_open(ProcessHandle process, const char *fname) { ++ int rv = process_open_nofail(process, fname); ++ if (rv < 0) { ++ fprintf(stderr, "Error: cannot open /proc/%d/%s: %s\n", process->pid, fname, strerror(errno)); ++ exit(1); ++ } ++ ++ return rv; ++} ++ ++FILE *process_fopen(ProcessHandle process, const char *fname) { ++ int fd = process_open(process, fname); ++ FILE *rv = fdopen(fd, "r"); ++ if (!rv) ++ errExit("fdopen"); ++ ++ return rv; ++} ++ ++int process_join_namespace(ProcessHandle process, char *type) { ++ return join_namespace_by_fd(process_get_fd(process), type); ++} ++ ++/********************************************* ++ * sending a signal ++ *********************************************/ ++ ++void process_send_signal(ProcessHandle process, int signum) { ++ fmessage("Sending signal %d to pid %d\n", signum, process_get_pid(process)); ++ ++ if (syscall(__NR_pidfd_send_signal, process_get_fd(process), signum, NULL, 0) == -1 && errno == ENOSYS) ++ kill(process_get_pid(process), signum); ++} ++ ++/********************************************* ++ * parent and child process ++ *********************************************/ ++ ++static pid_t process_parent_pid(ProcessHandle process) { ++ pid_t rv = 0; ++ ++ FILE *fp = process_fopen(process, "status"); ++ char buf[BUFLEN]; ++ while (fgets(buf, BUFLEN, fp)) { ++ if (sscanf(buf, "PPid: %d", &rv) == 1) ++ break; ++ } ++ fclose(fp); ++ ++ return rv; ++} ++ ++static ProcessHandle pin_process_relative_to(ProcessHandle process, pid_t pid) { ++ ProcessHandle rv = malloc(sizeof(struct processhandle_instance_t)); ++ if (!rv) ++ errExit("malloc"); ++ rv->pid = pid; ++ ++ char proc[64]; ++ snprintf(proc, sizeof(proc), "../%d", pid); ++ ++ rv->fd = process_open(process, proc); ++ ++ return rv; ++} ++ ++ProcessHandle pin_parent_process(ProcessHandle process) { ++ ProcessHandle parent = pin_process_relative_to(process, process_parent_pid(process)); ++ return parent; ++} ++ ++ProcessHandle pin_child_process(ProcessHandle process, pid_t child_pid) { ++ ProcessHandle child = pin_process_relative_to(process, child_pid); ++ ++ // verify parent/child relationship ++ if (process_parent_pid(child) != process_get_pid(process)) { ++ fprintf(stderr, "Error: cannot find child process of pid %d\n", process_get_pid(process)); ++ exit(1); ++ } ++ ++ return child; ++} ++ ++/********************************************* ++ * access process rootfs ++ *********************************************/ ++ ++void process_rootfs_chroot(ProcessHandle process) { ++ if (fchdir(process_get_fd(process)) < 0) ++ errExit("fchdir"); ++ ++ int called_as_root = 0; ++ if (geteuid() == 0) ++ called_as_root = 1; ++ if (called_as_root == 0) ++ EUID_ROOT(); ++ ++ if (chroot("root") < 0) ++ errExit("chroot"); ++ ++ if (called_as_root == 0) ++ EUID_USER(); ++ ++ if (chdir("/") < 0) ++ errExit("chdir"); ++} ++ ++int process_rootfs_stat(ProcessHandle process, const char *fname, struct stat *s) { ++ char *proc; ++ if (asprintf(&proc, "root%s", fname) < 0) ++ errExit("asprintf"); ++ ++ int rv = process_stat_nofail(process, proc, s); ++ ++ free(proc); ++ return rv; ++} ++ ++int process_rootfs_open(ProcessHandle process, const char *fname) { ++ char *proc; ++ if (asprintf(&proc, "root%s", fname) < 0) ++ errExit("asprintf"); ++ ++ int rv = process_open_nofail(process, proc); ++ ++ free(proc); ++ return rv; ++} +diff --git a/src/firejail/protocol.c b/src/firejail/protocol.c +index 37e541f509..37782b756a 100644 +--- a/src/firejail/protocol.c ++++ b/src/firejail/protocol.c +@@ -63,27 +63,23 @@ void protocol_print_filter(pid_t pid) { + + (void) pid; + #ifdef SYS_socket +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // exit if no permission to join the sandbox +- check_join_permission(pid); ++ // chroot in the sandbox ++ process_rootfs_chroot(sandbox); ++ unpin_process(sandbox); + + // 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) { ++ if (stat(RUN_PROTOCOL_CFG, &s) != 0) { + printf("Cannot access seccomp filter.\n"); + exit(1); + } + + // read and print the filter +- protocol_filter_load(fname); +- free(fname); ++ EUID_ROOT(); ++ protocol_filter_load(RUN_PROTOCOL_CFG); ++ + if (cfg.protocol) + printf("%s\n", cfg.protocol); + exit(0); +diff --git a/src/firejail/run_files.c b/src/firejail/run_files.c +index c971a4f53d..8b8bbae121 100644 +--- a/src/firejail/run_files.c ++++ b/src/firejail/run_files.c +@@ -20,8 +20,18 @@ + + #include "firejail.h" + #include "../include/pid.h" ++#include + #define BUFLEN 4096 + ++static void delete_sandbox_run_file(pid_t pid) { ++ char *fname; ++ if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_SANDBOX_DIR, pid) == -1) ++ errExit("asprintf"); ++ int rv = unlink(fname); ++ (void) rv; ++ free(fname); ++} ++ + static void delete_x11_run_file(pid_t pid) { + char *fname; + if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) +@@ -68,6 +78,7 @@ static void delete_network_run_file(pid_t pid) { + + + void delete_run_files(pid_t pid) { ++ delete_sandbox_run_file(pid); + delete_bandwidth_run_file(pid); + delete_network_run_file(pid); + delete_name_run_file(pid); +@@ -152,3 +163,44 @@ void set_profile_run_file(pid_t pid, const char *fname) { + EUID_USER(); + free(runfile); + } ++ ++int set_sandbox_run_file(pid_t pid, pid_t child) { ++ char *runfile; ++ if (asprintf(&runfile, "%s/%d", RUN_FIREJAIL_SANDBOX_DIR, pid) == -1) ++ errExit("asprintf"); ++ ++ EUID_ROOT(); ++ // the file is deleted first ++ // this file should be opened with O_CLOEXEC set ++ int fd = open(runfile, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR); ++ if (fd < 0) { ++ fprintf(stderr, "Error: cannot create %s\n", runfile); ++ exit(1); ++ } ++ free(runfile); ++ EUID_USER(); ++ ++ char buf[64]; ++ snprintf(buf, sizeof(buf), "%d\n", child); ++ size_t len = strlen(buf); ++ size_t done = 0; ++ while (done != len) { ++ ssize_t rv = write(fd, buf + done, len - done); ++ if (rv < 0) ++ errExit("write"); ++ done += rv; ++ } ++ ++ // set exclusive lock on the file ++ // the lock is never inherited, and is released if this process dies ungracefully ++ struct flock sandboxlock = { ++ .l_type = F_WRLCK, ++ .l_whence = SEEK_SET, ++ .l_start = 0, ++ .l_len = 0, ++ }; ++ if (fcntl(fd, F_SETLK, &sandboxlock) < 0) ++ errExit("fcntl"); ++ ++ return fd; ++} +diff --git a/src/firejail/seccomp.c b/src/firejail/seccomp.c +index 9fcf74c02f..e8959f2634 100644 +--- a/src/firejail/seccomp.c ++++ b/src/firejail/seccomp.c +@@ -21,6 +21,7 @@ + #include "firejail.h" + #include "../include/seccomp.h" + #include ++#include + + typedef struct filter_list { + struct filter_list *next; +@@ -425,26 +426,20 @@ int seccomp_filter_mdwx(bool native) { + void seccomp_print_filter(pid_t pid) { + EUID_ASSERT(); + +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid = switch_to_child(pid); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // exit if no permission to join the sandbox +- check_join_permission(pid); ++ // chroot in the sandbox ++ process_rootfs_chroot(sandbox); ++ unpin_process(sandbox); + +- // find the seccomp list file +- EUID_ROOT(); +- char *fname; +- if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_SECCOMP_LIST) == -1) +- errExit("asprintf"); +- +- int fd = open(fname, O_RDONLY|O_CLOEXEC); +- if (fd < 0) +- goto errexit; ++ drop_privs(0); + +- FILE *fp = fdopen(fd, "r"); +- if (!fp) +- goto errexit; +- free(fname); ++ // find the seccomp list file ++ FILE *fp = fopen(RUN_SECCOMP_LIST, "re"); ++ if (!fp) { ++ printf("Cannot access seccomp filter.\n"); ++ exit(1); ++ } + + char buf[MAXBUF]; + while (fgets(buf, MAXBUF, fp)) { +@@ -453,21 +448,21 @@ void seccomp_print_filter(pid_t pid) { + if (ptr) + *ptr = '\0'; + +- if (asprintf(&fname, "/proc/%d/root%s", pid, buf) == -1) +- errExit("asprintf"); +- printf("FILE: %s\n", fname); fflush(0); ++ printf("FILE: %s\n", buf); fflush(0); + +- // read and print the filter - run this as root, the user doesn't have access +- sbox_run(SBOX_ROOT | SBOX_SECCOMP, 2, PATH_FSEC_PRINT, fname); +- fflush(0); ++ // read and print the filter ++ pid_t child = fork(); ++ if (child < 0) ++ errExit("fork"); ++ if (child == 0) { ++ execl(PATH_FSEC_PRINT, PATH_FSEC_PRINT, buf, NULL); ++ errExit("execl"); ++ } ++ waitpid(child, NULL, 0); + + printf("\n"); fflush(0); +- free(fname); + } + fclose(fp); +- exit(0); + +-errexit: +- printf("Cannot access seccomp filter.\n"); +- exit(1); ++ exit(0); + } +diff --git a/src/firejail/shutdown.c b/src/firejail/shutdown.c +index 44fdd58abd..fb1ddef73b 100644 +--- a/src/firejail/shutdown.c ++++ b/src/firejail/shutdown.c +@@ -18,85 +18,43 @@ + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "firejail.h" +-#include +-#include + #include +-#include ++#include + + 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 { +- fprintf(stderr, "Error: cannot find process %d\n", pid); +- exit(1); +- } ++ ProcessHandle sandbox = pin_sandbox_process(pid); + +- // 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); ++ process_send_signal(sandbox, 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--; + +- EUID_ROOT(); +- FILE *fp = fopen(monfile, "re"); +- EUID_USER(); +- if (!fp) { ++ int monfd = process_open_nofail(sandbox, "cmdline"); ++ if (monfd < 0) { + killdone = 1; + break; + } + + char c; +- size_t count = fread(&c, 1, 1, fp); +- fclose(fp); ++ ssize_t count = read(monfd, &c, 1); ++ close(monfd); + 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); +- } ++ if (!killdone) ++ process_send_signal(sandbox, SIGKILL); + +- EUID_ROOT(); +- delete_run_files(pid); ++ unpin_process(sandbox); + } +diff --git a/src/firejail/util.c b/src/firejail/util.c +index eb7f056248..a35ad469e8 100644 +--- a/src/firejail/util.c ++++ b/src/firejail/util.c +@@ -41,7 +41,7 @@ + #endif + + #define MAX_GROUPS 1024 +-#define MAXBUF 4098 ++#define MAXBUF 4096 + #define EMPTY_STRING ("") + + +@@ -802,77 +802,6 @@ void check_unsigned(const char *str, const char *msg) { + } + + +-#define BUFLEN 4096 +-// 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, "re"); +- 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)) { +- // we don't want /usr/bin/xdg-dbus-proxy! +- char *cmdline = pid_proc_cmdline(pid); +- if (cmdline) { +- if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) +- *child = pid; +- free(cmdline); +- } +- } +- break; // stop reading the file +- } +- } +- fclose(fp); +- free(file); +- } +- closedir(dir); +- EUID_USER(); +- return (*child)? 0:1; // 0 = found, 1 = not found +-} +- +- + void extract_command_name(int index, char **argv) { + EUID_ASSERT(); + assert(argv); +@@ -961,14 +890,14 @@ void wait_for_other(int fd) { + //**************************** + // wait for the parent to be initialized + //**************************** +- char childstr[BUFLEN + 1]; ++ char childstr[MAXBUF + 1]; + int newfd = fcntl(fd, F_DUPFD_CLOEXEC, 0); + if (newfd == -1) + errExit("fcntl"); + FILE* stream; + stream = fdopen(newfd, "r"); + *childstr = '\0'; +- if (fgets(childstr, BUFLEN, stream)) { ++ if (fgets(childstr, MAXBUF, stream)) { + // remove \n) + char *ptr = childstr; + while(*ptr !='\0' && *ptr != '\n') +@@ -1020,50 +949,6 @@ void notify_other(int fd) { + fclose(stream); + } + +-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, "re"); +- 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 + 4; +- while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { +- ptr++; +- } +- if (*ptr == '\0') { +- fprintf(stderr, "Error: cannot read /proc file\n"); +- exit(1); +- } +- +- rv = atoi(ptr); +- break; // break regardless! +- } +- } +- +- fclose(fp); +- free(file); +- EUID_USER(); // grsecurity fix +- +- return rv; +-} +- + + gid_t get_group_id(const char *groupname) { + gid_t gid = 0; +@@ -1144,12 +1029,14 @@ void create_empty_dir_as_root(const char *dir, mode_t mode) { + /* coverity[toctou] */ + // don't fail if directory already exists. This can be the case in a race + // condition, when two jails launch at the same time. See #1013 +- if (mkdir(dir, mode) == -1 && errno != EEXIST) ++ mode_t tmp = umask(~mode); // let's avoid an extra chmod race ++ int rv = mkdir(dir, mode); ++ umask(tmp); ++ if (rv < 0 && errno != EEXIST) + errExit("mkdir"); +- if (set_perms(dir, 0, 0, mode)) +- errExit("set_perms"); +- ASSERT_PERMS(dir, 0, 0, mode); + } ++ ++ ASSERT_PERMS(dir, 0, 0, mode); + } + + void create_empty_file_as_root(const char *fname, mode_t mode) { +@@ -1163,12 +1050,15 @@ void create_empty_file_as_root(const char *fname, mode_t mode) { + /* coverity[toctou] */ + // don't fail if file already exists. This can be the case in a race + // condition, when two jails launch at the same time. Compare to #1013 +- FILE *fp = fopen(fname, "we"); +- if (!fp) +- errExit("fopen"); +- SET_PERMS_STREAM(fp, 0, 0, mode); +- fclose(fp); ++ mode_t tmp = umask(~mode); // let's avoid an extra chmod race ++ int fd = open(fname, O_RDONLY|O_CREAT|O_CLOEXEC, mode); ++ umask(tmp); ++ if (fd < 0) ++ errExit("open"); ++ close(fd); + } ++ ++ ASSERT_PERMS(fname, 0, 0, mode); + } + + // return 1 if error +@@ -1463,8 +1353,8 @@ int has_handler(pid_t pid, int signal) { + EUID_USER(); + free(fname); + if (fp) { +- char buf[BUFLEN]; +- while (fgets(buf, BUFLEN, fp)) { ++ char buf[MAXBUF]; ++ while (fgets(buf, MAXBUF, fp)) { + if (strncmp(buf, "SigCgt:", 7) == 0) { + unsigned long long val; + if (sscanf(buf + 7, "%llx", &val) != 1) { +@@ -1484,11 +1374,7 @@ int has_handler(pid_t pid, int signal) { + } + + void enter_network_namespace(pid_t pid) { +- // in case the pid is that of a firejail process, use the pid of the first child process +- pid_t child = switch_to_child(pid); +- +- // exit if no permission to join the sandbox +- check_join_permission(child); ++ ProcessHandle sandbox = pin_sandbox_process(pid); + + // check network namespace + char *name; +@@ -1502,10 +1388,11 @@ void enter_network_namespace(pid_t pid) { + + // join the namespace + EUID_ROOT(); +- if (join_namespace(child, "net")) { ++ if (process_join_namespace(sandbox, "net")) { + fprintf(stderr, "Error: cannot join the network namespace\n"); + exit(1); + } ++ unpin_process(sandbox); + } + + // return 1 if error, 0 if a valid pid was found +diff --git a/src/include/common.h b/src/include/common.h +index c9640435a2..ed6560701a 100644 +--- a/src/include/common.h ++++ b/src/include/common.h +@@ -134,7 +134,8 @@ static inline int mac_not_zero(const unsigned char mac[6]) { + + void timetrace_start(void); + float timetrace_end(void); +-int join_namespace(pid_t pid, char *type); ++int join_namespace_by_fd(int dirfd, char *typestr); ++int join_namespace(pid_t pid, char *typestr); + int name2pid(const char *name, pid_t *pid); + char *pid_proc_comm(const pid_t pid); + char *pid_proc_cmdline(const pid_t pid); +diff --git a/src/include/rundefs.h b/src/include/rundefs.h +index 4ba3e27f4d..2f6b474612 100644 +--- a/src/include/rundefs.h ++++ b/src/include/rundefs.h +@@ -23,6 +23,7 @@ + // filesystem + #define RUN_FIREJAIL_BASEDIR "/run" + #define RUN_FIREJAIL_DIR RUN_FIREJAIL_BASEDIR "/firejail" ++#define RUN_FIREJAIL_SANDBOX_DIR RUN_FIREJAIL_DIR "/sandbox" + #define RUN_FIREJAIL_APPIMAGE_DIR RUN_FIREJAIL_DIR "/appimage" + #define RUN_FIREJAIL_NAME_DIR RUN_FIREJAIL_DIR "/name" // also used in src/lib/pid.c - todo: move it in a common place + #define RUN_FIREJAIL_LIB_DIR RUN_FIREJAIL_DIR "/lib" +diff --git a/src/lib/common.c b/src/lib/common.c +index 8e84fab262..111366782e 100644 +--- a/src/lib/common.c ++++ b/src/lib/common.c +@@ -22,7 +22,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -32,32 +31,94 @@ + #include + #include + #include ++#include + #include "../include/common.h" ++ ++#include ++#ifndef O_PATH ++#define O_PATH 010000000 ++#endif ++ ++#include ++#ifndef NSIO ++#define NSIO 0xb7 ++#endif ++#ifndef NS_GET_USERNS ++#define NS_GET_USERNS _IO(NSIO, 0x1) ++#endif ++ + #define BUFLEN 4096 + +-int join_namespace(pid_t pid, char *type) { ++ ++int join_namespace_by_fd(int dirfd, char *typestr) { ++ int type; ++ if (strcmp(typestr, "net") == 0) ++ type = CLONE_NEWNET; ++ else if (strcmp(typestr, "mnt") == 0) ++ type = CLONE_NEWNS; ++ else if (strcmp(typestr, "ipc") == 0) ++ type = CLONE_NEWIPC; ++ else if (strcmp(typestr, "pid") == 0) ++ type = CLONE_NEWPID; ++ else if (strcmp(typestr, "uts") == 0) ++ type = CLONE_NEWUTS; ++ else if (strcmp(typestr, "user") == 0) ++ type = CLONE_NEWUSER; ++ else ++ assert(0); ++ + char *path; +- if (asprintf(&path, "/proc/%u/ns/%s", pid, type) == -1) ++ if (asprintf(&path, "ns/%s", typestr) == -1) + errExit("asprintf"); + +- int fd = open(path, O_RDONLY); ++ int fd = openat(dirfd, path, O_RDONLY|O_CLOEXEC); ++ free(path); + if (fd < 0) + goto errout; + +- if (syscall(__NR_setns, fd, 0) < 0) { ++ // require that target namespace is owned by ++ // the current user namespace (Linux >= 4.9) ++ struct stat self_userns; ++ if (stat("/proc/self/ns/user", &self_userns) == 0) { ++ int usernsfd = ioctl(fd, NS_GET_USERNS); ++ if (usernsfd != -1) { ++ struct stat dest_userns; ++ if (fstat(usernsfd, &dest_userns) < 0) ++ errExit("fstat"); ++ close(usernsfd); ++ if (dest_userns.st_ino != self_userns.st_ino || ++ dest_userns.st_dev != self_userns.st_dev) { ++ close(fd); ++ goto errout; ++ } ++ } ++ } ++ ++ if (syscall(__NR_setns, fd, type) < 0) { + close(fd); + goto errout; + } + + close(fd); +- free(path); + return 0; + + errout: +- free(path); +- fprintf(stderr, "Error: cannot join namespace %s\n", type); ++ fprintf(stderr, "Error: cannot join namespace %s\n", typestr); + return -1; ++} + ++int join_namespace(pid_t pid, char *typestr) { ++ char path[64]; ++ snprintf(path, sizeof(path), "/proc/%d", pid); ++ int fd = open(path, O_PATH|O_CLOEXEC); ++ if (fd < 0) { ++ fprintf(stderr, "Error: cannot open %s: %s\n", path, strerror(errno)); ++ exit(1); ++ } ++ ++ int rv = join_namespace_by_fd(fd, typestr); ++ close(fd); ++ return rv; + } + + // return 1 if error +From dab835e7a0eb287822016f5ae4e87f46e1d363e7 Mon Sep 17 00:00:00 2001 +From: smitsohu +Date: Wed, 8 Jun 2022 15:20:44 +0200 +Subject: [PATCH] CVE-2022-31214: fixing the fix + +--- + src/firejail/fs_etc.c | 6 +++--- + src/firejail/process.c | 4 ++-- + 2 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/src/firejail/fs_etc.c b/src/firejail/fs_etc.c +index deaee31bba..e87d3b726c 100644 +--- a/src/firejail/fs_etc.c ++++ b/src/firejail/fs_etc.c +@@ -104,7 +104,7 @@ static void build_dirs(char *src, char *dst, size_t src_prefix_len, size_t dst_p + *q = '\0'; + *r = '/'; + r = q; +- create_empty_dir_as_root(dst, s.st_mode); ++ mkdir_attr(dst, s.st_mode, 0, 0); + } + if (!last) { + // If we're not at the final terminating null, restore +@@ -330,9 +330,9 @@ void fs_rebuild_etc(void) { + symlink_done = 1; + } + else if (S_ISDIR(s.st_mode)) +- create_empty_dir_as_root(dest, s.st_mode); ++ create_empty_dir_as_root(dest, S_IRWXU); + else +- create_empty_file_as_root(dest, s.st_mode); ++ create_empty_file_as_root(dest, S_IRUSR | S_IWUSR); + + // bind-mount src on top of dest + if (!symlink_done) { +diff --git a/src/firejail/process.c b/src/firejail/process.c +index 5adb4f8cc2..fa6b1394d6 100644 +--- a/src/firejail/process.c ++++ b/src/firejail/process.c +@@ -96,7 +96,7 @@ int process_stat_nofail(ProcessHandle process, const char *fname, struct stat *s + int process_stat(ProcessHandle process, const char *fname, struct stat *s) { + int rv = process_stat_nofail(process, fname, s); + if (rv) { +- fprintf(stderr, "Error: cannot stat /proc/%d/%s: %s\n", process->pid, fname, strerror(errno)); ++ fprintf(stderr, "Error: cannot stat /proc/%d/%s: %s\n", process_get_pid(process), fname, strerror(errno)); + exit(1); + } + +@@ -117,7 +117,7 @@ int process_open_nofail(ProcessHandle process, const char *fname) { + int process_open(ProcessHandle process, const char *fname) { + int rv = process_open_nofail(process, fname); + if (rv < 0) { +- fprintf(stderr, "Error: cannot open /proc/%d/%s: %s\n", process->pid, fname, strerror(errno)); ++ fprintf(stderr, "Error: cannot open /proc/%d/%s: %s\n", process_get_pid(process), fname, strerror(errno)); + exit(1); + } + +From 1884ea22a90d225950d81c804f1771b42ae55f54 Mon Sep 17 00:00:00 2001 +From: smitsohu +Date: Wed, 8 Jun 2022 15:42:35 +0200 +Subject: [PATCH] CVE-2022-31214: fixing the fix, one more time + +the previous commit "CVE-2022-31214: fixing the fix" +made private-etc=fonts,fonts and similar commands +fail with an error + +fix that regression by tolerating already existing +directories +--- + src/firejail/fs_etc.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/firejail/fs_etc.c b/src/firejail/fs_etc.c +index e87d3b726c..77fa00d6bb 100644 +--- a/src/firejail/fs_etc.c ++++ b/src/firejail/fs_etc.c +@@ -104,7 +104,10 @@ static void build_dirs(char *src, char *dst, size_t src_prefix_len, size_t dst_p + *q = '\0'; + *r = '/'; + r = q; +- mkdir_attr(dst, s.st_mode, 0, 0); ++ if (mkdir(dst, 0700) != 0 && errno != EEXIST) ++ errExit("mkdir"); ++ if (chmod(dst, s.st_mode) != 0) ++ errExit("chmod"); + } + if (!last) { + // If we're not at the final terminating null, restore