diff options
Diffstat (limited to 'src/firejail/fs_home.c')
-rw-r--r-- | src/firejail/fs_home.c | 315 |
1 files changed, 77 insertions, 238 deletions
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c index 242482d26..1f8da398e 100644 --- a/src/firejail/fs_home.c +++ b/src/firejail/fs_home.c | |||
@@ -28,7 +28,7 @@ | |||
28 | #include <sys/wait.h> | 28 | #include <sys/wait.h> |
29 | #include <unistd.h> | 29 | #include <unistd.h> |
30 | #include <grp.h> | 30 | #include <grp.h> |
31 | #include <ftw.h> | 31 | //#include <ftw.h> |
32 | 32 | ||
33 | static void skel(const char *homedir, uid_t u, gid_t g) { | 33 | static void skel(const char *homedir, uid_t u, gid_t g) { |
34 | char *fname; | 34 | char *fname; |
@@ -212,12 +212,6 @@ void fs_private_homedir(void) { | |||
212 | 212 | ||
213 | uid_t u = getuid(); | 213 | uid_t u = getuid(); |
214 | gid_t g = getgid(); | 214 | gid_t g = getgid(); |
215 | struct stat s; | ||
216 | if (stat(homedir, &s) == -1) { | ||
217 | fprintf(stderr, "Error: cannot find user home directory\n"); | ||
218 | exit(1); | ||
219 | } | ||
220 | |||
221 | 215 | ||
222 | // mount bind private_homedir on top of homedir | 216 | // mount bind private_homedir on top of homedir |
223 | if (arg_debug) | 217 | if (arg_debug) |
@@ -349,122 +343,17 @@ void fs_check_private_dir(void) { | |||
349 | //*********************************************************************************** | 343 | //*********************************************************************************** |
350 | // --private-home | 344 | // --private-home |
351 | //*********************************************************************************** | 345 | //*********************************************************************************** |
352 | #define PRIVATE_COPY_LIMIT (500 * 1024 *1024) | ||
353 | static int size_limit_reached = 0; | ||
354 | static unsigned file_cnt = 0; | ||
355 | static unsigned size_cnt = 0; | ||
356 | static char *check_dir_or_file(const char *name); | ||
357 | |||
358 | int fs_copydir(const char *path, const struct stat *st, int ftype, struct FTW *sftw) { | ||
359 | (void) st; | ||
360 | (void) sftw; | ||
361 | if (size_limit_reached) | ||
362 | return 0; | ||
363 | |||
364 | struct stat s; | ||
365 | char *dest; | ||
366 | if (asprintf(&dest, "%s%s", RUN_HOME_DIR, path + strlen(cfg.homedir)) == -1) | ||
367 | errExit("asprintf"); | ||
368 | |||
369 | // don't copy it if we already have the file | ||
370 | if (stat(dest, &s) == 0) { | ||
371 | free(dest); | ||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | // extract mode and ownership | ||
376 | if (stat(path, &s) != 0) { | ||
377 | free(dest); | ||
378 | return 0; | ||
379 | } | ||
380 | |||
381 | // check uid | ||
382 | if (s.st_uid != firejail_uid || s.st_gid != firejail_gid) { | ||
383 | free(dest); | ||
384 | return 0; | ||
385 | } | ||
386 | |||
387 | if ((s.st_size + size_cnt) > PRIVATE_COPY_LIMIT) { | ||
388 | size_limit_reached = 1; | ||
389 | free(dest); | ||
390 | return 0; | ||
391 | } | ||
392 | |||
393 | file_cnt++; | ||
394 | size_cnt += s.st_size; | ||
395 | |||
396 | if(ftype == FTW_F) | ||
397 | copy_file(path, dest, firejail_uid, firejail_gid, s.st_mode); | ||
398 | else if (ftype == FTW_D) { | ||
399 | if (mkdir(dest, s.st_mode) == -1) | ||
400 | errExit("mkdir"); | ||
401 | if (set_perms(dest, firejail_uid, firejail_gid, s.st_mode)) | ||
402 | errExit("set_perms"); | ||
403 | #if 0 | ||
404 | struct stat s2; | ||
405 | if (stat(dest, &s2) == 0) { | ||
406 | printf("%s\t", dest); | ||
407 | printf((S_ISDIR(s.st_mode)) ? "d" : "-"); | ||
408 | printf((s.st_mode & S_IRUSR) ? "r" : "-"); | ||
409 | printf((s.st_mode & S_IWUSR) ? "w" : "-"); | ||
410 | printf((s.st_mode & S_IXUSR) ? "x" : "-"); | ||
411 | printf((s.st_mode & S_IRGRP) ? "r" : "-"); | ||
412 | printf((s.st_mode & S_IWGRP) ? "w" : "-"); | ||
413 | printf((s.st_mode & S_IXGRP) ? "x" : "-"); | ||
414 | printf((s.st_mode & S_IROTH) ? "r" : "-"); | ||
415 | printf((s.st_mode & S_IWOTH) ? "w" : "-"); | ||
416 | printf((s.st_mode & S_IXOTH) ? "x" : "-"); | ||
417 | printf("\n"); | ||
418 | } | ||
419 | #endif | ||
420 | |||
421 | fs_logger2("clone", path); | ||
422 | } | ||
423 | |||
424 | free(dest); | ||
425 | return(0); | ||
426 | } | ||
427 | |||
428 | static void duplicate(char *name) { | ||
429 | char *fname = check_dir_or_file(name); | ||
430 | |||
431 | if (arg_debug) | ||
432 | printf("Private home: duplicating %s\n", fname); | ||
433 | assert(strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0); | ||
434 | |||
435 | struct stat s; | ||
436 | if (stat(fname, &s) == -1) { | ||
437 | free(fname); | ||
438 | return; | ||
439 | } | ||
440 | |||
441 | if(nftw(fname, fs_copydir, 1, FTW_PHYS) != 0) { | ||
442 | fprintf(stderr, "Error: unable to copy template dir\n"); | ||
443 | exit(1); | ||
444 | } | ||
445 | fs_logger_print(); // save the current log | ||
446 | |||
447 | free(fname); | ||
448 | } | ||
449 | |||
450 | |||
451 | |||
452 | static char *check_dir_or_file(const char *name) { | 346 | static char *check_dir_or_file(const char *name) { |
453 | assert(name); | 347 | assert(name); |
454 | struct stat s; | ||
455 | 348 | ||
456 | // basic checks | 349 | // basic checks |
457 | invalid_filename(name); | 350 | invalid_filename(name); |
458 | |||
459 | if (arg_debug) | 351 | if (arg_debug) |
460 | printf("Private home: checking %s\n", name); | 352 | printf("Private home: checking %s\n", name); |
461 | 353 | ||
462 | // expand home directory | 354 | // expand home directory |
463 | char *fname = expand_home(name, cfg.homedir); | 355 | char *fname = expand_home(name, cfg.homedir); |
464 | if (!fname) { | 356 | assert(fname); |
465 | fprintf(stderr, "Error: file %s not found.\n", name); | ||
466 | exit(1); | ||
467 | } | ||
468 | 357 | ||
469 | // If it doesn't start with '/', it must be relative to homedir | 358 | // If it doesn't start with '/', it must be relative to homedir |
470 | if (fname[0] != '/') { | 359 | if (fname[0] != '/') { |
@@ -475,87 +364,77 @@ static char *check_dir_or_file(const char *name) { | |||
475 | fname = tmp; | 364 | fname = tmp; |
476 | } | 365 | } |
477 | 366 | ||
478 | // check the file is in user home directory | 367 | // we allow only files in user home directory or symbolic links to files or directories owned by the user |
479 | char *rname = realpath(fname, NULL); | 368 | struct stat s; |
480 | if (!rname) { | 369 | if (lstat(fname, &s) == 0 && S_ISLNK(s.st_mode)) { |
481 | fprintf(stderr, "Error: invalid file %s\n", name); | 370 | if (stat(fname, &s) == 0) { |
482 | exit(1); | 371 | if (s.st_uid != getuid()) { |
483 | } | 372 | fprintf(stderr, "Error: symbolic link %s to file or directory not owned by the user\n", fname); |
484 | if (strncmp(rname, cfg.homedir, strlen(cfg.homedir)) != 0) { | 373 | exit(1); |
485 | fprintf(stderr, "Error: file %s is not in user home directory\n", name); | 374 | } |
486 | exit(1); | 375 | return fname; |
487 | } | 376 | } |
488 | 377 | else { | |
489 | // a full home directory is not allowed | 378 | fprintf(stderr, "Error: invalid file %s\n", name); |
490 | if (strcmp(rname, cfg.homedir) == 0) { | ||
491 | fprintf(stderr, "Error: invalid directory %s\n", rname); | ||
492 | exit(1); | ||
493 | } | ||
494 | |||
495 | // only top files and directories in user home are allowed | ||
496 | char *ptr = rname + strlen(cfg.homedir); | ||
497 | if (*ptr == '\0') { | ||
498 | fprintf(stderr, "Error: invalid file %s\n", name); | ||
499 | exit(1); | ||
500 | } | ||
501 | ptr++; | ||
502 | ptr = strchr(ptr, '/'); | ||
503 | if (ptr) { | ||
504 | if (*ptr != '\0') { | ||
505 | fprintf(stderr, "Error: only top files and directories in user home are allowed\n"); | ||
506 | exit(1); | 379 | exit(1); |
507 | } | 380 | } |
508 | } | 381 | } |
509 | 382 | else { | |
510 | if (stat(fname, &s) == -1) { | 383 | // check the file is in user home directory, a full home directory is not allowed |
511 | fprintf(stderr, "Error: file %s not found.\n", fname); | 384 | char *rname = realpath(fname, NULL); |
512 | exit(1); | 385 | if (!rname || |
513 | } | 386 | strncmp(rname, cfg.homedir, strlen(cfg.homedir)) != 0 || |
514 | 387 | strcmp(rname, cfg.homedir) == 0) { | |
515 | // check uid | 388 | fprintf(stderr, "Error: invalid file %s\n", name); |
516 | uid_t uid = getuid(); | 389 | exit(1); |
517 | gid_t gid = getgid(); | 390 | } |
518 | if (s.st_uid != uid || s.st_gid != gid) { | 391 | |
519 | fprintf(stderr, "Error: only files or directories created by the current user are allowed.\n"); | 392 | // only top files and directories in user home are allowed |
520 | exit(1); | 393 | char *ptr = rname + strlen(cfg.homedir); |
521 | } | 394 | assert(*ptr != '\0'); |
522 | 395 | ptr = strchr(++ptr, '/'); | |
523 | // dir or regular file | 396 | if (ptr) { |
524 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) { | 397 | if (*ptr != '\0') { |
398 | fprintf(stderr, "Error: only top files and directories in user home are allowed\n"); | ||
399 | exit(1); | ||
400 | } | ||
401 | } | ||
525 | free(fname); | 402 | free(fname); |
526 | return rname; // regular exit from the function | 403 | return rname; |
527 | } | 404 | } |
528 | |||
529 | fprintf(stderr, "Error: invalid file type, %s.\n", fname); | ||
530 | exit(1); | ||
531 | } | 405 | } |
532 | 406 | ||
407 | static void duplicate(char *name) { | ||
408 | char *fname = check_dir_or_file(name); | ||
533 | 409 | ||
534 | // check directory list specified by user (--private-home option) - exit if it fails | 410 | if (arg_debug) |
535 | void fs_check_home_list(void) { | 411 | printf("Private home: duplicating %s\n", fname); |
536 | if (strstr(cfg.home_private_keep, "..")) { | 412 | assert(strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0); |
537 | fprintf(stderr, "Error: invalid private-home list\n"); | ||
538 | exit(1); | ||
539 | } | ||
540 | |||
541 | char *dlist = strdup(cfg.home_private_keep); | ||
542 | if (!dlist) | ||
543 | errExit("strdup"); | ||
544 | |||
545 | char *ptr = strtok(dlist, ","); | ||
546 | char *tmp = check_dir_or_file(ptr); | ||
547 | free(tmp); | ||
548 | 413 | ||
549 | while ((ptr = strtok(NULL, ",")) != NULL) { | 414 | struct stat s; |
550 | tmp = check_dir_or_file(ptr); | 415 | if (lstat(fname, &s) == -1) { |
551 | free(tmp); | 416 | free(fname); |
417 | return; | ||
418 | } | ||
419 | else if (S_ISDIR(s.st_mode)) { | ||
420 | // create the directory in RUN_HOME_DIR | ||
421 | char *name; | ||
422 | char *ptr = strrchr(fname, '/'); | ||
423 | ptr++; | ||
424 | if (asprintf(&name, "%s/%s", RUN_HOME_DIR, ptr) == -1) | ||
425 | errExit("asprintf"); | ||
426 | mkdir_attr(name, 0755, getuid(), getgid()); | ||
427 | sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FCOPY, fname, name); | ||
428 | free(name); | ||
552 | } | 429 | } |
430 | else | ||
431 | sbox_run(SBOX_USER| SBOX_CAPS_NONE | SBOX_SECCOMP, 3, PATH_FCOPY, fname, RUN_HOME_DIR); | ||
432 | fs_logger2("clone", fname); | ||
433 | fs_logger_print(); // save the current log | ||
553 | 434 | ||
554 | free(dlist); | 435 | free(fname); |
555 | } | 436 | } |
556 | 437 | ||
557 | |||
558 | |||
559 | // private mode (--private-home=list): | 438 | // private mode (--private-home=list): |
560 | // mount homedir on top of /home/user, | 439 | // mount homedir on top of /home/user, |
561 | // tmpfs on top of /root in nonroot mode, | 440 | // tmpfs on top of /root in nonroot mode, |
@@ -571,68 +450,28 @@ void fs_private_home_list(void) { | |||
571 | int xflag = store_xauthority(); | 450 | int xflag = store_xauthority(); |
572 | int aflag = store_asoundrc(); | 451 | int aflag = store_asoundrc(); |
573 | 452 | ||
574 | uid_t u = firejail_uid; | 453 | uid_t uid = getuid(); |
575 | gid_t g = firejail_gid; | 454 | gid_t gid = getgid(); |
576 | struct stat s; | ||
577 | if (stat(homedir, &s) == -1) { | ||
578 | fprintf(stderr, "Error: cannot find user home directory\n"); | ||
579 | exit(1); | ||
580 | } | ||
581 | 455 | ||
582 | // create /run/firejail/mnt/home directory | 456 | // create /run/firejail/mnt/home directory |
583 | int rv = mkdir(RUN_HOME_DIR, 0755); | 457 | mkdir_attr(RUN_HOME_DIR, 0755, uid, gid); |
584 | if (rv == -1) | ||
585 | errExit("mkdir"); | ||
586 | if (set_perms(RUN_HOME_DIR, u, g, 0755)) | ||
587 | errExit("set_perms"); | ||
588 | ASSERT_PERMS(RUN_HOME_DIR, u, g, 0755); | ||
589 | |||
590 | fs_logger_print(); // save the current log | 458 | fs_logger_print(); // save the current log |
591 | 459 | ||
460 | if (arg_debug) | ||
461 | printf("Copying files in the new home:\n"); | ||
462 | |||
592 | // copy the list of files in the new home directory | 463 | // copy the list of files in the new home directory |
593 | // using a new child process without root privileges | 464 | char *dlist = strdup(cfg.home_private_keep); |
594 | pid_t child = fork(); | 465 | if (!dlist) |
595 | if (child < 0) | 466 | errExit("strdup"); |
596 | errExit("fork"); | 467 | |
597 | if (child == 0) { | 468 | char *ptr = strtok(dlist, ","); |
598 | if (arg_debug) | 469 | duplicate(ptr); |
599 | printf("Copying files in the new home:\n"); | 470 | while ((ptr = strtok(NULL, ",")) != NULL) |
600 | |||
601 | // drop privileges | ||
602 | if (setgroups(0, NULL) < 0) | ||
603 | errExit("setgroups"); | ||
604 | if (setgid(getgid()) < 0) | ||
605 | errExit("setgid/getgid"); | ||
606 | if (setuid(getuid()) < 0) | ||
607 | errExit("setuid/getuid"); | ||
608 | |||
609 | // copy the list of files in the new home directory | ||
610 | char *dlist = strdup(cfg.home_private_keep); | ||
611 | if (!dlist) | ||
612 | errExit("strdup"); | ||
613 | |||
614 | char *ptr = strtok(dlist, ","); | ||
615 | duplicate(ptr); | 471 | duplicate(ptr); |
616 | while ((ptr = strtok(NULL, ",")) != NULL) | ||
617 | duplicate(ptr); | ||
618 | |||
619 | if (!arg_quiet) { | ||
620 | if (size_limit_reached) | ||
621 | fprintf(stderr, "Warning: private-home copy limit of %u MB reached, not all the files were copied\n", | ||
622 | PRIVATE_COPY_LIMIT / (1024 *1024)); | ||
623 | else | ||
624 | printf("Private home: %u files, total size %u bytes\n", file_cnt, size_cnt); | ||
625 | } | ||
626 | 472 | ||
627 | fs_logger_print(); // save the current log | 473 | fs_logger_print(); // save the current log |
628 | free(dlist); | 474 | free(dlist); |
629 | #ifdef HAVE_GCOV | ||
630 | __gcov_flush(); | ||
631 | #endif | ||
632 | _exit(0); | ||
633 | } | ||
634 | // wait for the child to finish | ||
635 | waitpid(child, NULL, 0); | ||
636 | 475 | ||
637 | if (arg_debug) | 476 | if (arg_debug) |
638 | printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); | 477 | printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); |
@@ -640,7 +479,7 @@ void fs_private_home_list(void) { | |||
640 | if (mount(RUN_HOME_DIR, homedir, NULL, MS_BIND|MS_REC, NULL) < 0) | 479 | if (mount(RUN_HOME_DIR, homedir, NULL, MS_BIND|MS_REC, NULL) < 0) |
641 | errExit("mount bind"); | 480 | errExit("mount bind"); |
642 | 481 | ||
643 | if (u != 0) { | 482 | if (uid != 0) { |
644 | // mask /root | 483 | // mask /root |
645 | if (arg_debug) | 484 | if (arg_debug) |
646 | printf("Mounting a new /root directory\n"); | 485 | printf("Mounting a new /root directory\n"); |
@@ -655,7 +494,7 @@ void fs_private_home_list(void) { | |||
655 | errExit("mounting home directory"); | 494 | errExit("mounting home directory"); |
656 | } | 495 | } |
657 | 496 | ||
658 | skel(homedir, u, g); | 497 | skel(homedir, uid, gid); |
659 | if (xflag) | 498 | if (xflag) |
660 | copy_xauthority(); | 499 | copy_xauthority(); |
661 | if (aflag) | 500 | if (aflag) |