aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kris7topher@gmail.com>2020-02-23 18:52:45 +0100
committerLibravatar Kristóf Marussy <kris7topher@gmail.com>2020-02-23 19:24:35 +0100
commitead0c0138810a42005098559ca9a29925e8499b7 (patch)
tree8d9cc31c5c04ca9e6c7299121acdec9517ce166b /src
parentMerge pull request #3239 from kris7t/dhcp-client (diff)
downloadfirejail-ead0c0138810a42005098559ca9a29925e8499b7.tar.gz
firejail-ead0c0138810a42005098559ca9a29925e8499b7.tar.zst
firejail-ead0c0138810a42005098559ca9a29925e8499b7.zip
Harden sbox_run by using fexecve instead of execvp
We require the command passed to sbox_run to be an absolute path, and avoid resolving PATH. Note that PATH-based attacks were already difficult to pull of, because sbox_run clears the environment before executing the command. This patch hopefully makes then impossible. As an additional precaution, we check that the executable is owned by either the root user or the root group, and is not world-writable. The use of O_PATH, fstat and fexecve aims to prevent a race condition when the invoked path (e.g., /usr/lib/firejail/fnet) is owned by root or is a symlink to a binary owned by root, but the containing directory (e.g., /usr/lib/firejail) is somehow owned by a user. This is quite unlikely (but may be possible by abusing some other setuid executable is a specific way), and would allow swapping the binary or symlink to a malicious one after we checked ownership. "Locking in" the file descriptor gets rid of the race condition. We have to get rid of the `/proc/[pid]/comm` check in dhcp_read_pidfile, because fexecve sets the comm value to the fd being exec'd (e.g., 3) instead of the name of the file. This is not a problem, unless by the time we pick up the pidfile of dhclient, it has already crashed, and the pid number have wrapper around. Needless to say, this is extremely unlikely (and does not cause a security issue, anyways).
Diffstat (limited to 'src')
-rw-r--r--src/firejail/dhcp.c10
-rw-r--r--src/firejail/sbox.c51
2 files changed, 38 insertions, 23 deletions
diff --git a/src/firejail/dhcp.c b/src/firejail/dhcp.c
index 4a7806a7f..bc5d68a5e 100644
--- a/src/firejail/dhcp.c
+++ b/src/firejail/dhcp.c
@@ -88,9 +88,6 @@ static void dhcp_run_dhclient(char *dhclient_path, const Dhclient *client) {
88static pid_t dhcp_read_pidfile(const Dhclient *client) { 88static pid_t dhcp_read_pidfile(const Dhclient *client) {
89 // We have to run dhclient as a forking daemon (not pass the -d option), 89 // We have to run dhclient as a forking daemon (not pass the -d option),
90 // because we want to be notified of a successful DHCP lease by the parent process exit. 90 // because we want to be notified of a successful DHCP lease by the parent process exit.
91 // However, try to be extra paranoid with race conditions,
92 // because dhclient only writes the daemon pid into the pidfile
93 // after its parent process has exited.
94 int tries = 0; 91 int tries = 0;
95 pid_t found = 0; 92 pid_t found = 0;
96 while (found == 0 && tries < 10) { 93 while (found == 0 && tries < 10) {
@@ -99,11 +96,8 @@ static pid_t dhcp_read_pidfile(const Dhclient *client) {
99 FILE *pidfile = fopen(client->pid_file, "r"); 96 FILE *pidfile = fopen(client->pid_file, "r");
100 if (pidfile) { 97 if (pidfile) {
101 long pid; 98 long pid;
102 if (fscanf(pidfile, "%ld", &pid) == 1) { 99 if (fscanf(pidfile, "%ld", &pid) == 1)
103 char *pidname = pid_proc_comm((pid_t) pid); 100 found = (pid_t) pid;
104 if (pidname && strcmp(pidname, "dhclient") == 0)
105 found = (pid_t) pid;
106 }
107 fclose(pidfile); 101 fclose(pidfile);
108 } 102 }
109 ++tries; 103 ++tries;
diff --git a/src/firejail/sbox.c b/src/firejail/sbox.c
index 77ca07a81..c3b68f3a8 100644
--- a/src/firejail/sbox.c
+++ b/src/firejail/sbox.c
@@ -26,6 +26,11 @@
26 #include <sys/wait.h> 26 #include <sys/wait.h>
27#include "../include/seccomp.h" 27#include "../include/seccomp.h"
28 28
29#include <fcntl.h>
30#ifndef O_PATH
31#define O_PATH 010000000
32#endif
33
29static struct sock_filter filter[] = { 34static struct sock_filter filter[] = {
30 VALIDATE_ARCHITECTURE, 35 VALIDATE_ARCHITECTURE,
31 EXAMINE_SYSCALL, 36 EXAMINE_SYSCALL,
@@ -140,24 +145,19 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) {
140 if (child < 0) 145 if (child < 0)
141 errExit("fork"); 146 errExit("fork");
142 if (child == 0) { 147 if (child == 0) {
148 int env_index = 0;
149 char *new_environment[256] = { NULL };
143 // preserve firejail-specific env vars 150 // preserve firejail-specific env vars
144 char *cl = getenv("FIREJAIL_FILE_COPY_LIMIT"); 151 char *cl = getenv("FIREJAIL_FILE_COPY_LIMIT");
145 if (cl) { 152 if (cl) {
146 // duplicate the value, who knows what's going to happen with it in clearenv! 153 if (asprintf(&new_environment[env_index++], "FIREJAIL_FILE_COPY_LIMIT=%s", cl) == -1)
147 cl = strdup(cl); 154 errExit("asprintf");
148 if (!cl)
149 errExit("strdup");
150 } 155 }
151 clearenv(); 156 clearenv();
152 if (cl) {
153 if (setenv("FIREJAIL_FILE_COPY_LIMIT", cl, 1) == -1)
154 errExit("setenv");
155 free(cl);
156 }
157 if (arg_quiet) // --quiet is passed as an environment variable 157 if (arg_quiet) // --quiet is passed as an environment variable
158 setenv("FIREJAIL_QUIET", "yes", 1); 158 new_environment[env_index++] = "FIREJAIL_QUIET=yes";
159 if (arg_debug) // --debug is passed as an environment variable 159 if (arg_debug) // --debug is passed as an environment variable
160 setenv("FIREJAIL_DEBUG", "yes", 1); 160 new_environment[env_index++] = "FIREJAIL_DEBUG=yes";
161 161
162 if (filtermask & SBOX_STDIN_FROM_FILE) { 162 if (filtermask & SBOX_STDIN_FROM_FILE) {
163 int fd; 163 int fd;
@@ -237,11 +237,32 @@ int sbox_run_v(unsigned filtermask, char * const arg[]) {
237 else if (filtermask & SBOX_USER) 237 else if (filtermask & SBOX_USER)
238 drop_privs(1); 238 drop_privs(1);
239 239
240 if (arg[0]) // get rid of scan-build warning 240 if (arg[0]) { // get rid of scan-build warning
241 execvp(arg[0], arg); 241 int fd = open(arg[0], O_PATH | O_CLOEXEC);
242 else 242 if (fd == -1) {
243 if (errno == ENOENT) {
244 fprintf(stderr, "Error: %s does not exist\n", arg[0]);
245 exit(1);
246 } else {
247 errExit("open");
248 }
249 }
250 struct stat s;
251 if (fstat(fd, &s) == -1)
252 errExit("fstat");
253 if (s.st_uid != 0 && s.st_gid != 0) {
254 fprintf(stderr, "Error: %s is not owned by root, refusing to execute\n", arg[0]);
255 exit(1);
256 }
257 if (s.st_mode & 00002) {
258 fprintf(stderr, "Error: %s is world writable, refusing to execute\n", arg[0]);
259 exit(1);
260 }
261 fexecve(fd, arg, new_environment);
262 } else {
243 assert(0); 263 assert(0);
244 perror("execvp"); 264 }
265 perror("fexecve");
245 _exit(1); 266 _exit(1);
246 } 267 }
247 268