aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar netblue30 <netblue30@yahoo.com>2019-07-09 11:45:19 -0500
committerLibravatar GitHub <noreply@github.com>2019-07-09 11:45:19 -0500
commita87a653ac93dd807020dd0444d34880a022b075c (patch)
tree11e501bcda1337ca672d49e8f7a755b52ceee957
parentFix #2726 (diff)
parentmain.c: define O_PATH (CentOS 6 fix) (diff)
downloadfirejail-a87a653ac93dd807020dd0444d34880a022b075c.tar.gz
firejail-a87a653ac93dd807020dd0444d34880a022b075c.tar.zst
firejail-a87a653ac93dd807020dd0444d34880a022b075c.zip
Merge pull request #2845 from smitsohu/homedir2
improve support for home directories outside /home
-rw-r--r--etc/firejail.config3
-rw-r--r--src/firejail/checkcfg.c2
-rw-r--r--src/firejail/firejail.h1
-rw-r--r--src/firejail/fs.c9
-rw-r--r--src/firejail/fs_home.c88
-rw-r--r--src/firejail/fs_whitelist.c10
-rw-r--r--src/firejail/main.c51
-rw-r--r--src/firejail/restrict_users.c16
8 files changed, 135 insertions, 45 deletions
diff --git a/etc/firejail.config b/etc/firejail.config
index 565796d5a..4c0cb2a41 100644
--- a/etc/firejail.config
+++ b/etc/firejail.config
@@ -2,6 +2,9 @@
2# keyword-argument pairs, one per line. Most features are enabled by default. 2# keyword-argument pairs, one per line. Most features are enabled by default.
3# Use 'yes' or 'no' as configuration values. 3# Use 'yes' or 'no' as configuration values.
4 4
5# Allow symbolic links in path of user home directories, default disabled.
6# homedir-symlink no
7
5# Enable AppArmor functionality, default enabled. 8# Enable AppArmor functionality, default enabled.
6# apparmor yes 9# apparmor yes
7 10
diff --git a/src/firejail/checkcfg.c b/src/firejail/checkcfg.c
index f94b95d60..84054fe76 100644
--- a/src/firejail/checkcfg.c
+++ b/src/firejail/checkcfg.c
@@ -50,6 +50,7 @@ int checkcfg(int val) {
50 cfg_val[CFG_DISABLE_MNT] = 0; 50 cfg_val[CFG_DISABLE_MNT] = 0;
51 cfg_val[CFG_ARP_PROBES] = DEFAULT_ARP_PROBES; 51 cfg_val[CFG_ARP_PROBES] = DEFAULT_ARP_PROBES;
52 cfg_val[CFG_XPRA_ATTACH] = 0; 52 cfg_val[CFG_XPRA_ATTACH] = 0;
53 cfg_val[CFG_HOMEDIR_SYMLINK] = 0;
53 54
54 // open configuration file 55 // open configuration file
55 const char *fname = SYSCONFDIR "/firejail.config"; 56 const char *fname = SYSCONFDIR "/firejail.config";
@@ -85,6 +86,7 @@ int checkcfg(int val) {
85 ptr = line_remove_spaces(buf); 86 ptr = line_remove_spaces(buf);
86 if (!ptr) 87 if (!ptr)
87 continue; 88 continue;
89 PARSE_YESNO(CFG_HOMEDIR_SYMLINK, "homedir-symlink")
88 PARSE_YESNO(CFG_FILE_TRANSFER, "file-transfer") 90 PARSE_YESNO(CFG_FILE_TRANSFER, "file-transfer")
89 PARSE_YESNO(CFG_DBUS, "dbus") 91 PARSE_YESNO(CFG_DBUS, "dbus")
90 PARSE_YESNO(CFG_JOIN, "join") 92 PARSE_YESNO(CFG_JOIN, "join")
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 7664c8037..4bd70697e 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -720,6 +720,7 @@ enum {
720 CFG_PRIVATE_CACHE, 720 CFG_PRIVATE_CACHE,
721 CFG_CGROUP, 721 CFG_CGROUP,
722 CFG_NAME_CHANGE, 722 CFG_NAME_CHANGE,
723 CFG_HOMEDIR_SYMLINK,
723 // CFG_FILE_COPY_LIMIT - file copy limit handled using setenv/getenv 724 // CFG_FILE_COPY_LIMIT - file copy limit handled using setenv/getenv
724 CFG_MAX // this should always be the last entry 725 CFG_MAX // this should always be the last entry
725}; 726};
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index fe0427466..ce2ca5e2a 100644
--- a/src/firejail/fs.c
+++ b/src/firejail/fs.c
@@ -441,6 +441,8 @@ static int get_mount_flags(const char *path, unsigned long *flags) {
441// mount a writable tmpfs on directory 441// mount a writable tmpfs on directory
442void fs_tmpfs(const char *dir, unsigned check_owner) { 442void fs_tmpfs(const char *dir, unsigned check_owner) {
443 assert(dir); 443 assert(dir);
444 if (arg_debug)
445 printf("Mounting tmpfs on %s\n", dir);
444 // get a file descriptor for dir, fails if there is any symlink 446 // get a file descriptor for dir, fails if there is any symlink
445 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 447 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
446 if (fd == -1) 448 if (fd == -1)
@@ -449,12 +451,9 @@ void fs_tmpfs(const char *dir, unsigned check_owner) {
449 if (fstat(fd, &s) == -1) 451 if (fstat(fd, &s) == -1)
450 errExit("fstat"); 452 errExit("fstat");
451 if (check_owner && s.st_uid != getuid()) { 453 if (check_owner && s.st_uid != getuid()) {
452 fwarning("no tmpfs mounted on %s: not owned by the current user\n", dir); 454 fprintf(stderr, "Error: cannot mount tmpfs on %s: not owned by the current user\n", dir);
453 close(fd); 455 exit(1);
454 return;
455 } 456 }
456 if (arg_debug)
457 printf("Mounting tmpfs on %s\n", dir);
458 // preserve ownership, mode 457 // preserve ownership, mode
459 char *options; 458 char *options;
460 if (asprintf(&options, "mode=%o,uid=%u,gid=%u", s.st_mode & 07777, s.st_uid, s.st_gid) == -1) 459 if (asprintf(&options, "mode=%o,uid=%u,gid=%u", s.st_mode & 07777, s.st_uid, s.st_gid) == -1)
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c
index 3f6d78db4..69ad5e2c8 100644
--- a/src/firejail/fs_home.c
+++ b/src/firejail/fs_home.c
@@ -239,13 +239,16 @@ void fs_private_homedir(void) {
239 // mount bind private_homedir on top of homedir 239 // mount bind private_homedir on top of homedir
240 if (arg_debug) 240 if (arg_debug)
241 printf("Mount-bind %s on top of %s\n", private_homedir, homedir); 241 printf("Mount-bind %s on top of %s\n", private_homedir, homedir);
242 // get a file descriptor for private_homedir, fails if there is any symlink 242 // get file descriptors for homedir and private_homedir, fails if there is any symlink
243 int fd = safe_fd(private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 243 int src = safe_fd(private_homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
244 if (fd == -1) 244 if (src == -1)
245 errExit("safe_fd");
246 int dst = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
247 if (dst == -1)
245 errExit("safe_fd"); 248 errExit("safe_fd");
246 // check if new home directory is owned by the user 249 // check if new home directory is owned by the user
247 struct stat s; 250 struct stat s;
248 if (fstat(fd, &s) == -1) 251 if (fstat(src, &s) == -1)
249 errExit("fstat"); 252 errExit("fstat");
250 if (s.st_uid != getuid()) { 253 if (s.st_uid != getuid()) {
251 fprintf(stderr, "Error: private directory is not owned by the current user\n"); 254 fprintf(stderr, "Error: private directory is not owned by the current user\n");
@@ -253,17 +256,27 @@ void fs_private_homedir(void) {
253 } 256 }
254 if ((S_IRWXU & s.st_mode) != S_IRWXU) 257 if ((S_IRWXU & s.st_mode) != S_IRWXU)
255 fwarning("no full permissions on private directory\n"); 258 fwarning("no full permissions on private directory\n");
256 // mount via the link in /proc/self/fd 259 // mount via the links in /proc/self/fd
257 char *proc; 260 char *proc_src, *proc_dst;
258 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 261 if (asprintf(&proc_src, "/proc/self/fd/%d", src) == -1)
262 errExit("asprintf");
263 if (asprintf(&proc_dst, "/proc/self/fd/%d", dst) == -1)
259 errExit("asprintf"); 264 errExit("asprintf");
260 if (mount(proc, homedir, NULL, MS_NOSUID | MS_NODEV | MS_BIND | MS_REC, NULL) < 0) 265 if (mount(proc_src, proc_dst, NULL, MS_NOSUID | MS_NODEV | MS_BIND | MS_REC, NULL) < 0)
261 errExit("mount bind"); 266 errExit("mount bind");
262 free(proc); 267 free(proc_src);
263 close(fd); 268 free(proc_dst);
264 269 close(src);
265 fs_logger3("mount-bind", private_homedir, cfg.homedir); 270 close(dst);
266 fs_logger2("whitelist", cfg.homedir); 271 // check /proc/self/mountinfo to confirm the mount is ok
272 MountData *mptr = get_last_mount();
273 size_t len = strlen(homedir);
274 if (strncmp(mptr->dir, homedir, len) != 0 ||
275 (*(mptr->dir + len) != '\0' && *(mptr->dir + len) != '/'))
276 errLogExit("invalid private mount");
277
278 fs_logger3("mount-bind", private_homedir, homedir);
279 fs_logger2("whitelist", homedir);
267// preserve mode and ownership 280// preserve mode and ownership
268// if (chown(homedir, s.st_uid, s.st_gid) == -1) 281// if (chown(homedir, s.st_uid, s.st_gid) == -1)
269// errExit("mount-bind chown"); 282// errExit("mount-bind chown");
@@ -310,13 +323,13 @@ void fs_private(void) {
310 int aflag = store_asoundrc(); 323 int aflag = store_asoundrc();
311 324
312 // mask /home 325 // mask /home
313 if (arg_debug)
314 printf("Mounting a new /home directory\n");
315 if (u == 0 && arg_allusers) // allow --allusers when starting the sandbox as root 326 if (u == 0 && arg_allusers) // allow --allusers when starting the sandbox as root
316 ; 327 ;
317 else { 328 else {
329 if (arg_debug)
330 printf("Mounting a new /home directory\n");
318 if (arg_allusers) 331 if (arg_allusers)
319 fwarning("--allusers disabled by --private or --whitelist\n"); 332 fwarning("allusers option disabled by private or whitelist option\n");
320 if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_STRICTATIME, "mode=755,gid=0") < 0) 333 if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_STRICTATIME, "mode=755,gid=0") < 0)
321 errExit("mounting home directory"); 334 errExit("mounting home directory");
322 fs_logger("tmpfs /home"); 335 fs_logger("tmpfs /home");
@@ -330,19 +343,24 @@ void fs_private(void) {
330 fs_logger("tmpfs /root"); 343 fs_logger("tmpfs /root");
331 344
332 if (u != 0) { 345 if (u != 0) {
333 // create /home/user 346 if (strncmp(homedir, "/home/", 6) == 0) {
334 if (arg_debug) 347 // create /home/user
335 printf("Create a new user directory\n"); 348 if (arg_debug)
336 if (mkdir(homedir, S_IRWXU) == -1) { 349 printf("Create a new user directory\n");
337 if (mkpath_as_root(homedir) == -1) 350 if (mkdir(homedir, S_IRWXU) == -1) {
338 errExit("mkpath"); 351 if (mkpath_as_root(homedir) == -1)
339 if (mkdir(homedir, S_IRWXU) == -1 && errno != EEXIST) 352 errExit("mkpath");
340 errExit("mkdir"); 353 if (mkdir(homedir, S_IRWXU) == -1 && errno != EEXIST)
354 errExit("mkdir");
355 }
356 if (chown(homedir, u, g) < 0)
357 errExit("chown");
358 fs_logger2("mkdir", homedir);
359 fs_logger2("tmpfs", homedir);
341 } 360 }
342 if (chown(homedir, u, g) < 0) 361 else
343 errExit("chown"); 362 // user directory is outside /home, mask it as well
344 fs_logger2("mkdir", homedir); 363 fs_tmpfs(homedir, 1);
345 fs_logger2("tmpfs", homedir);
346 } 364 }
347 365
348 skel(homedir, u, g); 366 skel(homedir, u, g);
@@ -528,8 +546,20 @@ void fs_private_home_list(void) {
528 if (arg_debug) 546 if (arg_debug)
529 printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); 547 printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir);
530 548
531 if (mount(RUN_HOME_DIR, homedir, NULL, MS_BIND|MS_REC, NULL) < 0) 549 int fd = safe_fd(homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
550 if (fd == -1)
551 errExit("safe_fd");
552 char *proc;
553 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
554 errExit("asprintf");
555 if (mount(RUN_HOME_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0)
532 errExit("mount bind"); 556 errExit("mount bind");
557 free(proc);
558 close(fd);
559 // check /proc/self/mountinfo to confirm the mount is ok
560 MountData *mptr = get_last_mount();
561 if (strcmp(mptr->dir, homedir) != 0 || strcmp(mptr->fstype, "tmpfs") != 0)
562 errLogExit("invalid private-home mount");
533 fs_logger2("tmpfs", homedir); 563 fs_logger2("tmpfs", homedir);
534 564
535 if (uid != 0) { 565 if (uid != 0) {
diff --git a/src/firejail/fs_whitelist.c b/src/firejail/fs_whitelist.c
index c8fa8c72b..a950d1124 100644
--- a/src/firejail/fs_whitelist.c
+++ b/src/firejail/fs_whitelist.c
@@ -710,8 +710,16 @@ void fs_whitelist(void) {
710 if (stat(cfg.homedir, &s) == 0) { 710 if (stat(cfg.homedir, &s) == 0) {
711 // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR 711 // keep a copy of real home dir in RUN_WHITELIST_HOME_USER_DIR
712 mkdir_attr(RUN_WHITELIST_HOME_USER_DIR, 0755, getuid(), getgid()); 712 mkdir_attr(RUN_WHITELIST_HOME_USER_DIR, 0755, getuid(), getgid());
713 if (mount(cfg.homedir, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) 713 int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
714 if (fd == -1)
715 errExit("safe_fd");
716 char *proc;
717 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
718 errExit("asprintf");
719 if (mount(proc, RUN_WHITELIST_HOME_USER_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
714 errExit("mount bind"); 720 errExit("mount bind");
721 free(proc);
722 close(fd);
715 723
716 // mount a tmpfs and initialize /home/user, overrides --allusers 724 // mount a tmpfs and initialize /home/user, overrides --allusers
717 fs_private(); 725 fs_private();
diff --git a/src/firejail/main.c b/src/firejail/main.c
index 1e2488062..d00147c74 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -26,7 +26,6 @@
26#include <sys/mount.h> 26#include <sys/mount.h>
27#include <sys/wait.h> 27#include <sys/wait.h>
28#include <sys/stat.h> 28#include <sys/stat.h>
29#include <fcntl.h>
30#include <dirent.h> 29#include <dirent.h>
31#include <pwd.h> 30#include <pwd.h>
32#include <errno.h> 31#include <errno.h>
@@ -38,6 +37,11 @@
38#include <net/if.h> 37#include <net/if.h>
39#include <sys/utsname.h> 38#include <sys/utsname.h>
40 39
40#include <fcntl.h>
41#ifndef O_PATH
42#define O_PATH 010000000
43#endif
44
41#ifdef __ia64__ 45#ifdef __ia64__
42/* clone(2) has a different interface on ia64, as it needs to know 46/* clone(2) has a different interface on ia64, as it needs to know
43 the size of the stack */ 47 the size of the stack */
@@ -242,6 +246,41 @@ static pid_t require_pid(const char *name) {
242 return pid; 246 return pid;
243} 247}
244 248
249// return 1 if there is a link somewhere in path of directory
250static int has_link(const char *dir) {
251 assert(dir);
252 int fd = safe_fd(dir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
253 if (fd == -1) {
254 if (errno == ENOTDIR && is_dir(dir))
255 return 1;
256 }
257 else
258 close(fd);
259 return 0;
260}
261
262static void build_cfg_homedir(const char *dir) {
263 EUID_ASSERT();
264 assert(dir);
265 if (dir[0] != '/') {
266 fprintf(stderr, "Error: invalid user directory \"%s\"\n", dir);
267 exit(1);
268 }
269 // symlinks are rejected in many places, provide a solution for home directories
270 if (checkcfg(CFG_HOMEDIR_SYMLINK)) {
271 cfg.homedir = realpath(dir, NULL);
272 if (cfg.homedir)
273 return;
274 }
275 else if (has_link(dir)) {
276 fprintf(stderr, "Error: path of user directory contains a symbolic link. "
277 "Please provide resolved path in password database (/etc/passwd) "
278 "or enable symbolic link resolution in Firejail configuration file.\n");
279 exit(1);
280 }
281 cfg.homedir = clean_pathname(dir);
282}
283
245// init configuration 284// init configuration
246static void init_cfg(int argc, char **argv) { 285static void init_cfg(int argc, char **argv) {
247 EUID_ASSERT(); 286 EUID_ASSERT();
@@ -265,15 +304,12 @@ static void init_cfg(int argc, char **argv) {
265 errExit("strdup"); 304 errExit("strdup");
266 305
267 // build home directory name 306 // build home directory name
268 cfg.homedir = NULL; 307 if (pw->pw_dir == NULL) {
269 if (pw->pw_dir != NULL) {
270 cfg.homedir = clean_pathname(pw->pw_dir);
271 assert(cfg.homedir);
272 }
273 else {
274 fprintf(stderr, "Error: user %s doesn't have a user directory assigned\n", cfg.username); 308 fprintf(stderr, "Error: user %s doesn't have a user directory assigned\n", cfg.username);
275 exit(1); 309 exit(1);
276 } 310 }
311 build_cfg_homedir(pw->pw_dir);
312 assert(cfg.homedir);
277 313
278 cfg.cwd = getcwd(NULL, 0); 314 cfg.cwd = getcwd(NULL, 0);
279 if (!cfg.cwd && errno != ENOENT) 315 if (!cfg.cwd && errno != ENOENT)
@@ -926,7 +962,6 @@ int main(int argc, char **argv) {
926 962
927 // check if the user is allowed to use firejail 963 // check if the user is allowed to use firejail
928 init_cfg(argc, argv); 964 init_cfg(argc, argv);
929 assert(cfg.homedir);
930 965
931 // get starting timestamp, process --quiet 966 // get starting timestamp, process --quiet
932 start_timestamp = getticks(); 967 start_timestamp = getticks();
diff --git a/src/firejail/restrict_users.c b/src/firejail/restrict_users.c
index 5c5ace90b..ee2e497cb 100644
--- a/src/firejail/restrict_users.c
+++ b/src/firejail/restrict_users.c
@@ -25,9 +25,13 @@
25#include <fnmatch.h> 25#include <fnmatch.h>
26#include <glob.h> 26#include <glob.h>
27#include <dirent.h> 27#include <dirent.h>
28#include <fcntl.h>
29#include <errno.h> 28#include <errno.h>
30 29
30#include <fcntl.h>
31#ifndef O_PATH
32# define O_PATH 010000000
33#endif
34
31#define MAXBUF 1024 35#define MAXBUF 1024
32 36
33// linked list of users 37// linked list of users
@@ -79,8 +83,16 @@ static void sanitize_home(void) {
79 errExit("mkdir"); 83 errExit("mkdir");
80 84
81 // keep a copy of the user home directory 85 // keep a copy of the user home directory
82 if (mount(cfg.homedir, RUN_WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) 86 int fd = safe_fd(cfg.homedir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
87 if (fd == -1)
88 errExit("safe_fd");
89 char *proc;
90 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
91 errExit("asprintf");
92 if (mount(proc, RUN_WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0)
83 errExit("mount bind"); 93 errExit("mount bind");
94 free(proc);
95 close(fd);
84 96
85 // mount tmpfs in the new home 97 // mount tmpfs in the new home
86 if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0) 98 if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME, "mode=755,gid=0") < 0)