aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar smitsohu <smitsohu@gmail.com>2022-06-08 12:12:04 +0200
committerLibravatar smitsohu <smitsohu@gmail.com>2022-06-08 12:12:04 +0200
commit27cde3d7d1e4e16d4190932347c7151dc2a84c50 (patch)
tree0da23ef1269411abd2621847e55392712b7e2cf8 /src
parentRELNOTES: add feature/bugfix (diff)
downloadfirejail-27cde3d7d1e4e16d4190932347c7151dc2a84c50.tar.gz
firejail-27cde3d7d1e4e16d4190932347c7151dc2a84c50.tar.zst
firejail-27cde3d7d1e4e16d4190932347c7151dc2a84c50.zip
fixing CVE-2022-31214
Diffstat (limited to 'src')
-rw-r--r--src/firejail/caps.c36
-rw-r--r--src/firejail/cgroup.c24
-rw-r--r--src/firejail/cpu.c49
-rw-r--r--src/firejail/firejail.h31
-rw-r--r--src/firejail/fs.c1
-rw-r--r--src/firejail/fs_logger.c20
-rw-r--r--src/firejail/join.c496
-rw-r--r--src/firejail/ls.c32
-rw-r--r--src/firejail/main.c35
-rw-r--r--src/firejail/network_main.c45
-rw-r--r--src/firejail/preproc.c6
-rw-r--r--src/firejail/process.c244
-rw-r--r--src/firejail/protocol.c20
-rw-r--r--src/firejail/run_files.c52
-rw-r--r--src/firejail/seccomp.c51
-rw-r--r--src/firejail/shutdown.c62
-rw-r--r--src/firejail/util.c157
-rw-r--r--src/include/common.h3
-rw-r--r--src/include/rundefs.h1
-rw-r--r--src/lib/common.c77
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
384static uint64_t extract_caps(int pid) { 385static 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
410errexit:
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:
416void caps_print_filter(pid_t pid) { 404void 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
49void 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 }
67errout:
68 fwarning("cannot load control group\n");
69 if (fp)
70 fclose(fp);
71}
72
73static int is_cgroup_path(const char *fname) { 49static 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
90void 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
106void set_cpu_affinity(void) { 91void 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
134static void print_cpu(int pid) { 119static 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
166void cpu_print_filter(pid_t pid) { 136void 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
478void usage(void); 478void usage(void);
479 479
480// process.c
481typedef struct processhandle_instance_t * ProcessHandle;
482
483ProcessHandle pin_process(pid_t pid);
484void unpin_process(ProcessHandle process);
485pid_t process_get_pid(ProcessHandle process);
486int process_get_fd(ProcessHandle process);
487int process_stat_nofail(ProcessHandle process, const char *fname, struct stat *s);
488int process_stat(ProcessHandle process, const char *fname, struct stat *s);
489int process_open_nofail(ProcessHandle process, const char *fname);
490int process_open(ProcessHandle process, const char *fname);
491FILE *process_fopen(ProcessHandle process, const char *fname);
492int process_join_namespace(ProcessHandle process, char *type);
493void process_send_signal(ProcessHandle process, int signum);
494ProcessHandle pin_parent_process(ProcessHandle process);
495ProcessHandle pin_child_process(ProcessHandle process, pid_t child);
496void process_rootfs_chroot(ProcessHandle process);
497int process_rootfs_stat(ProcessHandle process, const char *fname, struct stat *s);
498int process_rootfs_open(ProcessHandle process, const char *fname);
499
480// join.c 500// join.c
501ProcessHandle pin_sandbox_process(pid_t pid);
481void join(pid_t pid, int argc, char **argv, int index) __attribute__((noreturn)); 502void join(pid_t pid, int argc, char **argv, int index) __attribute__((noreturn));
482bool is_ready_for_join(const pid_t pid);
483void check_join_permission(pid_t pid);
484pid_t switch_to_child(pid_t pid);
485 503
486// shutdown.c 504// shutdown.c
487void shut(pid_t pid); 505void shut(pid_t pid);
@@ -648,13 +666,11 @@ void set_rlimits(void);
648// cpu.c 666// cpu.c
649void read_cpu_list(const char *str); 667void read_cpu_list(const char *str);
650void set_cpu_affinity(void); 668void set_cpu_affinity(void);
651void load_cpu(const char *fname);
652void save_cpu(void); 669void save_cpu(void);
653void cpu_print_filter(pid_t pid) __attribute__((noreturn)); 670void cpu_print_filter(pid_t pid) __attribute__((noreturn));
654 671
655// cgroup.c 672// cgroup.c
656void save_cgroup(void); 673void save_cgroup(void);
657void load_cgroup(const char *fname);
658void check_cgroup_file(const char *fname); 674void check_cgroup_file(const char *fname);
659void set_cgroup(const char *fname, pid_t pid); 675void 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);
899void set_name_run_file(pid_t pid); 913void set_name_run_file(pid_t pid);
900void set_x11_run_file(pid_t pid, int display); 914void set_x11_run_file(pid_t pid, int display);
901void set_profile_run_file(pid_t pid, const char *fname); 915void set_profile_run_file(pid_t pid, const char *fname);
916int set_sandbox_run_file(pid_t pid, pid_t child);
902 917
903// dbus.c 918// dbus.c
904int dbus_check_name(const char *name); 919int 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) {
120void fs_logger_print_log(pid_t pid) { 121void 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;
42static unsigned display = 0; 42static unsigned display = 0;
43#define BUFLEN 4096 43#define BUFLEN 4096
44 44
45
45static void signal_handler(int sig){ 46static 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
62static 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
95errexit:
96 fprintf(stderr, "Error: cannot read /proc file\n");
97 exit(1);
98}
99#endif // HAVE_APPARMOR
100
101static void extract_x11_display(pid_t pid) { 62static 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
153static void extract_nogroups(pid_t pid) { 114static 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
168static 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
183static void extract_cpu(pid_t pid) { 134static 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
199static void extract_cgroup(pid_t pid) { 141static 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
215static void extract_caps(pid_t pid) { 148static 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
249errexit: 172errexit:
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
254static void extract_user_namespace(pid_t pid) { 177static 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); 192static 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
288static void extract_umask(pid_t pid) { 207static 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
306static int open_shell(void) { 225static 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
328bool is_ready_for_join(const pid_t pid) { 245static 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
357void check_join_permission(pid_t pid) { 267static 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) { 279static 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
378pid_t switch_to_child(pid_t pid) { 306static 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
326static 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
406void 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
348errexit:
349 fprintf(stderr, "Error: no valid sandbox\n");
350 exit(1);
351}
352
353static 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
392errexit:
393 fprintf(stderr, "Error: no valid sandbox\n");
394 exit(1);
395}
396
397static 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
409ProcessHandle 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
425void 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
272void net_dns_print(pid_t pid) { 272void 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
36struct processhandle_instance_t {
37 pid_t pid;
38 int fd; // file descriptor referring to /proc/[PID]
39};
40
41
42ProcessHandle 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
68void unpin_process(ProcessHandle process) {
69 close(process->fd);
70 free(process);
71}
72
73pid_t process_get_pid(ProcessHandle process) {
74 return process->pid;
75}
76
77int process_get_fd(ProcessHandle process) {
78 return process->fd;
79}
80
81/*********************************************
82 * access path in proc filesystem
83 *********************************************/
84
85int 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
96int 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
106int 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
117int 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
127FILE *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
136int 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
144void 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
155static 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
169static 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
183ProcessHandle pin_parent_process(ProcessHandle process) {
184 ProcessHandle parent = pin_process_relative_to(process, process_parent_pid(process));
185 return parent;
186}
187
188ProcessHandle 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
204void 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
224int 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
235int 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
26static 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
25static void delete_x11_run_file(pid_t pid) { 35static 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
70void delete_run_files(pid_t pid) { 80void 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
167int 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
25typedef struct filter_list { 26typedef struct filter_list {
26 struct filter_list *next; 27 struct filter_list *next;
@@ -425,26 +426,20 @@ int seccomp_filter_mdwx(bool native) {
425void seccomp_print_filter(pid_t pid) { 426void 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
470errexit: 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
26void shut(pid_t pid) { 24void 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
807int 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
876void extract_command_name(int index, char **argv) { 805void 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
1023uid_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
1068gid_t get_group_id(const char *groupname) { 953gid_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
1155void create_empty_file_as_root(const char *fname, mode_t mode) { 1042void 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
1486void enter_network_namespace(pid_t pid) { 1376void 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
135void timetrace_start(void); 135void timetrace_start(void);
136float timetrace_end(void); 136float timetrace_end(void);
137int join_namespace(pid_t pid, char *type); 137int join_namespace_by_fd(int dirfd, char *typestr);
138int join_namespace(pid_t pid, char *typestr);
138int name2pid(const char *name, pid_t *pid); 139int name2pid(const char *name, pid_t *pid);
139char *pid_proc_comm(const pid_t pid); 140char *pid_proc_comm(const pid_t pid);
140char *pid_proc_cmdline(const pid_t pid); 141char *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
38int join_namespace(pid_t pid, char *type) { 52
53int 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
56errout: 105errout:
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
110int 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