diff options
author | 2022-06-08 12:12:04 +0200 | |
---|---|---|
committer | 2022-06-08 12:12:04 +0200 | |
commit | 27cde3d7d1e4e16d4190932347c7151dc2a84c50 (patch) | |
tree | 0da23ef1269411abd2621847e55392712b7e2cf8 /src | |
parent | RELNOTES: add feature/bugfix (diff) | |
download | firejail-27cde3d7d1e4e16d4190932347c7151dc2a84c50.tar.gz firejail-27cde3d7d1e4e16d4190932347c7151dc2a84c50.tar.zst firejail-27cde3d7d1e4e16d4190932347c7151dc2a84c50.zip |
fixing CVE-2022-31214
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/caps.c | 36 | ||||
-rw-r--r-- | src/firejail/cgroup.c | 24 | ||||
-rw-r--r-- | src/firejail/cpu.c | 49 | ||||
-rw-r--r-- | src/firejail/firejail.h | 31 | ||||
-rw-r--r-- | src/firejail/fs.c | 1 | ||||
-rw-r--r-- | src/firejail/fs_logger.c | 20 | ||||
-rw-r--r-- | src/firejail/join.c | 496 | ||||
-rw-r--r-- | src/firejail/ls.c | 32 | ||||
-rw-r--r-- | src/firejail/main.c | 35 | ||||
-rw-r--r-- | src/firejail/network_main.c | 45 | ||||
-rw-r--r-- | src/firejail/preproc.c | 6 | ||||
-rw-r--r-- | src/firejail/process.c | 244 | ||||
-rw-r--r-- | src/firejail/protocol.c | 20 | ||||
-rw-r--r-- | src/firejail/run_files.c | 52 | ||||
-rw-r--r-- | src/firejail/seccomp.c | 51 | ||||
-rw-r--r-- | src/firejail/shutdown.c | 62 | ||||
-rw-r--r-- | src/firejail/util.c | 157 | ||||
-rw-r--r-- | src/include/common.h | 3 | ||||
-rw-r--r-- | src/include/rundefs.h | 1 | ||||
-rw-r--r-- | src/lib/common.c | 77 |
20 files changed, 781 insertions, 661 deletions
diff --git a/src/firejail/caps.c b/src/firejail/caps.c index c5c06c675..d88a99132 100644 --- a/src/firejail/caps.c +++ b/src/firejail/caps.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <errno.h> | 22 | #include <errno.h> |
23 | #include <linux/filter.h> | 23 | #include <linux/filter.h> |
24 | #include <stddef.h> | 24 | #include <stddef.h> |
25 | #include <fcntl.h> | ||
25 | #include <linux/capability.h> | 26 | #include <linux/capability.h> |
26 | #include <linux/audit.h> | 27 | #include <linux/audit.h> |
27 | #include <sys/prctl.h> | 28 | #include <sys/prctl.h> |
@@ -381,34 +382,21 @@ void caps_keep_list(const char *clist) { | |||
381 | } | 382 | } |
382 | 383 | ||
383 | #define MAXBUF 4098 | 384 | #define MAXBUF 4098 |
384 | static uint64_t extract_caps(int pid) { | 385 | static uint64_t extract_caps(ProcessHandle process) { |
385 | EUID_ASSERT(); | 386 | FILE *fp = process_fopen(process, "status"); |
386 | |||
387 | char *file; | ||
388 | if (asprintf(&file, "/proc/%d/status", pid) == -1) | ||
389 | errExit("asprintf"); | ||
390 | |||
391 | EUID_ROOT(); // grsecurity | ||
392 | FILE *fp = fopen(file, "re"); | ||
393 | EUID_USER(); // grsecurity | ||
394 | if (!fp) | ||
395 | goto errexit; | ||
396 | 387 | ||
397 | char buf[MAXBUF]; | 388 | char buf[MAXBUF]; |
398 | while (fgets(buf, MAXBUF, fp)) { | 389 | while (fgets(buf, MAXBUF, fp)) { |
399 | if (strncmp(buf, "CapBnd:\t", 8) == 0) { | 390 | if (strncmp(buf, "CapBnd:\t", 8) == 0) { |
400 | char *ptr = buf + 8; | ||
401 | unsigned long long val; | 391 | unsigned long long val; |
402 | sscanf(ptr, "%llx", &val); | 392 | if (sscanf(buf + 8, "%llx", &val) == 1) { |
403 | free(file); | 393 | fclose(fp); |
404 | fclose(fp); | 394 | return val; |
405 | return val; | 395 | } |
396 | break; | ||
406 | } | 397 | } |
407 | } | 398 | } |
408 | fclose(fp); | ||
409 | 399 | ||
410 | errexit: | ||
411 | free(file); | ||
412 | fprintf(stderr, "Error: cannot read caps configuration\n"); | 400 | fprintf(stderr, "Error: cannot read caps configuration\n"); |
413 | exit(1); | 401 | exit(1); |
414 | } | 402 | } |
@@ -416,13 +404,11 @@ errexit: | |||
416 | void caps_print_filter(pid_t pid) { | 404 | void caps_print_filter(pid_t pid) { |
417 | EUID_ASSERT(); | 405 | EUID_ASSERT(); |
418 | 406 | ||
419 | // in case the pid is that of a firejail process, use the pid of the first child process | 407 | ProcessHandle sandbox = pin_sandbox_process(pid); |
420 | pid = switch_to_child(pid); | ||
421 | 408 | ||
422 | // exit if no permission to join the sandbox | 409 | uint64_t caps = extract_caps(sandbox); |
423 | check_join_permission(pid); | 410 | unpin_process(sandbox); |
424 | 411 | ||
425 | uint64_t caps = extract_caps(pid); | ||
426 | int i; | 412 | int i; |
427 | uint64_t mask; | 413 | uint64_t mask; |
428 | int elems = sizeof(capslist) / sizeof(capslist[0]); | 414 | int elems = sizeof(capslist) / sizeof(capslist[0]); |
diff --git a/src/firejail/cgroup.c b/src/firejail/cgroup.c index f1e16187f..c8cb96f98 100644 --- a/src/firejail/cgroup.c +++ b/src/firejail/cgroup.c | |||
@@ -46,30 +46,6 @@ errout: | |||
46 | exit(1); | 46 | exit(1); |
47 | } | 47 | } |
48 | 48 | ||
49 | void load_cgroup(const char *fname) { | ||
50 | if (!fname) | ||
51 | return; | ||
52 | |||
53 | FILE *fp = fopen(fname, "re"); | ||
54 | if (fp) { | ||
55 | char buf[MAXBUF]; | ||
56 | if (fgets(buf, MAXBUF, fp)) { | ||
57 | cfg.cgroup = strdup(buf); | ||
58 | if (!cfg.cgroup) | ||
59 | errExit("strdup"); | ||
60 | } | ||
61 | else | ||
62 | goto errout; | ||
63 | |||
64 | fclose(fp); | ||
65 | return; | ||
66 | } | ||
67 | errout: | ||
68 | fwarning("cannot load control group\n"); | ||
69 | if (fp) | ||
70 | fclose(fp); | ||
71 | } | ||
72 | |||
73 | static int is_cgroup_path(const char *fname) { | 49 | static int is_cgroup_path(const char *fname) { |
74 | // path starts with /sys/fs/cgroup | 50 | // path starts with /sys/fs/cgroup |
75 | if (strncmp(fname, "/sys/fs/cgroup", 14) != 0) | 51 | if (strncmp(fname, "/sys/fs/cgroup", 14) != 0) |
diff --git a/src/firejail/cpu.c b/src/firejail/cpu.c index 1ec510456..917726359 100644 --- a/src/firejail/cpu.c +++ b/src/firejail/cpu.c | |||
@@ -18,6 +18,7 @@ | |||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
19 | */ | 19 | */ |
20 | #include "firejail.h" | 20 | #include "firejail.h" |
21 | #include <fcntl.h> | ||
21 | #include <sched.h> | 22 | #include <sched.h> |
22 | #include <unistd.h> | 23 | #include <unistd.h> |
23 | #include <sys/stat.h> | 24 | #include <sys/stat.h> |
@@ -87,22 +88,6 @@ void save_cpu(void) { | |||
87 | } | 88 | } |
88 | } | 89 | } |
89 | 90 | ||
90 | void load_cpu(const char *fname) { | ||
91 | if (!fname) | ||
92 | return; | ||
93 | |||
94 | FILE *fp = fopen(fname, "re"); | ||
95 | if (fp) { | ||
96 | unsigned tmp; | ||
97 | int rv = fscanf(fp, "%x", &tmp); | ||
98 | if (rv) | ||
99 | cfg.cpus = (uint32_t) tmp; | ||
100 | fclose(fp); | ||
101 | } | ||
102 | else | ||
103 | fwarning("cannot load cpu affinity mask\n"); | ||
104 | } | ||
105 | |||
106 | void set_cpu_affinity(void) { | 91 | void set_cpu_affinity(void) { |
107 | // set cpu affinity | 92 | // set cpu affinity |
108 | cpu_set_t mask; | 93 | cpu_set_t mask; |
@@ -131,21 +116,8 @@ void set_cpu_affinity(void) { | |||
131 | } | 116 | } |
132 | } | 117 | } |
133 | 118 | ||
134 | static void print_cpu(int pid) { | 119 | static void print_cpu(ProcessHandle process) { |
135 | char *file; | 120 | FILE *fp = process_fopen(process, "status"); |
136 | if (asprintf(&file, "/proc/%d/status", pid) == -1) { | ||
137 | errExit("asprintf"); | ||
138 | exit(1); | ||
139 | } | ||
140 | |||
141 | EUID_ROOT(); // grsecurity | ||
142 | FILE *fp = fopen(file, "re"); | ||
143 | EUID_USER(); // grsecurity | ||
144 | if (!fp) { | ||
145 | printf(" Error: cannot open %s\n", file); | ||
146 | free(file); | ||
147 | return; | ||
148 | } | ||
149 | 121 | ||
150 | #define MAXBUF 4096 | 122 | #define MAXBUF 4096 |
151 | char buf[MAXBUF]; | 123 | char buf[MAXBUF]; |
@@ -153,28 +125,21 @@ static void print_cpu(int pid) { | |||
153 | if (strncmp(buf, "Cpus_allowed_list:", 18) == 0) { | 125 | if (strncmp(buf, "Cpus_allowed_list:", 18) == 0) { |
154 | printf(" %s", buf); | 126 | printf(" %s", buf); |
155 | fflush(0); | 127 | fflush(0); |
156 | free(file); | ||
157 | fclose(fp); | 128 | fclose(fp); |
158 | return; | 129 | return; |
159 | } | 130 | } |
160 | } | 131 | } |
161 | fclose(fp); | 132 | fclose(fp); |
162 | free(file); | ||
163 | } | 133 | } |
164 | 134 | ||
165 | // allow any user to run --cpu.print | 135 | // TODO: allow any user to run --cpu.print |
166 | void cpu_print_filter(pid_t pid) { | 136 | void cpu_print_filter(pid_t pid) { |
167 | EUID_ASSERT(); | 137 | EUID_ASSERT(); |
168 | 138 | ||
169 | // in case the pid is that of a firejail process, use the pid of the first child process | 139 | ProcessHandle sandbox = pin_sandbox_process(pid); |
170 | pid = switch_to_child(pid); | ||
171 | 140 | ||
172 | // now check if the pid belongs to a firejail sandbox | 141 | print_cpu(sandbox); |
173 | if (is_ready_for_join(pid) == false) { | 142 | unpin_process(sandbox); |
174 | fprintf(stderr, "Error: no valid sandbox\n"); | ||
175 | exit(1); | ||
176 | } | ||
177 | 143 | ||
178 | print_cpu(pid); | ||
179 | exit(0); | 144 | exit(0); |
180 | } | 145 | } |
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index 38408b534..7e1d45c01 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h | |||
@@ -477,11 +477,29 @@ void top(void); | |||
477 | // usage.c | 477 | // usage.c |
478 | void usage(void); | 478 | void usage(void); |
479 | 479 | ||
480 | // process.c | ||
481 | typedef struct processhandle_instance_t * ProcessHandle; | ||
482 | |||
483 | ProcessHandle pin_process(pid_t pid); | ||
484 | void unpin_process(ProcessHandle process); | ||
485 | pid_t process_get_pid(ProcessHandle process); | ||
486 | int process_get_fd(ProcessHandle process); | ||
487 | int process_stat_nofail(ProcessHandle process, const char *fname, struct stat *s); | ||
488 | int process_stat(ProcessHandle process, const char *fname, struct stat *s); | ||
489 | int process_open_nofail(ProcessHandle process, const char *fname); | ||
490 | int process_open(ProcessHandle process, const char *fname); | ||
491 | FILE *process_fopen(ProcessHandle process, const char *fname); | ||
492 | int process_join_namespace(ProcessHandle process, char *type); | ||
493 | void process_send_signal(ProcessHandle process, int signum); | ||
494 | ProcessHandle pin_parent_process(ProcessHandle process); | ||
495 | ProcessHandle pin_child_process(ProcessHandle process, pid_t child); | ||
496 | void process_rootfs_chroot(ProcessHandle process); | ||
497 | int process_rootfs_stat(ProcessHandle process, const char *fname, struct stat *s); | ||
498 | int process_rootfs_open(ProcessHandle process, const char *fname); | ||
499 | |||
480 | // join.c | 500 | // join.c |
501 | ProcessHandle pin_sandbox_process(pid_t pid); | ||
481 | void join(pid_t pid, int argc, char **argv, int index) __attribute__((noreturn)); | 502 | void join(pid_t pid, int argc, char **argv, int index) __attribute__((noreturn)); |
482 | bool is_ready_for_join(const pid_t pid); | ||
483 | void check_join_permission(pid_t pid); | ||
484 | pid_t switch_to_child(pid_t pid); | ||
485 | 503 | ||
486 | // shutdown.c | 504 | // shutdown.c |
487 | void shut(pid_t pid); | 505 | void shut(pid_t pid); |
@@ -648,13 +666,11 @@ void set_rlimits(void); | |||
648 | // cpu.c | 666 | // cpu.c |
649 | void read_cpu_list(const char *str); | 667 | void read_cpu_list(const char *str); |
650 | void set_cpu_affinity(void); | 668 | void set_cpu_affinity(void); |
651 | void load_cpu(const char *fname); | ||
652 | void save_cpu(void); | 669 | void save_cpu(void); |
653 | void cpu_print_filter(pid_t pid) __attribute__((noreturn)); | 670 | void cpu_print_filter(pid_t pid) __attribute__((noreturn)); |
654 | 671 | ||
655 | // cgroup.c | 672 | // cgroup.c |
656 | void save_cgroup(void); | 673 | void save_cgroup(void); |
657 | void load_cgroup(const char *fname); | ||
658 | void check_cgroup_file(const char *fname); | 674 | void check_cgroup_file(const char *fname); |
659 | void set_cgroup(const char *fname, pid_t pid); | 675 | void set_cgroup(const char *fname, pid_t pid); |
660 | 676 | ||
@@ -862,9 +878,7 @@ void build_appimage_cmdline(char **command_line, char **window_title, int argc, | |||
862 | #define PATH_FSECCOMP_MAIN (LIBDIR "/firejail/fseccomp") // when called from main thread | 878 | #define PATH_FSECCOMP_MAIN (LIBDIR "/firejail/fseccomp") // when called from main thread |
863 | #define PATH_FSECCOMP ( RUN_FIREJAIL_LIB_DIR "/fseccomp") // when called from sandbox thread | 879 | #define PATH_FSECCOMP ( RUN_FIREJAIL_LIB_DIR "/fseccomp") // when called from sandbox thread |
864 | 880 | ||
865 | // FSEC_PRINT is run outside of sandbox by --seccomp.print | 881 | #define PATH_FSEC_PRINT (RUN_FIREJAIL_LIB_DIR "/fsec-print") |
866 | // it is also run from inside the sandbox by --debug; in this case we do an access(filename, X_OK) test first | ||
867 | #define PATH_FSEC_PRINT (LIBDIR "/firejail/fsec-print") | ||
868 | 882 | ||
869 | #define PATH_FSEC_OPTIMIZE (RUN_FIREJAIL_LIB_DIR "/fsec-optimize") | 883 | #define PATH_FSEC_OPTIMIZE (RUN_FIREJAIL_LIB_DIR "/fsec-optimize") |
870 | 884 | ||
@@ -899,6 +913,7 @@ void delete_bandwidth_run_file(pid_t pid); | |||
899 | void set_name_run_file(pid_t pid); | 913 | void set_name_run_file(pid_t pid); |
900 | void set_x11_run_file(pid_t pid, int display); | 914 | void set_x11_run_file(pid_t pid, int display); |
901 | void set_profile_run_file(pid_t pid, const char *fname); | 915 | void set_profile_run_file(pid_t pid, const char *fname); |
916 | int set_sandbox_run_file(pid_t pid, pid_t child); | ||
902 | 917 | ||
903 | // dbus.c | 918 | // dbus.c |
904 | int dbus_check_name(const char *name); | 919 | int dbus_check_name(const char *name); |
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index c03cd7a12..f30e56e8d 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -834,6 +834,7 @@ void disable_config(void) { | |||
834 | #endif | 834 | #endif |
835 | 835 | ||
836 | // disable run time information | 836 | // disable run time information |
837 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_SANDBOX_DIR); | ||
837 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR); | 838 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NETWORK_DIR); |
838 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR); | 839 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_BANDWIDTH_DIR); |
839 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR); | 840 | disable_file(BLACKLIST_FILE, RUN_FIREJAIL_NAME_DIR); |
diff --git a/src/firejail/fs_logger.c b/src/firejail/fs_logger.c index 06f03dac5..8b6c41278 100644 --- a/src/firejail/fs_logger.c +++ b/src/firejail/fs_logger.c | |||
@@ -22,6 +22,7 @@ | |||
22 | #include <sys/types.h> | 22 | #include <sys/types.h> |
23 | #include <sys/stat.h> | 23 | #include <sys/stat.h> |
24 | #include <unistd.h> | 24 | #include <unistd.h> |
25 | #include <fcntl.h> | ||
25 | 26 | ||
26 | #define MAXBUF 4098 | 27 | #define MAXBUF 4098 |
27 | 28 | ||
@@ -120,24 +121,21 @@ void fs_logger_change_owner(void) { | |||
120 | void fs_logger_print_log(pid_t pid) { | 121 | void fs_logger_print_log(pid_t pid) { |
121 | EUID_ASSERT(); | 122 | EUID_ASSERT(); |
122 | 123 | ||
123 | // in case the pid is that of a firejail process, use the pid of the first child process | 124 | ProcessHandle sandbox = pin_sandbox_process(pid); |
124 | pid = switch_to_child(pid); | ||
125 | 125 | ||
126 | // exit if no permission to join the sandbox | 126 | // chroot in the sandbox |
127 | check_join_permission(pid); | 127 | process_rootfs_chroot(sandbox); |
128 | unpin_process(sandbox); | ||
128 | 129 | ||
129 | // print RUN_FSLOGGER_FILE | 130 | drop_privs(0); |
130 | char *fname; | ||
131 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_FSLOGGER_FILE) == -1) | ||
132 | errExit("asprintf"); | ||
133 | 131 | ||
134 | EUID_ROOT(); | 132 | // print RUN_FSLOGGER_FILE |
135 | FILE *fp = fopen(fname, "re"); | 133 | FILE *fp = fopen(RUN_FSLOGGER_FILE, "re"); |
136 | free(fname); | ||
137 | if (!fp) { | 134 | if (!fp) { |
138 | fprintf(stderr, "Error: Cannot open filesystem log\n"); | 135 | fprintf(stderr, "Error: Cannot open filesystem log\n"); |
139 | exit(1); | 136 | exit(1); |
140 | } | 137 | } |
138 | |||
141 | char buf[MAXBUF]; | 139 | char buf[MAXBUF]; |
142 | while (fgets(buf, MAXBUF, fp)) | 140 | while (fgets(buf, MAXBUF, fp)) |
143 | printf("%s", buf); | 141 | printf("%s", buf); |
diff --git a/src/firejail/join.c b/src/firejail/join.c index 5d73f71be..5acdcb060 100644 --- a/src/firejail/join.c +++ b/src/firejail/join.c | |||
@@ -42,6 +42,7 @@ static uint64_t caps = 0; | |||
42 | static unsigned display = 0; | 42 | static unsigned display = 0; |
43 | #define BUFLEN 4096 | 43 | #define BUFLEN 4096 |
44 | 44 | ||
45 | |||
45 | static void signal_handler(int sig){ | 46 | static void signal_handler(int sig){ |
46 | flush_stdin(); | 47 | flush_stdin(); |
47 | 48 | ||
@@ -58,46 +59,6 @@ static void install_handler(void) { | |||
58 | sigaction(SIGTERM, &sga, NULL); | 59 | sigaction(SIGTERM, &sga, NULL); |
59 | } | 60 | } |
60 | 61 | ||
61 | #ifdef HAVE_APPARMOR | ||
62 | static void extract_apparmor(pid_t pid) { | ||
63 | if (checkcfg(CFG_APPARMOR)) { | ||
64 | EUID_USER(); | ||
65 | if (aa_is_enabled() == 1) { | ||
66 | // get pid of next child process | ||
67 | pid_t child; | ||
68 | if (find_child(pid, &child) == 1) | ||
69 | child = pid; // no child, proceed with current pid | ||
70 | |||
71 | // get name of AppArmor profile | ||
72 | char *fname; | ||
73 | if (asprintf(&fname, "/proc/%d/attr/current", child) == -1) | ||
74 | errExit("asprintf"); | ||
75 | EUID_ROOT(); | ||
76 | int fd = open(fname, O_RDONLY|O_CLOEXEC); | ||
77 | EUID_USER(); | ||
78 | free(fname); | ||
79 | if (fd == -1) | ||
80 | goto errexit; | ||
81 | char buf[BUFLEN]; | ||
82 | ssize_t rv = read(fd, buf, sizeof(buf) - 1); | ||
83 | close(fd); | ||
84 | if (rv < 0) | ||
85 | goto errexit; | ||
86 | buf[rv] = '\0'; | ||
87 | // process confined by Firejail's AppArmor policy? | ||
88 | if (strncmp(buf, "firejail-default", 16) == 0) | ||
89 | arg_apparmor = 1; | ||
90 | } | ||
91 | EUID_ROOT(); | ||
92 | } | ||
93 | return; | ||
94 | |||
95 | errexit: | ||
96 | fprintf(stderr, "Error: cannot read /proc file\n"); | ||
97 | exit(1); | ||
98 | } | ||
99 | #endif // HAVE_APPARMOR | ||
100 | |||
101 | static void extract_x11_display(pid_t pid) { | 62 | static void extract_x11_display(pid_t pid) { |
102 | char *fname; | 63 | char *fname; |
103 | if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) | 64 | 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) { | |||
150 | build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, index, true); | 111 | build_cmdline(&cfg.command_line, &cfg.window_title, argc, argv, index, true); |
151 | } | 112 | } |
152 | 113 | ||
153 | static void extract_nogroups(pid_t pid) { | 114 | static int open_shell(void) { |
154 | char *fname; | 115 | EUID_ASSERT(); |
155 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_GROUPS_CFG) == -1) | 116 | assert(cfg.shell); |
156 | errExit("asprintf"); | ||
157 | 117 | ||
158 | struct stat s; | 118 | if (arg_debug) |
159 | if (stat(fname, &s) == -1) { | 119 | printf("Opening shell %s\n", cfg.shell); |
160 | free(fname); | 120 | // file descriptor will leak if not opened with O_CLOEXEC !! |
161 | return; | 121 | int fd = open(cfg.shell, O_PATH|O_CLOEXEC); |
122 | if (fd == -1) { | ||
123 | fprintf(stderr, "Error: cannot open shell %s\n", cfg.shell); | ||
124 | exit(1); | ||
162 | } | 125 | } |
163 | 126 | ||
164 | arg_nogroups = 1; | 127 | // file descriptor needs to reach final fexecve |
165 | free(fname); | 128 | if (asprintf(&cfg.keep_fd, "%s,%d", cfg.keep_fd ? cfg.keep_fd : "", fd) == -1) |
166 | } | ||
167 | |||
168 | static void extract_nonewprivs(pid_t pid) { | ||
169 | char *fname; | ||
170 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_NONEWPRIVS_CFG) == -1) | ||
171 | errExit("asprintf"); | 129 | errExit("asprintf"); |
172 | 130 | ||
173 | struct stat s; | 131 | return fd; |
174 | if (stat(fname, &s) == -1) { | ||
175 | free(fname); | ||
176 | return; | ||
177 | } | ||
178 | |||
179 | arg_nonewprivs = 1; | ||
180 | free(fname); | ||
181 | } | 132 | } |
182 | 133 | ||
183 | static void extract_cpu(pid_t pid) { | 134 | static void extract_nogroups(ProcessHandle sandbox) { |
184 | char *fname; | ||
185 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_CPU_CFG) == -1) | ||
186 | errExit("asprintf"); | ||
187 | |||
188 | struct stat s; | 135 | struct stat s; |
189 | if (stat(fname, &s) == -1) { | ||
190 | free(fname); | ||
191 | return; | ||
192 | } | ||
193 | 136 | ||
194 | // there is a CPU_CFG file, load it! | 137 | if (process_rootfs_stat(sandbox, RUN_GROUPS_CFG, &s) == 0) |
195 | load_cpu(fname); | 138 | arg_nogroups = 1; |
196 | free(fname); | ||
197 | } | 139 | } |
198 | 140 | ||
199 | static void extract_cgroup(pid_t pid) { | 141 | static void extract_nonewprivs(ProcessHandle sandbox) { |
200 | char *fname; | ||
201 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_CGROUP_CFG) == -1) | ||
202 | errExit("asprintf"); | ||
203 | |||
204 | struct stat s; | 142 | struct stat s; |
205 | if (stat(fname, &s) == -1) { | ||
206 | free(fname); | ||
207 | return; | ||
208 | } | ||
209 | 143 | ||
210 | // there is a cgroup file CGROUP_CFG, load it! | 144 | if (process_rootfs_stat(sandbox, RUN_NONEWPRIVS_CFG, &s) == 0) |
211 | load_cgroup(fname); | 145 | arg_nonewprivs = 1; |
212 | free(fname); | ||
213 | } | 146 | } |
214 | 147 | ||
215 | static void extract_caps(pid_t pid) { | 148 | static void extract_caps(ProcessHandle sandbox) { |
216 | // open stat file | 149 | // open status file |
217 | char *file; | 150 | FILE *fp = process_fopen(sandbox, "status"); |
218 | if (asprintf(&file, "/proc/%u/status", pid) == -1) { | ||
219 | perror("asprintf"); | ||
220 | exit(1); | ||
221 | } | ||
222 | FILE *fp = fopen(file, "re"); | ||
223 | if (!fp) | ||
224 | goto errexit; | ||
225 | 151 | ||
226 | char buf[BUFLEN]; | 152 | char buf[BUFLEN]; |
227 | while (fgets(buf, BUFLEN - 1, fp)) { | 153 | while (fgets(buf, BUFLEN, fp)) { |
228 | if (strncmp(buf, "CapBnd:", 7) == 0) { | 154 | if (strncmp(buf, "CapBnd:", 7) == 0) { |
229 | char *ptr = buf + 7; | ||
230 | unsigned long long val; | 155 | unsigned long long val; |
231 | if (sscanf(ptr, "%llx", &val) != 1) | 156 | if (sscanf(buf + 7, "%llx", &val) != 1) |
232 | goto errexit; | 157 | goto errexit; |
233 | apply_caps = 1; | 158 | apply_caps = 1; |
234 | caps = val; | 159 | caps = val; |
235 | } | 160 | } |
236 | else if (strncmp(buf, "NoNewPrivs:", 11) == 0) { | 161 | else if (strncmp(buf, "NoNewPrivs:", 11) == 0) { |
237 | char *ptr = buf + 11; | ||
238 | int val; | 162 | int val; |
239 | if (sscanf(ptr, "%d", &val) != 1) | 163 | if (sscanf(buf + 11, "%d", &val) != 1) |
240 | goto errexit; | 164 | goto errexit; |
241 | if (val) | 165 | if (val) |
242 | arg_nonewprivs = 1; | 166 | arg_nonewprivs = 1; |
243 | } | 167 | } |
244 | } | 168 | } |
245 | fclose(fp); | 169 | fclose(fp); |
246 | free(file); | ||
247 | return; | 170 | return; |
248 | 171 | ||
249 | errexit: | 172 | errexit: |
250 | fprintf(stderr, "Error: cannot read stat file for process %u\n", pid); | 173 | fprintf(stderr, "Error: cannot read /proc/%d/status\n", process_get_pid(sandbox)); |
251 | exit(1); | 174 | exit(1); |
252 | } | 175 | } |
253 | 176 | ||
254 | static void extract_user_namespace(pid_t pid) { | 177 | static void extract_user_namespace(ProcessHandle sandbox) { |
255 | // test user namespaces available in the kernel | 178 | // test user namespaces available in the kernel |
256 | struct stat s1; | 179 | struct stat self_userns; |
257 | struct stat s2; | 180 | if (stat("/proc/self/ns/user", &self_userns) != 0) |
258 | struct stat s3; | ||
259 | if (stat("/proc/self/ns/user", &s1) == 0 && | ||
260 | stat("/proc/self/uid_map", &s2) == 0 && | ||
261 | stat("/proc/self/gid_map", &s3) == 0); | ||
262 | else | ||
263 | return; | 181 | return; |
264 | 182 | ||
265 | // read uid map | 183 | // check sandbox user namespace |
266 | char *uidmap; | 184 | struct stat dest_userns; |
267 | if (asprintf(&uidmap, "/proc/%u/uid_map", pid) == -1) | 185 | process_stat(sandbox, "ns/user", &dest_userns); |
268 | errExit("asprintf"); | ||
269 | FILE *fp = fopen(uidmap, "re"); | ||
270 | if (!fp) { | ||
271 | free(uidmap); | ||
272 | return; | ||
273 | } | ||
274 | 186 | ||
275 | // check uid map | 187 | if (dest_userns.st_ino != self_userns.st_ino || |
276 | int u1; | 188 | dest_userns.st_dev != self_userns.st_dev) |
277 | int u2; | 189 | arg_noroot = 1; |
278 | if (fscanf(fp, "%d %d", &u1, &u2) == 2) { | 190 | } |
279 | if (arg_debug) | 191 | |
280 | printf("User namespace detected: %s, %d, %d\n", uidmap, u1, u2); | 192 | static void extract_cpu(ProcessHandle sandbox) { |
281 | if (u1 != 0 || u2 != 0) | 193 | int fd = process_rootfs_open(sandbox, RUN_CPU_CFG); |
282 | arg_noroot = 1; | 194 | if (fd < 0) |
283 | } | 195 | return; // not configured |
196 | |||
197 | FILE *fp = fdopen(fd, "r"); | ||
198 | if (!fp) | ||
199 | errExit("fdopen"); | ||
200 | |||
201 | unsigned tmp; | ||
202 | if (fscanf(fp, "%x", &tmp) == 1) | ||
203 | cfg.cpus = (uint32_t) tmp; | ||
284 | fclose(fp); | 204 | fclose(fp); |
285 | free(uidmap); | ||
286 | } | 205 | } |
287 | 206 | ||
288 | static void extract_umask(pid_t pid) { | 207 | static void extract_cgroup(ProcessHandle sandbox) { |
289 | char *fname; | 208 | int fd = process_rootfs_open(sandbox, RUN_CGROUP_CFG); |
290 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_UMASK_FILE) == -1) | 209 | if (fd < 0) |
291 | errExit("asprintf"); | 210 | return; // not configured |
292 | 211 | ||
293 | FILE *fp = fopen(fname, "re"); | 212 | FILE *fp = fdopen(fd, "r"); |
294 | free(fname); | 213 | if (!fp) |
295 | if (!fp) { | 214 | errExit("fdopen"); |
296 | fprintf(stderr, "Error: cannot open umask file\n"); | 215 | |
297 | exit(1); | 216 | char buf[BUFLEN]; |
298 | } | 217 | if (fgets(buf, BUFLEN, fp)) { |
299 | if (fscanf(fp, "%3o", &orig_umask) != 1) { | 218 | cfg.cgroup = strdup(buf); |
300 | fprintf(stderr, "Error: cannot read umask\n"); | 219 | if (!cfg.cgroup) |
301 | exit(1); | 220 | errExit("strdup"); |
302 | } | 221 | } |
303 | fclose(fp); | 222 | fclose(fp); |
304 | } | 223 | } |
305 | 224 | ||
306 | static int open_shell(void) { | 225 | static void extract_umask(ProcessHandle sandbox) { |
307 | EUID_ASSERT(); | 226 | int fd = process_rootfs_open(sandbox, RUN_UMASK_FILE); |
308 | assert(cfg.shell); | 227 | if (fd < 0) { |
309 | 228 | fprintf(stderr, "Error: cannot open umask file\n"); | |
310 | if (arg_debug) | ||
311 | printf("Opening shell %s\n", cfg.shell); | ||
312 | // file descriptor will leak if not opened with O_CLOEXEC !! | ||
313 | int fd = open(cfg.shell, O_PATH|O_CLOEXEC); | ||
314 | if (fd == -1) { | ||
315 | fprintf(stderr, "Error: cannot open shell %s\n", cfg.shell); | ||
316 | exit(1); | 229 | exit(1); |
317 | } | 230 | } |
318 | 231 | ||
319 | // pass file descriptor through to the final fexecve | 232 | FILE *fp = fdopen(fd, "r"); |
320 | if (asprintf(&cfg.keep_fd, "%s,%d", cfg.keep_fd ? cfg.keep_fd : "", fd) == -1) | 233 | if (!fp) |
321 | errExit("asprintf"); | 234 | errExit("fdopen"); |
322 | 235 | ||
323 | return fd; | 236 | if (fscanf(fp, "%3o", &orig_umask) != 1) { |
237 | fprintf(stderr, "Error: cannot read umask\n"); | ||
238 | exit(1); | ||
239 | } | ||
240 | fclose(fp); | ||
324 | } | 241 | } |
325 | 242 | ||
326 | // return false if the sandbox identified by pid is not fully set up yet or if | 243 | // returns false if the sandbox is not fully set up yet, |
327 | // it is no firejail sandbox at all, return true if the sandbox is complete | 244 | // or true if the sandbox is complete |
328 | bool is_ready_for_join(const pid_t pid) { | 245 | static bool has_join_file(ProcessHandle sandbox) { |
329 | EUID_ASSERT(); | ||
330 | // check if a file /run/firejail/mnt/join exists | 246 | // check if a file /run/firejail/mnt/join exists |
331 | char *fname; | 247 | int fd = process_rootfs_open(sandbox, RUN_JOIN_FILE); |
332 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_JOIN_FILE) == -1) | ||
333 | errExit("asprintf"); | ||
334 | EUID_ROOT(); | ||
335 | int fd = open(fname, O_RDONLY|O_CLOEXEC); | ||
336 | EUID_USER(); | ||
337 | free(fname); | ||
338 | if (fd == -1) | 248 | if (fd == -1) |
339 | return false; | 249 | return false; |
340 | struct stat s; | 250 | struct stat s; |
@@ -354,84 +264,183 @@ bool is_ready_for_join(const pid_t pid) { | |||
354 | } | 264 | } |
355 | 265 | ||
356 | #define SNOOZE 10000 // sleep interval in microseconds | 266 | #define SNOOZE 10000 // sleep interval in microseconds |
357 | void check_join_permission(pid_t pid) { | 267 | static void check_joinable(ProcessHandle sandbox) { |
358 | // check if pid belongs to a fully set up firejail sandbox | 268 | // check if pid belongs to a fully set up firejail sandbox |
359 | unsigned long i; | 269 | unsigned long i; |
360 | for (i = SNOOZE; is_ready_for_join(pid) == false; i += SNOOZE) { // give sandbox some time to start up | 270 | for (i = SNOOZE; has_join_file(sandbox) == false; i += SNOOZE) { // give sandbox some time to start up |
361 | if (i > join_timeout) { | 271 | if (i > join_timeout) { |
362 | fprintf(stderr, "Error: no valid sandbox\n"); | 272 | fprintf(stderr, "Error: no valid sandbox\n"); |
363 | exit(1); | 273 | exit(1); |
364 | } | 274 | } |
365 | usleep(SNOOZE); | 275 | usleep(SNOOZE); |
366 | } | 276 | } |
367 | // check privileges for non-root users | 277 | } |
368 | uid_t uid = getuid(); | 278 | |
369 | if (uid != 0) { | 279 | static ProcessHandle find_pidns_parent(pid_t pid) { |
370 | uid_t sandbox_uid = pid_get_uid(pid); | 280 | // identify current pid namespace |
371 | if (uid != sandbox_uid) { | 281 | struct stat self_pidns; |
372 | fprintf(stderr, "Error: permission is denied to join a sandbox created by a different user.\n"); | 282 | if (stat("/proc/self/ns/pid", &self_pidns) < 0) |
373 | exit(1); | 283 | errExit("stat"); |
374 | } | 284 | |
285 | ProcessHandle process = pin_process(pid); | ||
286 | |||
287 | // in case pid is member of a different pid namespace | ||
288 | // find parent who created that namespace | ||
289 | while (1) { | ||
290 | struct stat dest_pidns; | ||
291 | process_stat(process, "ns/pid", &dest_pidns); | ||
292 | |||
293 | if (dest_pidns.st_ino == self_pidns.st_ino && | ||
294 | dest_pidns.st_dev == self_pidns.st_dev) | ||
295 | break; // always true for init process | ||
296 | |||
297 | // next parent process | ||
298 | ProcessHandle next = pin_parent_process(process); | ||
299 | unpin_process(process); | ||
300 | process = next; | ||
375 | } | 301 | } |
302 | |||
303 | return process; | ||
376 | } | 304 | } |
377 | 305 | ||
378 | pid_t switch_to_child(pid_t pid) { | 306 | static void check_firejail_comm(ProcessHandle process) { |
379 | EUID_ASSERT(); | 307 | // open /proc/pid/comm |
380 | EUID_ROOT(); | 308 | // note: comm value is under control of the target process |
381 | pid_t rv = pid; | 309 | FILE *fp = process_fopen(process, "comm"); |
382 | errno = 0; | 310 | |
383 | char *comm = pid_proc_comm(pid); | 311 | char comm[16]; |
384 | if (!comm) { | 312 | if (fscanf(fp, "%15s", comm) != 1) { |
385 | if (errno == ENOENT) | 313 | fprintf(stderr, "Error: cannot read /proc file\n"); |
386 | fprintf(stderr, "Error: cannot find process with pid %d\n", pid); | ||
387 | else | ||
388 | fprintf(stderr, "Error: cannot read /proc file\n"); | ||
389 | exit(1); | 314 | exit(1); |
390 | } | 315 | } |
391 | EUID_USER(); | 316 | fclose(fp); |
392 | 317 | ||
393 | if (strcmp(comm, "firejail") == 0) { | 318 | if (strcmp(comm, "firejail") != 0) { |
394 | if (find_child(pid, &rv) == 1) { | 319 | fprintf(stderr, "Error: no valid sandbox\n"); |
395 | fprintf(stderr, "Error: no valid sandbox\n"); | 320 | exit(1); |
396 | exit(1); | ||
397 | } | ||
398 | fmessage("Switching to pid %u, the first child process inside the sandbox\n", (unsigned) rv); | ||
399 | } | 321 | } |
400 | free(comm); | 322 | |
401 | return rv; | 323 | return; |
402 | } | 324 | } |
403 | 325 | ||
326 | static void check_firejail_credentials(ProcessHandle process) { | ||
327 | // open /proc/pid/status | ||
328 | FILE *fp = process_fopen(process, "status"); | ||
404 | 329 | ||
330 | uid_t ruid = -1; | ||
331 | uid_t suid = -1; | ||
332 | char buf[4096]; | ||
333 | while (fgets(buf, sizeof(buf), fp)) { | ||
334 | if (sscanf(buf, "Uid: %u %*u %u", &ruid, &suid) == 2) | ||
335 | break; | ||
336 | } | ||
337 | fclose(fp); | ||
405 | 338 | ||
406 | void join(pid_t pid, int argc, char **argv, int index) { | 339 | // target process should be privileged and owned by the user |
340 | if (suid != 0) | ||
341 | goto errexit; | ||
342 | uid_t u = getuid(); | ||
343 | if (ruid != u && u != 0) | ||
344 | goto errexit; | ||
345 | |||
346 | return; | ||
347 | |||
348 | errexit: | ||
349 | fprintf(stderr, "Error: no valid sandbox\n"); | ||
350 | exit(1); | ||
351 | } | ||
352 | |||
353 | static pid_t read_sandbox_pidfile(pid_t parent) { | ||
354 | char *pidfile; | ||
355 | if (asprintf(&pidfile, "%s/%d", RUN_FIREJAIL_SANDBOX_DIR, parent) == -1) | ||
356 | errExit("asprintf"); | ||
357 | |||
358 | // open the pidfile | ||
359 | EUID_ROOT(); | ||
360 | int pidfile_fd = open(pidfile, O_RDWR|O_CLOEXEC); | ||
361 | free(pidfile); | ||
362 | EUID_USER(); | ||
363 | if (pidfile_fd < 0) | ||
364 | goto errexit; | ||
365 | |||
366 | // assume pidfile is outdated if parent doesn't hold a lock | ||
367 | struct flock pidfile_lock = { | ||
368 | .l_type = F_WRLCK, | ||
369 | .l_whence = SEEK_SET, | ||
370 | .l_start = 0, | ||
371 | .l_len = 0, | ||
372 | .l_pid = 0, | ||
373 | }; | ||
374 | if (fcntl(pidfile_fd, F_GETLK, &pidfile_lock) < 0) | ||
375 | errExit("fcntl"); | ||
376 | if (pidfile_lock.l_type == F_UNLCK) | ||
377 | goto errexit; | ||
378 | if (pidfile_lock.l_pid != parent) | ||
379 | goto errexit; | ||
380 | |||
381 | // read pidfile | ||
382 | pid_t sandbox; | ||
383 | FILE *fp = fdopen(pidfile_fd, "r"); | ||
384 | if (!fp) | ||
385 | errExit("fdopen"); | ||
386 | if (fscanf(fp, "%d", &sandbox) != 1) | ||
387 | goto errexit; | ||
388 | fclose(fp); | ||
389 | |||
390 | return sandbox; | ||
391 | |||
392 | errexit: | ||
393 | fprintf(stderr, "Error: no valid sandbox\n"); | ||
394 | exit(1); | ||
395 | } | ||
396 | |||
397 | static ProcessHandle switch_to_sandbox(ProcessHandle parent) { | ||
398 | // firejail forks many children, identify the sandbox child | ||
399 | // using a pidfile created by the sandbox parent | ||
400 | pid_t pid = read_sandbox_pidfile(process_get_pid(parent)); | ||
401 | |||
402 | // pin the sandbox child | ||
403 | fmessage("Switching to pid %d, the first child process inside the sandbox\n", pid); | ||
404 | ProcessHandle sandbox = pin_child_process(parent, pid); | ||
405 | |||
406 | return sandbox; | ||
407 | } | ||
408 | |||
409 | ProcessHandle pin_sandbox_process(pid_t pid) { | ||
407 | EUID_ASSERT(); | 410 | EUID_ASSERT(); |
408 | 411 | ||
409 | pid_t parent = pid; | 412 | ProcessHandle parent = find_pidns_parent(pid); |
410 | // in case the pid is that of a firejail process, use the pid of the first child process | 413 | check_firejail_comm(parent); |
411 | pid = switch_to_child(pid); | 414 | check_firejail_credentials(parent); |
415 | |||
416 | ProcessHandle sandbox = switch_to_sandbox(parent); | ||
417 | check_joinable(sandbox); | ||
418 | |||
419 | unpin_process(parent); | ||
420 | return sandbox; | ||
421 | } | ||
412 | 422 | ||
413 | // exit if no permission to join the sandbox | ||
414 | check_join_permission(pid); | ||
415 | 423 | ||
416 | extract_x11_display(parent); | 424 | |
425 | void join(pid_t pid, int argc, char **argv, int index) { | ||
426 | EUID_ASSERT(); | ||
427 | ProcessHandle sandbox = pin_sandbox_process(pid); | ||
428 | |||
429 | extract_x11_display(pid); | ||
417 | 430 | ||
418 | int shfd = -1; | 431 | int shfd = -1; |
419 | if (!arg_shell_none) | 432 | if (!arg_shell_none) |
420 | shfd = open_shell(); | 433 | shfd = open_shell(); |
421 | 434 | ||
422 | EUID_ROOT(); | ||
423 | // in user mode set caps seccomp, cpu, cgroup, etc | 435 | // in user mode set caps seccomp, cpu, cgroup, etc |
424 | if (getuid() != 0) { | 436 | if (getuid() != 0) { |
425 | extract_nonewprivs(pid); // redundant on Linux >= 4.10; duplicated in function extract_caps | 437 | extract_nonewprivs(sandbox); // redundant on Linux >= 4.10; duplicated in function extract_caps |
426 | extract_caps(pid); | 438 | extract_caps(sandbox); |
427 | extract_cpu(pid); | 439 | extract_cpu(sandbox); |
428 | extract_cgroup(pid); | 440 | extract_cgroup(sandbox); |
429 | extract_nogroups(pid); | 441 | extract_nogroups(sandbox); |
430 | extract_user_namespace(pid); | 442 | extract_user_namespace(sandbox); |
431 | extract_umask(pid); | 443 | extract_umask(sandbox); |
432 | #ifdef HAVE_APPARMOR | ||
433 | extract_apparmor(pid); | ||
434 | #endif | ||
435 | } | 444 | } |
436 | 445 | ||
437 | // set cgroup | 446 | // set cgroup |
@@ -439,20 +448,21 @@ void join(pid_t pid, int argc, char **argv, int index) { | |||
439 | set_cgroup(cfg.cgroup, getpid()); | 448 | set_cgroup(cfg.cgroup, getpid()); |
440 | 449 | ||
441 | // join namespaces | 450 | // join namespaces |
451 | EUID_ROOT(); | ||
442 | if (arg_join_network) { | 452 | if (arg_join_network) { |
443 | if (join_namespace(pid, "net")) | 453 | if (process_join_namespace(sandbox, "net")) |
444 | exit(1); | 454 | exit(1); |
445 | } | 455 | } |
446 | else if (arg_join_filesystem) { | 456 | else if (arg_join_filesystem) { |
447 | if (join_namespace(pid, "mnt")) | 457 | if (process_join_namespace(sandbox, "mnt")) |
448 | exit(1); | 458 | exit(1); |
449 | } | 459 | } |
450 | else { | 460 | else { |
451 | if (join_namespace(pid, "ipc") || | 461 | if (process_join_namespace(sandbox, "ipc") || |
452 | join_namespace(pid, "net") || | 462 | process_join_namespace(sandbox, "net") || |
453 | join_namespace(pid, "pid") || | 463 | process_join_namespace(sandbox, "pid") || |
454 | join_namespace(pid, "uts") || | 464 | process_join_namespace(sandbox, "uts") || |
455 | join_namespace(pid, "mnt")) | 465 | process_join_namespace(sandbox, "mnt")) |
456 | exit(1); | 466 | exit(1); |
457 | } | 467 | } |
458 | 468 | ||
@@ -463,42 +473,25 @@ void join(pid_t pid, int argc, char **argv, int index) { | |||
463 | // drop discretionary access control capabilities for root sandboxes | 473 | // drop discretionary access control capabilities for root sandboxes |
464 | caps_drop_dac_override(); | 474 | caps_drop_dac_override(); |
465 | 475 | ||
466 | // chroot into /proc/PID/root directory | ||
467 | char *rootdir; | ||
468 | if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) | ||
469 | errExit("asprintf"); | ||
470 | |||
471 | int rv; | ||
472 | if (!arg_join_network) { | 476 | if (!arg_join_network) { |
473 | rv = chroot(rootdir); // this will fail for processes in sandboxes not started with --chroot option | 477 | // mount namespace doesn't know about --chroot |
474 | if (rv == 0) | 478 | fmessage("Changing root to /proc/%d/root\n", process_get_pid(sandbox)); |
475 | printf("changing root to %s\n", rootdir); | 479 | process_rootfs_chroot(sandbox); |
476 | } | ||
477 | 480 | ||
478 | EUID_USER(); | 481 | // load seccomp filters |
479 | if (chdir("/") < 0) | 482 | if (getuid() != 0) |
480 | errExit("chdir"); | 483 | seccomp_load_file_list(); |
481 | if (cfg.homedir) { | ||
482 | struct stat s; | ||
483 | if (stat(cfg.homedir, &s) == 0) { | ||
484 | /* coverity[toctou] */ | ||
485 | if (chdir(cfg.homedir) < 0) | ||
486 | errExit("chdir"); | ||
487 | } | ||
488 | } | 484 | } |
489 | 485 | ||
490 | // set caps filter | 486 | // set caps filter |
491 | EUID_ROOT(); | ||
492 | if (apply_caps == 1) // not available for uid 0 | 487 | if (apply_caps == 1) // not available for uid 0 |
493 | caps_set(caps); | 488 | caps_set(caps); |
494 | if (getuid() != 0) | ||
495 | seccomp_load_file_list(); | ||
496 | 489 | ||
497 | // mount user namespace or drop privileges | 490 | // user namespace |
498 | if (arg_noroot) { // not available for uid 0 | 491 | if (arg_noroot) { // not available for uid 0 |
499 | if (arg_debug) | 492 | if (arg_debug) |
500 | printf("Joining user namespace\n"); | 493 | printf("Joining user namespace\n"); |
501 | if (join_namespace(1, "user")) | 494 | if (process_join_namespace(sandbox, "user")) |
502 | exit(1); | 495 | exit(1); |
503 | 496 | ||
504 | // user namespace resets capabilities | 497 | // user namespace resets capabilities |
@@ -506,6 +499,8 @@ void join(pid_t pid, int argc, char **argv, int index) { | |||
506 | if (apply_caps == 1) // not available for uid 0 | 499 | if (apply_caps == 1) // not available for uid 0 |
507 | caps_set(caps); | 500 | caps_set(caps); |
508 | } | 501 | } |
502 | EUID_USER(); | ||
503 | unpin_process(sandbox); | ||
509 | 504 | ||
510 | // set nonewprivs | 505 | // set nonewprivs |
511 | if (arg_nonewprivs == 1) { // not available for uid 0 | 506 | if (arg_nonewprivs == 1) { // not available for uid 0 |
@@ -514,7 +509,6 @@ void join(pid_t pid, int argc, char **argv, int index) { | |||
514 | printf("NO_NEW_PRIVS set\n"); | 509 | printf("NO_NEW_PRIVS set\n"); |
515 | } | 510 | } |
516 | 511 | ||
517 | EUID_USER(); | ||
518 | int cwd = 0; | 512 | int cwd = 0; |
519 | if (cfg.cwd) { | 513 | if (cfg.cwd) { |
520 | if (chdir(cfg.cwd) == 0) | 514 | if (chdir(cfg.cwd) == 0) |
@@ -579,6 +573,8 @@ void join(pid_t pid, int argc, char **argv, int index) { | |||
579 | __builtin_unreachable(); | 573 | __builtin_unreachable(); |
580 | } | 574 | } |
581 | EUID_USER(); | 575 | EUID_USER(); |
576 | unpin_process(sandbox); | ||
577 | |||
582 | if (shfd != -1) | 578 | if (shfd != -1) |
583 | close(shfd); | 579 | close(shfd); |
584 | 580 | ||
diff --git a/src/firejail/ls.c b/src/firejail/ls.c index 4156a7b25..5ffd468a4 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) { | |||
286 | EUID_ASSERT(); | 286 | EUID_ASSERT(); |
287 | assert(path1); | 287 | assert(path1); |
288 | 288 | ||
289 | // in case the pid is that of a firejail process, use the pid of the first child process | 289 | ProcessHandle sandbox = pin_sandbox_process(pid); |
290 | pid = switch_to_child(pid); | ||
291 | |||
292 | // exit if no permission to join the sandbox | ||
293 | check_join_permission(pid); | ||
294 | 290 | ||
295 | // expand paths | 291 | // expand paths |
296 | char *fname1 = expand_path(path1); | 292 | char *fname1 = expand_path(path1); |
@@ -337,18 +333,9 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { | |||
337 | op = SANDBOX_FS_CAT; | 333 | op = SANDBOX_FS_CAT; |
338 | } | 334 | } |
339 | 335 | ||
340 | // sandbox root directory | ||
341 | char *rootdir; | ||
342 | if (asprintf(&rootdir, "/proc/%d/root", pid) == -1) | ||
343 | errExit("asprintf"); | ||
344 | |||
345 | if (op == SANDBOX_FS_LS || op == SANDBOX_FS_CAT) { | 336 | if (op == SANDBOX_FS_LS || op == SANDBOX_FS_CAT) { |
346 | EUID_ROOT(); | 337 | // chroot into the sandbox |
347 | // chroot | 338 | process_rootfs_chroot(sandbox); |
348 | if (chroot(rootdir) < 0) | ||
349 | errExit("chroot"); | ||
350 | if (chdir("/") < 0) | ||
351 | errExit("chdir"); | ||
352 | 339 | ||
353 | // drop privileges | 340 | // drop privileges |
354 | drop_privs(0); | 341 | drop_privs(0); |
@@ -410,11 +397,8 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { | |||
410 | if (child < 0) | 397 | if (child < 0) |
411 | errExit("fork"); | 398 | errExit("fork"); |
412 | if (child == 0) { | 399 | if (child == 0) { |
413 | // chroot | 400 | // chroot into the sandbox |
414 | if (chroot(rootdir) < 0) | 401 | process_rootfs_chroot(sandbox); |
415 | errExit("chroot"); | ||
416 | if (chdir("/") < 0) | ||
417 | errExit("chdir"); | ||
418 | 402 | ||
419 | // drop privileges | 403 | // drop privileges |
420 | drop_privs(0); | 404 | drop_privs(0); |
@@ -442,10 +426,6 @@ void sandboxfs(int op, pid_t pid, const char *path1, const char *path2) { | |||
442 | EUID_USER(); | 426 | EUID_USER(); |
443 | } | 427 | } |
444 | 428 | ||
445 | if (fname2) | 429 | unpin_process(sandbox); |
446 | free(fname2); | ||
447 | free(fname1); | ||
448 | free(rootdir); | ||
449 | |||
450 | exit(0); | 430 | exit(0); |
451 | } | 431 | } |
diff --git a/src/firejail/main.c b/src/firejail/main.c index 7344be23a..7081e067f 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c | |||
@@ -1011,6 +1011,7 @@ int main(int argc, char **argv, char **envp) { | |||
1011 | int prog_index = -1; // index in argv where the program command starts | 1011 | int prog_index = -1; // index in argv where the program command starts |
1012 | int lockfd_network = -1; | 1012 | int lockfd_network = -1; |
1013 | int lockfd_directory = -1; | 1013 | int lockfd_directory = -1; |
1014 | int lockfd_sandboxfile = -1; | ||
1014 | int option_cgroup = 0; | 1015 | int option_cgroup = 0; |
1015 | int custom_profile = 0; // custom profile loaded | 1016 | int custom_profile = 0; // custom profile loaded |
1016 | int arg_caps_cmdline = 0; // caps requested on command line (used to break out of --chroot) | 1017 | 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) { | |||
3119 | errExit("clone"); | 3120 | errExit("clone"); |
3120 | EUID_USER(); | 3121 | EUID_USER(); |
3121 | 3122 | ||
3123 | // sandbox pidfile | ||
3124 | lockfd_sandboxfile = set_sandbox_run_file(getpid(), child); | ||
3125 | |||
3122 | if (!arg_command && !arg_quiet) { | 3126 | if (!arg_command && !arg_quiet) { |
3123 | fmessage("Parent pid %u, child pid %u\n", sandbox_pid, child); | 3127 | fmessage("Parent pid %u, child pid %u\n", sandbox_pid, child); |
3124 | // print the path of the new log directory | 3128 | // print the path of the new log directory |
@@ -3301,12 +3305,28 @@ int main(int argc, char **argv, char **envp) { | |||
3301 | 3305 | ||
3302 | // lock netfilter firewall | 3306 | // lock netfilter firewall |
3303 | if (arg_netlock) { | 3307 | if (arg_netlock) { |
3304 | char *cmd; | 3308 | pid_t netlock_child = fork(); |
3305 | if (asprintf(&cmd, "firejail --netlock=%d&", getpid()) == -1) | 3309 | if (netlock_child < 0) |
3306 | errExit("asprintf"); | 3310 | errExit("fork"); |
3307 | int rv = system(cmd); | 3311 | if (netlock_child == 0) { |
3308 | (void) rv; | 3312 | close_all(NULL, 0); |
3309 | free(cmd); | 3313 | // drop privileges |
3314 | if (setresgid(-1, getgid(), getgid()) != 0) | ||
3315 | errExit("setresgid"); | ||
3316 | if (setresuid(-1, getuid(), getuid()) != 0) | ||
3317 | errExit("setresuid"); | ||
3318 | |||
3319 | char arg[64]; | ||
3320 | snprintf(arg, sizeof(arg), "--netlock=%d", getpid()); | ||
3321 | |||
3322 | char *cmd[3]; | ||
3323 | cmd[0] = BINDIR "/firejail"; | ||
3324 | cmd[1] = arg; | ||
3325 | cmd[2] = NULL; | ||
3326 | execvp(cmd[0], cmd); | ||
3327 | perror("Cannot start netlock"); | ||
3328 | _exit(1); | ||
3329 | } | ||
3310 | } | 3330 | } |
3311 | 3331 | ||
3312 | int status = 0; | 3332 | int status = 0; |
@@ -3326,7 +3346,8 @@ int main(int argc, char **argv, char **envp) { | |||
3326 | // end of signal-safe code | 3346 | // end of signal-safe code |
3327 | //***************************** | 3347 | //***************************** |
3328 | 3348 | ||
3329 | 3349 | // release lock | |
3350 | close(lockfd_sandboxfile); | ||
3330 | 3351 | ||
3331 | if (WIFEXITED(status)){ | 3352 | if (WIFEXITED(status)){ |
3332 | myexit(WEXITSTATUS(status)); | 3353 | myexit(WEXITSTATUS(status)); |
diff --git a/src/firejail/network_main.c b/src/firejail/network_main.c index dd66ecc55..46ddf269e 100644 --- a/src/firejail/network_main.c +++ b/src/firejail/network_main.c | |||
@@ -271,44 +271,25 @@ void net_check_cfg(void) { | |||
271 | #define MAXBUF 4096 | 271 | #define MAXBUF 4096 |
272 | void net_dns_print(pid_t pid) { | 272 | void net_dns_print(pid_t pid) { |
273 | EUID_ASSERT(); | 273 | EUID_ASSERT(); |
274 | // drop privileges - will not be able to read /etc/resolv.conf for --noroot option | 274 | ProcessHandle sandbox = pin_sandbox_process(pid); |
275 | 275 | ||
276 | // in case the pid is that of a firejail process, use the pid of the first child process | 276 | // chroot in the sandbox |
277 | pid = switch_to_child(pid); | 277 | process_rootfs_chroot(sandbox); |
278 | unpin_process(sandbox); | ||
278 | 279 | ||
279 | // exit if no permission to join the sandbox | 280 | drop_privs(0); |
280 | check_join_permission(pid); | ||
281 | 281 | ||
282 | EUID_ROOT(); | 282 | // read /etc/resolv.conf |
283 | if (join_namespace(pid, "mnt")) | 283 | FILE *fp = fopen("/etc/resolv.conf", "re"); |
284 | if (!fp) { | ||
285 | fprintf(stderr, "Error: cannot read /etc/resolv.conf\n"); | ||
284 | exit(1); | 286 | exit(1); |
285 | |||
286 | pid_t child = fork(); | ||
287 | if (child < 0) | ||
288 | errExit("fork"); | ||
289 | if (child == 0) { | ||
290 | caps_drop_all(); | ||
291 | if (chdir("/") < 0) | ||
292 | errExit("chdir"); | ||
293 | |||
294 | // access /etc/resolv.conf | ||
295 | FILE *fp = fopen("/etc/resolv.conf", "re"); | ||
296 | if (!fp) { | ||
297 | fprintf(stderr, "Error: cannot access /etc/resolv.conf\n"); | ||
298 | exit(1); | ||
299 | } | ||
300 | |||
301 | char buf[MAXBUF]; | ||
302 | while (fgets(buf, MAXBUF, fp)) | ||
303 | printf("%s", buf); | ||
304 | printf("\n"); | ||
305 | fclose(fp); | ||
306 | exit(0); | ||
307 | } | 287 | } |
308 | 288 | ||
309 | // wait for the child to finish | 289 | char buf[MAXBUF]; |
310 | waitpid(child, NULL, 0); | 290 | while (fgets(buf, MAXBUF, fp)) |
311 | flush_stdin(); | 291 | printf("%s", buf); |
292 | |||
312 | exit(0); | 293 | exit(0); |
313 | } | 294 | } |
314 | 295 | ||
diff --git a/src/firejail/preproc.c b/src/firejail/preproc.c index 0517f3506..c117150b8 100644 --- a/src/firejail/preproc.c +++ b/src/firejail/preproc.c | |||
@@ -38,6 +38,12 @@ void preproc_build_firejail_dir(void) { | |||
38 | create_empty_dir_as_root(RUN_FIREJAIL_DIR, 0755); | 38 | create_empty_dir_as_root(RUN_FIREJAIL_DIR, 0755); |
39 | } | 39 | } |
40 | 40 | ||
41 | // restricted search permission | ||
42 | // only root should be able to lock files in this directory | ||
43 | if (stat(RUN_FIREJAIL_SANDBOX_DIR, &s)) { | ||
44 | create_empty_dir_as_root(RUN_FIREJAIL_SANDBOX_DIR, 0700); | ||
45 | } | ||
46 | |||
41 | if (stat(RUN_FIREJAIL_NETWORK_DIR, &s)) { | 47 | if (stat(RUN_FIREJAIL_NETWORK_DIR, &s)) { |
42 | create_empty_dir_as_root(RUN_FIREJAIL_NETWORK_DIR, 0755); | 48 | create_empty_dir_as_root(RUN_FIREJAIL_NETWORK_DIR, 0755); |
43 | } | 49 | } |
diff --git a/src/firejail/process.c b/src/firejail/process.c new file mode 100644 index 000000000..5adb4f8cc --- /dev/null +++ b/src/firejail/process.c | |||
@@ -0,0 +1,244 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2022 Firejail Authors | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | #include "firejail.h" | ||
21 | #include <errno.h> | ||
22 | #include <signal.h> | ||
23 | |||
24 | #include <fcntl.h> | ||
25 | #ifndef O_PATH | ||
26 | #define O_PATH 010000000 | ||
27 | #endif | ||
28 | |||
29 | #include <sys/syscall.h> | ||
30 | #ifndef __NR_pidfd_send_signal | ||
31 | #define __NR_pidfd_send_signal 424 | ||
32 | #endif | ||
33 | |||
34 | #define BUFLEN 4096 | ||
35 | |||
36 | struct processhandle_instance_t { | ||
37 | pid_t pid; | ||
38 | int fd; // file descriptor referring to /proc/[PID] | ||
39 | }; | ||
40 | |||
41 | |||
42 | ProcessHandle pin_process(pid_t pid) { | ||
43 | EUID_ASSERT(); | ||
44 | |||
45 | ProcessHandle rv = malloc(sizeof(struct processhandle_instance_t)); | ||
46 | if (!rv) | ||
47 | errExit("malloc"); | ||
48 | rv->pid = pid; | ||
49 | |||
50 | char proc[64]; | ||
51 | snprintf(proc, sizeof(proc), "/proc/%d", pid); | ||
52 | |||
53 | EUID_ROOT(); | ||
54 | int fd = open(proc, O_RDONLY|O_CLOEXEC); | ||
55 | EUID_USER(); | ||
56 | if (fd < 0) { | ||
57 | if (errno == ENOENT) | ||
58 | fprintf(stderr, "Error: cannot find process with pid %d\n", pid); | ||
59 | else | ||
60 | fprintf(stderr, "Error: cannot open %s: %s\n", proc, strerror(errno)); | ||
61 | exit(1); | ||
62 | } | ||
63 | rv->fd = fd; | ||
64 | |||
65 | return rv; | ||
66 | } | ||
67 | |||
68 | void unpin_process(ProcessHandle process) { | ||
69 | close(process->fd); | ||
70 | free(process); | ||
71 | } | ||
72 | |||
73 | pid_t process_get_pid(ProcessHandle process) { | ||
74 | return process->pid; | ||
75 | } | ||
76 | |||
77 | int process_get_fd(ProcessHandle process) { | ||
78 | return process->fd; | ||
79 | } | ||
80 | |||
81 | /********************************************* | ||
82 | * access path in proc filesystem | ||
83 | *********************************************/ | ||
84 | |||
85 | int process_stat_nofail(ProcessHandle process, const char *fname, struct stat *s) { | ||
86 | EUID_ASSERT(); | ||
87 | assert(fname[0] != '/'); | ||
88 | |||
89 | EUID_ROOT(); | ||
90 | int rv = fstatat(process_get_fd(process), fname, s, 0); | ||
91 | EUID_USER(); | ||
92 | |||
93 | return rv; | ||
94 | } | ||
95 | |||
96 | int process_stat(ProcessHandle process, const char *fname, struct stat *s) { | ||
97 | int rv = process_stat_nofail(process, fname, s); | ||
98 | if (rv) { | ||
99 | fprintf(stderr, "Error: cannot stat /proc/%d/%s: %s\n", process->pid, fname, strerror(errno)); | ||
100 | exit(1); | ||
101 | } | ||
102 | |||
103 | return rv; | ||
104 | } | ||
105 | |||
106 | int process_open_nofail(ProcessHandle process, const char *fname) { | ||
107 | EUID_ASSERT(); | ||
108 | assert(fname[0] != '/'); | ||
109 | |||
110 | EUID_ROOT(); | ||
111 | int rv = openat(process_get_fd(process), fname, O_RDONLY|O_CLOEXEC); | ||
112 | EUID_USER(); | ||
113 | |||
114 | return rv; | ||
115 | } | ||
116 | |||
117 | int process_open(ProcessHandle process, const char *fname) { | ||
118 | int rv = process_open_nofail(process, fname); | ||
119 | if (rv < 0) { | ||
120 | fprintf(stderr, "Error: cannot open /proc/%d/%s: %s\n", process->pid, fname, strerror(errno)); | ||
121 | exit(1); | ||
122 | } | ||
123 | |||
124 | return rv; | ||
125 | } | ||
126 | |||
127 | FILE *process_fopen(ProcessHandle process, const char *fname) { | ||
128 | int fd = process_open(process, fname); | ||
129 | FILE *rv = fdopen(fd, "r"); | ||
130 | if (!rv) | ||
131 | errExit("fdopen"); | ||
132 | |||
133 | return rv; | ||
134 | } | ||
135 | |||
136 | int process_join_namespace(ProcessHandle process, char *type) { | ||
137 | return join_namespace_by_fd(process_get_fd(process), type); | ||
138 | } | ||
139 | |||
140 | /********************************************* | ||
141 | * sending a signal | ||
142 | *********************************************/ | ||
143 | |||
144 | void process_send_signal(ProcessHandle process, int signum) { | ||
145 | fmessage("Sending signal %d to pid %d\n", signum, process_get_pid(process)); | ||
146 | |||
147 | if (syscall(__NR_pidfd_send_signal, process_get_fd(process), signum, NULL, 0) == -1 && errno == ENOSYS) | ||
148 | kill(process_get_pid(process), signum); | ||
149 | } | ||
150 | |||
151 | /********************************************* | ||
152 | * parent and child process | ||
153 | *********************************************/ | ||
154 | |||
155 | static pid_t process_parent_pid(ProcessHandle process) { | ||
156 | pid_t rv = 0; | ||
157 | |||
158 | FILE *fp = process_fopen(process, "status"); | ||
159 | char buf[BUFLEN]; | ||
160 | while (fgets(buf, BUFLEN, fp)) { | ||
161 | if (sscanf(buf, "PPid: %d", &rv) == 1) | ||
162 | break; | ||
163 | } | ||
164 | fclose(fp); | ||
165 | |||
166 | return rv; | ||
167 | } | ||
168 | |||
169 | static ProcessHandle pin_process_relative_to(ProcessHandle process, pid_t pid) { | ||
170 | ProcessHandle rv = malloc(sizeof(struct processhandle_instance_t)); | ||
171 | if (!rv) | ||
172 | errExit("malloc"); | ||
173 | rv->pid = pid; | ||
174 | |||
175 | char proc[64]; | ||
176 | snprintf(proc, sizeof(proc), "../%d", pid); | ||
177 | |||
178 | rv->fd = process_open(process, proc); | ||
179 | |||
180 | return rv; | ||
181 | } | ||
182 | |||
183 | ProcessHandle pin_parent_process(ProcessHandle process) { | ||
184 | ProcessHandle parent = pin_process_relative_to(process, process_parent_pid(process)); | ||
185 | return parent; | ||
186 | } | ||
187 | |||
188 | ProcessHandle pin_child_process(ProcessHandle process, pid_t child_pid) { | ||
189 | ProcessHandle child = pin_process_relative_to(process, child_pid); | ||
190 | |||
191 | // verify parent/child relationship | ||
192 | if (process_parent_pid(child) != process_get_pid(process)) { | ||
193 | fprintf(stderr, "Error: cannot find child process of pid %d\n", process_get_pid(process)); | ||
194 | exit(1); | ||
195 | } | ||
196 | |||
197 | return child; | ||
198 | } | ||
199 | |||
200 | /********************************************* | ||
201 | * access process rootfs | ||
202 | *********************************************/ | ||
203 | |||
204 | void process_rootfs_chroot(ProcessHandle process) { | ||
205 | if (fchdir(process_get_fd(process)) < 0) | ||
206 | errExit("fchdir"); | ||
207 | |||
208 | int called_as_root = 0; | ||
209 | if (geteuid() == 0) | ||
210 | called_as_root = 1; | ||
211 | if (called_as_root == 0) | ||
212 | EUID_ROOT(); | ||
213 | |||
214 | if (chroot("root") < 0) | ||
215 | errExit("chroot"); | ||
216 | |||
217 | if (called_as_root == 0) | ||
218 | EUID_USER(); | ||
219 | |||
220 | if (chdir("/") < 0) | ||
221 | errExit("chdir"); | ||
222 | } | ||
223 | |||
224 | int process_rootfs_stat(ProcessHandle process, const char *fname, struct stat *s) { | ||
225 | char *proc; | ||
226 | if (asprintf(&proc, "root%s", fname) < 0) | ||
227 | errExit("asprintf"); | ||
228 | |||
229 | int rv = process_stat_nofail(process, proc, s); | ||
230 | |||
231 | free(proc); | ||
232 | return rv; | ||
233 | } | ||
234 | |||
235 | int process_rootfs_open(ProcessHandle process, const char *fname) { | ||
236 | char *proc; | ||
237 | if (asprintf(&proc, "root%s", fname) < 0) | ||
238 | errExit("asprintf"); | ||
239 | |||
240 | int rv = process_open_nofail(process, proc); | ||
241 | |||
242 | free(proc); | ||
243 | return rv; | ||
244 | } | ||
diff --git a/src/firejail/protocol.c b/src/firejail/protocol.c index 37e541f50..37782b756 100644 --- a/src/firejail/protocol.c +++ b/src/firejail/protocol.c | |||
@@ -63,27 +63,23 @@ void protocol_print_filter(pid_t pid) { | |||
63 | 63 | ||
64 | (void) pid; | 64 | (void) pid; |
65 | #ifdef SYS_socket | 65 | #ifdef SYS_socket |
66 | // in case the pid is that of a firejail process, use the pid of the first child process | 66 | ProcessHandle sandbox = pin_sandbox_process(pid); |
67 | pid = switch_to_child(pid); | ||
68 | 67 | ||
69 | // exit if no permission to join the sandbox | 68 | // chroot in the sandbox |
70 | check_join_permission(pid); | 69 | process_rootfs_chroot(sandbox); |
70 | unpin_process(sandbox); | ||
71 | 71 | ||
72 | // find the seccomp filter | 72 | // find the seccomp filter |
73 | EUID_ROOT(); | ||
74 | char *fname; | ||
75 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_PROTOCOL_CFG) == -1) | ||
76 | errExit("asprintf"); | ||
77 | |||
78 | struct stat s; | 73 | struct stat s; |
79 | if (stat(fname, &s) == -1) { | 74 | if (stat(RUN_PROTOCOL_CFG, &s) != 0) { |
80 | printf("Cannot access seccomp filter.\n"); | 75 | printf("Cannot access seccomp filter.\n"); |
81 | exit(1); | 76 | exit(1); |
82 | } | 77 | } |
83 | 78 | ||
84 | // read and print the filter | 79 | // read and print the filter |
85 | protocol_filter_load(fname); | 80 | EUID_ROOT(); |
86 | free(fname); | 81 | protocol_filter_load(RUN_PROTOCOL_CFG); |
82 | |||
87 | if (cfg.protocol) | 83 | if (cfg.protocol) |
88 | printf("%s\n", cfg.protocol); | 84 | printf("%s\n", cfg.protocol); |
89 | exit(0); | 85 | exit(0); |
diff --git a/src/firejail/run_files.c b/src/firejail/run_files.c index c971a4f53..8b8bbae12 100644 --- a/src/firejail/run_files.c +++ b/src/firejail/run_files.c | |||
@@ -20,8 +20,18 @@ | |||
20 | 20 | ||
21 | #include "firejail.h" | 21 | #include "firejail.h" |
22 | #include "../include/pid.h" | 22 | #include "../include/pid.h" |
23 | #include <fcntl.h> | ||
23 | #define BUFLEN 4096 | 24 | #define BUFLEN 4096 |
24 | 25 | ||
26 | static void delete_sandbox_run_file(pid_t pid) { | ||
27 | char *fname; | ||
28 | if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_SANDBOX_DIR, pid) == -1) | ||
29 | errExit("asprintf"); | ||
30 | int rv = unlink(fname); | ||
31 | (void) rv; | ||
32 | free(fname); | ||
33 | } | ||
34 | |||
25 | static void delete_x11_run_file(pid_t pid) { | 35 | static void delete_x11_run_file(pid_t pid) { |
26 | char *fname; | 36 | char *fname; |
27 | if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) | 37 | if (asprintf(&fname, "%s/%d", RUN_FIREJAIL_X11_DIR, pid) == -1) |
@@ -68,6 +78,7 @@ static void delete_network_run_file(pid_t pid) { | |||
68 | 78 | ||
69 | 79 | ||
70 | void delete_run_files(pid_t pid) { | 80 | void delete_run_files(pid_t pid) { |
81 | delete_sandbox_run_file(pid); | ||
71 | delete_bandwidth_run_file(pid); | 82 | delete_bandwidth_run_file(pid); |
72 | delete_network_run_file(pid); | 83 | delete_network_run_file(pid); |
73 | delete_name_run_file(pid); | 84 | delete_name_run_file(pid); |
@@ -152,3 +163,44 @@ void set_profile_run_file(pid_t pid, const char *fname) { | |||
152 | EUID_USER(); | 163 | EUID_USER(); |
153 | free(runfile); | 164 | free(runfile); |
154 | } | 165 | } |
166 | |||
167 | int set_sandbox_run_file(pid_t pid, pid_t child) { | ||
168 | char *runfile; | ||
169 | if (asprintf(&runfile, "%s/%d", RUN_FIREJAIL_SANDBOX_DIR, pid) == -1) | ||
170 | errExit("asprintf"); | ||
171 | |||
172 | EUID_ROOT(); | ||
173 | // the file is deleted first | ||
174 | // this file should be opened with O_CLOEXEC set | ||
175 | int fd = open(runfile, O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR); | ||
176 | if (fd < 0) { | ||
177 | fprintf(stderr, "Error: cannot create %s\n", runfile); | ||
178 | exit(1); | ||
179 | } | ||
180 | free(runfile); | ||
181 | EUID_USER(); | ||
182 | |||
183 | char buf[64]; | ||
184 | snprintf(buf, sizeof(buf), "%d\n", child); | ||
185 | size_t len = strlen(buf); | ||
186 | size_t done = 0; | ||
187 | while (done != len) { | ||
188 | ssize_t rv = write(fd, buf + done, len - done); | ||
189 | if (rv < 0) | ||
190 | errExit("write"); | ||
191 | done += rv; | ||
192 | } | ||
193 | |||
194 | // set exclusive lock on the file | ||
195 | // the lock is never inherited, and is released if this process dies ungracefully | ||
196 | struct flock sandboxlock = { | ||
197 | .l_type = F_WRLCK, | ||
198 | .l_whence = SEEK_SET, | ||
199 | .l_start = 0, | ||
200 | .l_len = 0, | ||
201 | }; | ||
202 | if (fcntl(fd, F_SETLK, &sandboxlock) < 0) | ||
203 | errExit("fcntl"); | ||
204 | |||
205 | return fd; | ||
206 | } | ||
diff --git a/src/firejail/seccomp.c b/src/firejail/seccomp.c index 9fcf74c02..e8959f263 100644 --- a/src/firejail/seccomp.c +++ b/src/firejail/seccomp.c | |||
@@ -21,6 +21,7 @@ | |||
21 | #include "firejail.h" | 21 | #include "firejail.h" |
22 | #include "../include/seccomp.h" | 22 | #include "../include/seccomp.h" |
23 | #include <sys/mman.h> | 23 | #include <sys/mman.h> |
24 | #include <sys/wait.h> | ||
24 | 25 | ||
25 | typedef struct filter_list { | 26 | typedef struct filter_list { |
26 | struct filter_list *next; | 27 | struct filter_list *next; |
@@ -425,26 +426,20 @@ int seccomp_filter_mdwx(bool native) { | |||
425 | void seccomp_print_filter(pid_t pid) { | 426 | void seccomp_print_filter(pid_t pid) { |
426 | EUID_ASSERT(); | 427 | EUID_ASSERT(); |
427 | 428 | ||
428 | // in case the pid is that of a firejail process, use the pid of the first child process | 429 | ProcessHandle sandbox = pin_sandbox_process(pid); |
429 | pid = switch_to_child(pid); | ||
430 | 430 | ||
431 | // exit if no permission to join the sandbox | 431 | // chroot in the sandbox |
432 | check_join_permission(pid); | 432 | process_rootfs_chroot(sandbox); |
433 | unpin_process(sandbox); | ||
433 | 434 | ||
434 | // find the seccomp list file | 435 | drop_privs(0); |
435 | EUID_ROOT(); | ||
436 | char *fname; | ||
437 | if (asprintf(&fname, "/proc/%d/root%s", pid, RUN_SECCOMP_LIST) == -1) | ||
438 | errExit("asprintf"); | ||
439 | |||
440 | int fd = open(fname, O_RDONLY|O_CLOEXEC); | ||
441 | if (fd < 0) | ||
442 | goto errexit; | ||
443 | 436 | ||
444 | FILE *fp = fdopen(fd, "r"); | 437 | // find the seccomp list file |
445 | if (!fp) | 438 | FILE *fp = fopen(RUN_SECCOMP_LIST, "re"); |
446 | goto errexit; | 439 | if (!fp) { |
447 | free(fname); | 440 | printf("Cannot access seccomp filter.\n"); |
441 | exit(1); | ||
442 | } | ||
448 | 443 | ||
449 | char buf[MAXBUF]; | 444 | char buf[MAXBUF]; |
450 | while (fgets(buf, MAXBUF, fp)) { | 445 | while (fgets(buf, MAXBUF, fp)) { |
@@ -453,21 +448,21 @@ void seccomp_print_filter(pid_t pid) { | |||
453 | if (ptr) | 448 | if (ptr) |
454 | *ptr = '\0'; | 449 | *ptr = '\0'; |
455 | 450 | ||
456 | if (asprintf(&fname, "/proc/%d/root%s", pid, buf) == -1) | 451 | printf("FILE: %s\n", buf); fflush(0); |
457 | errExit("asprintf"); | ||
458 | printf("FILE: %s\n", fname); fflush(0); | ||
459 | 452 | ||
460 | // read and print the filter - run this as root, the user doesn't have access | 453 | // read and print the filter |
461 | sbox_run(SBOX_ROOT | SBOX_SECCOMP, 2, PATH_FSEC_PRINT, fname); | 454 | pid_t child = fork(); |
462 | fflush(0); | 455 | if (child < 0) |
456 | errExit("fork"); | ||
457 | if (child == 0) { | ||
458 | execl(PATH_FSEC_PRINT, PATH_FSEC_PRINT, buf, NULL); | ||
459 | errExit("execl"); | ||
460 | } | ||
461 | waitpid(child, NULL, 0); | ||
463 | 462 | ||
464 | printf("\n"); fflush(0); | 463 | printf("\n"); fflush(0); |
465 | free(fname); | ||
466 | } | 464 | } |
467 | fclose(fp); | 465 | fclose(fp); |
468 | exit(0); | ||
469 | 466 | ||
470 | errexit: | 467 | exit(0); |
471 | printf("Cannot access seccomp filter.\n"); | ||
472 | exit(1); | ||
473 | } | 468 | } |
diff --git a/src/firejail/shutdown.c b/src/firejail/shutdown.c index 44fdd58ab..fb1ddef73 100644 --- a/src/firejail/shutdown.c +++ b/src/firejail/shutdown.c | |||
@@ -18,85 +18,43 @@ | |||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | 18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
19 | */ | 19 | */ |
20 | #include "firejail.h" | 20 | #include "firejail.h" |
21 | #include <sys/stat.h> | ||
22 | #include <sys/wait.h> | ||
23 | #include <fcntl.h> | 21 | #include <fcntl.h> |
24 | #include <sys/prctl.h> | 22 | #include <signal.h> |
25 | 23 | ||
26 | void shut(pid_t pid) { | 24 | void shut(pid_t pid) { |
27 | EUID_ASSERT(); | 25 | EUID_ASSERT(); |
28 | 26 | ||
29 | EUID_ROOT(); | 27 | ProcessHandle sandbox = pin_sandbox_process(pid); |
30 | char *comm = pid_proc_comm(pid); | ||
31 | EUID_USER(); | ||
32 | if (comm) { | ||
33 | if (strcmp(comm, "firejail") != 0) { | ||
34 | fprintf(stderr, "Error: this is not a firejail sandbox\n"); | ||
35 | exit(1); | ||
36 | } | ||
37 | free(comm); | ||
38 | } | ||
39 | else { | ||
40 | fprintf(stderr, "Error: cannot find process %d\n", pid); | ||
41 | exit(1); | ||
42 | } | ||
43 | 28 | ||
44 | // check privileges for non-root users | 29 | process_send_signal(sandbox, SIGTERM); |
45 | uid_t uid = getuid(); | ||
46 | if (uid != 0) { | ||
47 | uid_t sandbox_uid = pid_get_uid(pid); | ||
48 | if (uid != sandbox_uid) { | ||
49 | fprintf(stderr, "Error: permission is denied to shutdown a sandbox created by a different user.\n"); | ||
50 | exit(1); | ||
51 | } | ||
52 | } | ||
53 | |||
54 | printf("Sending SIGTERM to %u\n", pid); | ||
55 | kill(pid, SIGTERM); | ||
56 | 30 | ||
57 | // wait for not more than 11 seconds | 31 | // wait for not more than 11 seconds |
58 | int monsec = 11; | 32 | int monsec = 11; |
59 | char *monfile; | ||
60 | if (asprintf(&monfile, "/proc/%d/cmdline", pid) == -1) | ||
61 | errExit("asprintf"); | ||
62 | int killdone = 0; | 33 | int killdone = 0; |
63 | 34 | ||
64 | while (monsec) { | 35 | while (monsec) { |
65 | sleep(1); | 36 | sleep(1); |
66 | monsec--; | 37 | monsec--; |
67 | 38 | ||
68 | EUID_ROOT(); | 39 | int monfd = process_open_nofail(sandbox, "cmdline"); |
69 | FILE *fp = fopen(monfile, "re"); | 40 | if (monfd < 0) { |
70 | EUID_USER(); | ||
71 | if (!fp) { | ||
72 | killdone = 1; | 41 | killdone = 1; |
73 | break; | 42 | break; |
74 | } | 43 | } |
75 | 44 | ||
76 | char c; | 45 | char c; |
77 | size_t count = fread(&c, 1, 1, fp); | 46 | ssize_t count = read(monfd, &c, 1); |
78 | fclose(fp); | 47 | close(monfd); |
79 | if (count == 0) { | 48 | if (count == 0) { |
80 | // all done | 49 | // all done |
81 | killdone = 1; | 50 | killdone = 1; |
82 | break; | 51 | break; |
83 | } | 52 | } |
84 | } | 53 | } |
85 | free(monfile); | ||
86 | |||
87 | 54 | ||
88 | // force SIGKILL | 55 | // force SIGKILL |
89 | if (!killdone) { | 56 | if (!killdone) |
90 | // kill the process and its child | 57 | process_send_signal(sandbox, SIGKILL); |
91 | pid_t child; | ||
92 | if (find_child(pid, &child) == 0) { | ||
93 | printf("Sending SIGKILL to %u\n", child); | ||
94 | kill(child, SIGKILL); | ||
95 | } | ||
96 | printf("Sending SIGKILL to %u\n", pid); | ||
97 | kill(pid, SIGKILL); | ||
98 | } | ||
99 | 58 | ||
100 | EUID_ROOT(); | 59 | unpin_process(sandbox); |
101 | delete_run_files(pid); | ||
102 | } | 60 | } |
diff --git a/src/firejail/util.c b/src/firejail/util.c index eb7f05624..a35ad469e 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c | |||
@@ -41,7 +41,7 @@ | |||
41 | #endif | 41 | #endif |
42 | 42 | ||
43 | #define MAX_GROUPS 1024 | 43 | #define MAX_GROUPS 1024 |
44 | #define MAXBUF 4098 | 44 | #define MAXBUF 4096 |
45 | #define EMPTY_STRING ("") | 45 | #define EMPTY_STRING ("") |
46 | 46 | ||
47 | 47 | ||
@@ -802,77 +802,6 @@ void check_unsigned(const char *str, const char *msg) { | |||
802 | } | 802 | } |
803 | 803 | ||
804 | 804 | ||
805 | #define BUFLEN 4096 | ||
806 | // find the first child for this parent; return 1 if error | ||
807 | int find_child(pid_t parent, pid_t *child) { | ||
808 | EUID_ASSERT(); | ||
809 | *child = 0; // use it to flag a found child | ||
810 | |||
811 | DIR *dir; | ||
812 | EUID_ROOT(); // grsecurity fix | ||
813 | if (!(dir = opendir("/proc"))) { | ||
814 | // sleep 2 seconds and try again | ||
815 | sleep(2); | ||
816 | if (!(dir = opendir("/proc"))) { | ||
817 | fprintf(stderr, "Error: cannot open /proc directory\n"); | ||
818 | exit(1); | ||
819 | } | ||
820 | } | ||
821 | |||
822 | struct dirent *entry; | ||
823 | char *end; | ||
824 | while (*child == 0 && (entry = readdir(dir))) { | ||
825 | pid_t pid = strtol(entry->d_name, &end, 10); | ||
826 | if (end == entry->d_name || *end) | ||
827 | continue; | ||
828 | if (pid == parent) | ||
829 | continue; | ||
830 | |||
831 | // open stat file | ||
832 | char *file; | ||
833 | if (asprintf(&file, "/proc/%u/status", pid) == -1) { | ||
834 | perror("asprintf"); | ||
835 | exit(1); | ||
836 | } | ||
837 | FILE *fp = fopen(file, "re"); | ||
838 | if (!fp) { | ||
839 | free(file); | ||
840 | continue; | ||
841 | } | ||
842 | |||
843 | // look for firejail executable name | ||
844 | char buf[BUFLEN]; | ||
845 | while (fgets(buf, BUFLEN - 1, fp)) { | ||
846 | if (strncmp(buf, "PPid:", 5) == 0) { | ||
847 | char *ptr = buf + 5; | ||
848 | while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { | ||
849 | ptr++; | ||
850 | } | ||
851 | if (*ptr == '\0') { | ||
852 | fprintf(stderr, "Error: cannot read /proc file\n"); | ||
853 | exit(1); | ||
854 | } | ||
855 | if (parent == atoi(ptr)) { | ||
856 | // we don't want /usr/bin/xdg-dbus-proxy! | ||
857 | char *cmdline = pid_proc_cmdline(pid); | ||
858 | if (cmdline) { | ||
859 | if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) | ||
860 | *child = pid; | ||
861 | free(cmdline); | ||
862 | } | ||
863 | } | ||
864 | break; // stop reading the file | ||
865 | } | ||
866 | } | ||
867 | fclose(fp); | ||
868 | free(file); | ||
869 | } | ||
870 | closedir(dir); | ||
871 | EUID_USER(); | ||
872 | return (*child)? 0:1; // 0 = found, 1 = not found | ||
873 | } | ||
874 | |||
875 | |||
876 | void extract_command_name(int index, char **argv) { | 805 | void extract_command_name(int index, char **argv) { |
877 | EUID_ASSERT(); | 806 | EUID_ASSERT(); |
878 | assert(argv); | 807 | assert(argv); |
@@ -961,14 +890,14 @@ void wait_for_other(int fd) { | |||
961 | //**************************** | 890 | //**************************** |
962 | // wait for the parent to be initialized | 891 | // wait for the parent to be initialized |
963 | //**************************** | 892 | //**************************** |
964 | char childstr[BUFLEN + 1]; | 893 | char childstr[MAXBUF + 1]; |
965 | int newfd = fcntl(fd, F_DUPFD_CLOEXEC, 0); | 894 | int newfd = fcntl(fd, F_DUPFD_CLOEXEC, 0); |
966 | if (newfd == -1) | 895 | if (newfd == -1) |
967 | errExit("fcntl"); | 896 | errExit("fcntl"); |
968 | FILE* stream; | 897 | FILE* stream; |
969 | stream = fdopen(newfd, "r"); | 898 | stream = fdopen(newfd, "r"); |
970 | *childstr = '\0'; | 899 | *childstr = '\0'; |
971 | if (fgets(childstr, BUFLEN, stream)) { | 900 | if (fgets(childstr, MAXBUF, stream)) { |
972 | // remove \n) | 901 | // remove \n) |
973 | char *ptr = childstr; | 902 | char *ptr = childstr; |
974 | while(*ptr !='\0' && *ptr != '\n') | 903 | while(*ptr !='\0' && *ptr != '\n') |
@@ -1020,50 +949,6 @@ void notify_other(int fd) { | |||
1020 | fclose(stream); | 949 | fclose(stream); |
1021 | } | 950 | } |
1022 | 951 | ||
1023 | uid_t pid_get_uid(pid_t pid) { | ||
1024 | EUID_ASSERT(); | ||
1025 | uid_t rv = 0; | ||
1026 | |||
1027 | // open status file | ||
1028 | char *file; | ||
1029 | if (asprintf(&file, "/proc/%u/status", pid) == -1) { | ||
1030 | perror("asprintf"); | ||
1031 | exit(1); | ||
1032 | } | ||
1033 | EUID_ROOT(); // grsecurity fix | ||
1034 | FILE *fp = fopen(file, "re"); | ||
1035 | if (!fp) { | ||
1036 | free(file); | ||
1037 | fprintf(stderr, "Error: cannot open /proc file\n"); | ||
1038 | exit(1); | ||
1039 | } | ||
1040 | |||
1041 | // extract uid | ||
1042 | static const int PIDS_BUFLEN = 1024; | ||
1043 | char buf[PIDS_BUFLEN]; | ||
1044 | while (fgets(buf, PIDS_BUFLEN - 1, fp)) { | ||
1045 | if (strncmp(buf, "Uid:", 4) == 0) { | ||
1046 | char *ptr = buf + 4; | ||
1047 | while (*ptr != '\0' && (*ptr == ' ' || *ptr == '\t')) { | ||
1048 | ptr++; | ||
1049 | } | ||
1050 | if (*ptr == '\0') { | ||
1051 | fprintf(stderr, "Error: cannot read /proc file\n"); | ||
1052 | exit(1); | ||
1053 | } | ||
1054 | |||
1055 | rv = atoi(ptr); | ||
1056 | break; // break regardless! | ||
1057 | } | ||
1058 | } | ||
1059 | |||
1060 | fclose(fp); | ||
1061 | free(file); | ||
1062 | EUID_USER(); // grsecurity fix | ||
1063 | |||
1064 | return rv; | ||
1065 | } | ||
1066 | |||
1067 | 952 | ||
1068 | gid_t get_group_id(const char *groupname) { | 953 | gid_t get_group_id(const char *groupname) { |
1069 | gid_t gid = 0; | 954 | gid_t gid = 0; |
@@ -1144,12 +1029,14 @@ void create_empty_dir_as_root(const char *dir, mode_t mode) { | |||
1144 | /* coverity[toctou] */ | 1029 | /* coverity[toctou] */ |
1145 | // don't fail if directory already exists. This can be the case in a race | 1030 | // don't fail if directory already exists. This can be the case in a race |
1146 | // condition, when two jails launch at the same time. See #1013 | 1031 | // condition, when two jails launch at the same time. See #1013 |
1147 | if (mkdir(dir, mode) == -1 && errno != EEXIST) | 1032 | mode_t tmp = umask(~mode); // let's avoid an extra chmod race |
1033 | int rv = mkdir(dir, mode); | ||
1034 | umask(tmp); | ||
1035 | if (rv < 0 && errno != EEXIST) | ||
1148 | errExit("mkdir"); | 1036 | errExit("mkdir"); |
1149 | if (set_perms(dir, 0, 0, mode)) | ||
1150 | errExit("set_perms"); | ||
1151 | ASSERT_PERMS(dir, 0, 0, mode); | ||
1152 | } | 1037 | } |
1038 | |||
1039 | ASSERT_PERMS(dir, 0, 0, mode); | ||
1153 | } | 1040 | } |
1154 | 1041 | ||
1155 | void create_empty_file_as_root(const char *fname, mode_t mode) { | 1042 | 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) { | |||
1163 | /* coverity[toctou] */ | 1050 | /* coverity[toctou] */ |
1164 | // don't fail if file already exists. This can be the case in a race | 1051 | // don't fail if file already exists. This can be the case in a race |
1165 | // condition, when two jails launch at the same time. Compare to #1013 | 1052 | // condition, when two jails launch at the same time. Compare to #1013 |
1166 | FILE *fp = fopen(fname, "we"); | 1053 | mode_t tmp = umask(~mode); // let's avoid an extra chmod race |
1167 | if (!fp) | 1054 | int fd = open(fname, O_RDONLY|O_CREAT|O_CLOEXEC, mode); |
1168 | errExit("fopen"); | 1055 | umask(tmp); |
1169 | SET_PERMS_STREAM(fp, 0, 0, mode); | 1056 | if (fd < 0) |
1170 | fclose(fp); | 1057 | errExit("open"); |
1058 | close(fd); | ||
1171 | } | 1059 | } |
1060 | |||
1061 | ASSERT_PERMS(fname, 0, 0, mode); | ||
1172 | } | 1062 | } |
1173 | 1063 | ||
1174 | // return 1 if error | 1064 | // return 1 if error |
@@ -1463,8 +1353,8 @@ int has_handler(pid_t pid, int signal) { | |||
1463 | EUID_USER(); | 1353 | EUID_USER(); |
1464 | free(fname); | 1354 | free(fname); |
1465 | if (fp) { | 1355 | if (fp) { |
1466 | char buf[BUFLEN]; | 1356 | char buf[MAXBUF]; |
1467 | while (fgets(buf, BUFLEN, fp)) { | 1357 | while (fgets(buf, MAXBUF, fp)) { |
1468 | if (strncmp(buf, "SigCgt:", 7) == 0) { | 1358 | if (strncmp(buf, "SigCgt:", 7) == 0) { |
1469 | unsigned long long val; | 1359 | unsigned long long val; |
1470 | if (sscanf(buf + 7, "%llx", &val) != 1) { | 1360 | if (sscanf(buf + 7, "%llx", &val) != 1) { |
@@ -1484,11 +1374,7 @@ int has_handler(pid_t pid, int signal) { | |||
1484 | } | 1374 | } |
1485 | 1375 | ||
1486 | void enter_network_namespace(pid_t pid) { | 1376 | void enter_network_namespace(pid_t pid) { |
1487 | // in case the pid is that of a firejail process, use the pid of the first child process | 1377 | ProcessHandle sandbox = pin_sandbox_process(pid); |
1488 | pid_t child = switch_to_child(pid); | ||
1489 | |||
1490 | // exit if no permission to join the sandbox | ||
1491 | check_join_permission(child); | ||
1492 | 1378 | ||
1493 | // check network namespace | 1379 | // check network namespace |
1494 | char *name; | 1380 | char *name; |
@@ -1502,10 +1388,11 @@ void enter_network_namespace(pid_t pid) { | |||
1502 | 1388 | ||
1503 | // join the namespace | 1389 | // join the namespace |
1504 | EUID_ROOT(); | 1390 | EUID_ROOT(); |
1505 | if (join_namespace(child, "net")) { | 1391 | if (process_join_namespace(sandbox, "net")) { |
1506 | fprintf(stderr, "Error: cannot join the network namespace\n"); | 1392 | fprintf(stderr, "Error: cannot join the network namespace\n"); |
1507 | exit(1); | 1393 | exit(1); |
1508 | } | 1394 | } |
1395 | unpin_process(sandbox); | ||
1509 | } | 1396 | } |
1510 | 1397 | ||
1511 | // return 1 if error, 0 if a valid pid was found | 1398 | // return 1 if error, 0 if a valid pid was found |
diff --git a/src/include/common.h b/src/include/common.h index c9640435a..ed6560701 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]) { | |||
134 | 134 | ||
135 | void timetrace_start(void); | 135 | void timetrace_start(void); |
136 | float timetrace_end(void); | 136 | float timetrace_end(void); |
137 | int join_namespace(pid_t pid, char *type); | 137 | int join_namespace_by_fd(int dirfd, char *typestr); |
138 | int join_namespace(pid_t pid, char *typestr); | ||
138 | int name2pid(const char *name, pid_t *pid); | 139 | int name2pid(const char *name, pid_t *pid); |
139 | char *pid_proc_comm(const pid_t pid); | 140 | char *pid_proc_comm(const pid_t pid); |
140 | char *pid_proc_cmdline(const pid_t pid); | 141 | char *pid_proc_cmdline(const pid_t pid); |
diff --git a/src/include/rundefs.h b/src/include/rundefs.h index 4ba3e27f4..2f6b47461 100644 --- a/src/include/rundefs.h +++ b/src/include/rundefs.h | |||
@@ -23,6 +23,7 @@ | |||
23 | // filesystem | 23 | // filesystem |
24 | #define RUN_FIREJAIL_BASEDIR "/run" | 24 | #define RUN_FIREJAIL_BASEDIR "/run" |
25 | #define RUN_FIREJAIL_DIR RUN_FIREJAIL_BASEDIR "/firejail" | 25 | #define RUN_FIREJAIL_DIR RUN_FIREJAIL_BASEDIR "/firejail" |
26 | #define RUN_FIREJAIL_SANDBOX_DIR RUN_FIREJAIL_DIR "/sandbox" | ||
26 | #define RUN_FIREJAIL_APPIMAGE_DIR RUN_FIREJAIL_DIR "/appimage" | 27 | #define RUN_FIREJAIL_APPIMAGE_DIR RUN_FIREJAIL_DIR "/appimage" |
27 | #define RUN_FIREJAIL_NAME_DIR RUN_FIREJAIL_DIR "/name" // also used in src/lib/pid.c - todo: move it in a common place | 28 | #define RUN_FIREJAIL_NAME_DIR RUN_FIREJAIL_DIR "/name" // also used in src/lib/pid.c - todo: move it in a common place |
28 | #define RUN_FIREJAIL_LIB_DIR RUN_FIREJAIL_DIR "/lib" | 29 | #define RUN_FIREJAIL_LIB_DIR RUN_FIREJAIL_DIR "/lib" |
diff --git a/src/lib/common.c b/src/lib/common.c index 8e84fab26..111366782 100644 --- a/src/lib/common.c +++ b/src/lib/common.c | |||
@@ -22,7 +22,6 @@ | |||
22 | #include <sys/types.h> | 22 | #include <sys/types.h> |
23 | #include <sys/stat.h> | 23 | #include <sys/stat.h> |
24 | #include <sys/wait.h> | 24 | #include <sys/wait.h> |
25 | #include <fcntl.h> | ||
26 | #include <sys/syscall.h> | 25 | #include <sys/syscall.h> |
27 | #include <errno.h> | 26 | #include <errno.h> |
28 | #include <unistd.h> | 27 | #include <unistd.h> |
@@ -32,32 +31,94 @@ | |||
32 | #include <string.h> | 31 | #include <string.h> |
33 | #include <time.h> | 32 | #include <time.h> |
34 | #include <limits.h> | 33 | #include <limits.h> |
34 | #include <sched.h> | ||
35 | #include "../include/common.h" | 35 | #include "../include/common.h" |
36 | |||
37 | #include <fcntl.h> | ||
38 | #ifndef O_PATH | ||
39 | #define O_PATH 010000000 | ||
40 | #endif | ||
41 | |||
42 | #include <sys/ioctl.h> | ||
43 | #ifndef NSIO | ||
44 | #define NSIO 0xb7 | ||
45 | #endif | ||
46 | #ifndef NS_GET_USERNS | ||
47 | #define NS_GET_USERNS _IO(NSIO, 0x1) | ||
48 | #endif | ||
49 | |||
36 | #define BUFLEN 4096 | 50 | #define BUFLEN 4096 |
37 | 51 | ||
38 | int join_namespace(pid_t pid, char *type) { | 52 | |
53 | int join_namespace_by_fd(int dirfd, char *typestr) { | ||
54 | int type; | ||
55 | if (strcmp(typestr, "net") == 0) | ||
56 | type = CLONE_NEWNET; | ||
57 | else if (strcmp(typestr, "mnt") == 0) | ||
58 | type = CLONE_NEWNS; | ||
59 | else if (strcmp(typestr, "ipc") == 0) | ||
60 | type = CLONE_NEWIPC; | ||
61 | else if (strcmp(typestr, "pid") == 0) | ||
62 | type = CLONE_NEWPID; | ||
63 | else if (strcmp(typestr, "uts") == 0) | ||
64 | type = CLONE_NEWUTS; | ||
65 | else if (strcmp(typestr, "user") == 0) | ||
66 | type = CLONE_NEWUSER; | ||
67 | else | ||
68 | assert(0); | ||
69 | |||
39 | char *path; | 70 | char *path; |
40 | if (asprintf(&path, "/proc/%u/ns/%s", pid, type) == -1) | 71 | if (asprintf(&path, "ns/%s", typestr) == -1) |
41 | errExit("asprintf"); | 72 | errExit("asprintf"); |
42 | 73 | ||
43 | int fd = open(path, O_RDONLY); | 74 | int fd = openat(dirfd, path, O_RDONLY|O_CLOEXEC); |
75 | free(path); | ||
44 | if (fd < 0) | 76 | if (fd < 0) |
45 | goto errout; | 77 | goto errout; |
46 | 78 | ||
47 | if (syscall(__NR_setns, fd, 0) < 0) { | 79 | // require that target namespace is owned by |
80 | // the current user namespace (Linux >= 4.9) | ||
81 | struct stat self_userns; | ||
82 | if (stat("/proc/self/ns/user", &self_userns) == 0) { | ||
83 | int usernsfd = ioctl(fd, NS_GET_USERNS); | ||
84 | if (usernsfd != -1) { | ||
85 | struct stat dest_userns; | ||
86 | if (fstat(usernsfd, &dest_userns) < 0) | ||
87 | errExit("fstat"); | ||
88 | close(usernsfd); | ||
89 | if (dest_userns.st_ino != self_userns.st_ino || | ||
90 | dest_userns.st_dev != self_userns.st_dev) { | ||
91 | close(fd); | ||
92 | goto errout; | ||
93 | } | ||
94 | } | ||
95 | } | ||
96 | |||
97 | if (syscall(__NR_setns, fd, type) < 0) { | ||
48 | close(fd); | 98 | close(fd); |
49 | goto errout; | 99 | goto errout; |
50 | } | 100 | } |
51 | 101 | ||
52 | close(fd); | 102 | close(fd); |
53 | free(path); | ||
54 | return 0; | 103 | return 0; |
55 | 104 | ||
56 | errout: | 105 | errout: |
57 | free(path); | 106 | fprintf(stderr, "Error: cannot join namespace %s\n", typestr); |
58 | fprintf(stderr, "Error: cannot join namespace %s\n", type); | ||
59 | return -1; | 107 | return -1; |
108 | } | ||
60 | 109 | ||
110 | int join_namespace(pid_t pid, char *typestr) { | ||
111 | char path[64]; | ||
112 | snprintf(path, sizeof(path), "/proc/%d", pid); | ||
113 | int fd = open(path, O_PATH|O_CLOEXEC); | ||
114 | if (fd < 0) { | ||
115 | fprintf(stderr, "Error: cannot open %s: %s\n", path, strerror(errno)); | ||
116 | exit(1); | ||
117 | } | ||
118 | |||
119 | int rv = join_namespace_by_fd(fd, typestr); | ||
120 | close(fd); | ||
121 | return rv; | ||
61 | } | 122 | } |
62 | 123 | ||
63 | // return 1 if error | 124 | // return 1 if error |