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