diff options
Diffstat (limited to 'src/firejail/fs_home.c')
-rw-r--r-- | src/firejail/fs_home.c | 408 |
1 files changed, 317 insertions, 91 deletions
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c index 85fa244be..a4b2ec046 100644 --- a/src/firejail/fs_home.c +++ b/src/firejail/fs_home.c | |||
@@ -313,61 +313,6 @@ void fs_private(void) { | |||
313 | 313 | ||
314 | } | 314 | } |
315 | 315 | ||
316 | int fs_copydir(const char *path, const struct stat *st, int ftype, struct FTW *sftw); | ||
317 | |||
318 | |||
319 | int fs_copydir(const char *path, const struct stat *st, int ftype, struct FTW *sftw) | ||
320 | { | ||
321 | (void) st; | ||
322 | (void) sftw; | ||
323 | char *homedir = cfg.homedir; | ||
324 | char *dest; | ||
325 | int srcbaselen = 0; | ||
326 | assert(homedir); | ||
327 | uid_t u = getuid(); | ||
328 | gid_t g = getgid(); | ||
329 | srcbaselen = strlen(cfg.private_template); | ||
330 | |||
331 | if(ftype == FTW_F || ftype == FTW_D) { | ||
332 | if (asprintf(&dest, "%s/%s", homedir, path + srcbaselen) == -1) | ||
333 | errExit("asprintf"); | ||
334 | struct stat s; | ||
335 | // don't copy it if we already have the file | ||
336 | if (stat(dest, &s) == 0) | ||
337 | return(0); | ||
338 | if (stat(path, &s) == 0) { | ||
339 | if(ftype == FTW_F) { | ||
340 | if (copy_file(path, dest, u, g, 0644) == 0) { | ||
341 | if (arg_debug) | ||
342 | printf("copy from %s to %s\n", path, dest); | ||
343 | fs_logger2("clone", path); | ||
344 | } | ||
345 | } | ||
346 | else if(ftype == FTW_D) { | ||
347 | if (mkdir(dest, s.st_mode) == -1) | ||
348 | errExit("mkdir"); | ||
349 | if (chown(dest, u, g) < 0) | ||
350 | errExit("chown"); | ||
351 | if (arg_debug) | ||
352 | printf("copy from %s to %s\n", path, dest); | ||
353 | fs_logger2("clone", path); | ||
354 | } | ||
355 | } | ||
356 | free(dest); | ||
357 | } | ||
358 | return(0); | ||
359 | } | ||
360 | |||
361 | void fs_private_template(void) { | ||
362 | |||
363 | fs_private(); | ||
364 | if(nftw(cfg.private_template, fs_copydir, 1, FTW_PHYS) != 0) { | ||
365 | fprintf(stderr, "Error: unable to copy template dir\n"); | ||
366 | exit(1); | ||
367 | } | ||
368 | |||
369 | } | ||
370 | |||
371 | // check new private home directory (--private= option) - exit if it fails | 316 | // check new private home directory (--private= option) - exit if it fails |
372 | void fs_check_private_dir(void) { | 317 | void fs_check_private_dir(void) { |
373 | EUID_ASSERT(); | 318 | EUID_ASSERT(); |
@@ -406,42 +351,323 @@ void fs_check_private_dir(void) { | |||
406 | } | 351 | } |
407 | } | 352 | } |
408 | 353 | ||
409 | // check new template home directoty (--private-template= option) - exit if it fails | 354 | //*********************************************************************************** |
410 | void fs_check_private_template(void) { | 355 | // --private-home |
411 | EUID_ASSERT(); | 356 | //*********************************************************************************** |
412 | invalid_filename(cfg.private_template); | 357 | #define PRIVATE_COPY_LIMIT (500 * 1024 *1024) |
413 | 358 | static int size_limit_reached = 0; | |
414 | // Expand the home directory | 359 | static unsigned file_cnt = 0; |
415 | char *tmp = expand_home(cfg.private_template, cfg.homedir); | 360 | static unsigned size_cnt = 0; |
416 | cfg.private_template = realpath(tmp, NULL); | 361 | static char *check_dir_or_file(const char *name); |
417 | free(tmp); | 362 | |
418 | 363 | int fs_copydir(const char *path, const struct stat *st, int ftype, struct FTW *sftw) { | |
419 | if (!cfg.private_template | 364 | if (size_limit_reached) |
420 | || !is_dir(cfg.private_template) | 365 | return 0; |
421 | || is_link(cfg.private_template) | 366 | |
422 | || strstr(cfg.private_template, "..")) { | 367 | struct stat s; |
423 | fprintf(stderr, "Error: invalid private template directory\n"); | 368 | char *dest; |
424 | exit(1); | 369 | if (asprintf(&dest, "%s%s", RUN_HOME_DIR, path + strlen(cfg.homedir)) == -1) |
425 | } | 370 | errExit("asprintf"); |
426 | 371 | ||
427 | // check home directory and chroot home directory have the same owner | 372 | // don't copy it if we already have the file |
428 | struct stat s2; | 373 | if (stat(dest, &s) == 0) { |
429 | int rv = stat(cfg.private_template, &s2); | 374 | free(dest); |
430 | if (rv < 0) { | 375 | return 0; |
431 | fprintf(stderr, "Error: cannot find %s directory\n", cfg.private_template); | 376 | } |
432 | exit(1); | 377 | |
433 | } | 378 | // extract mode and ownership |
434 | 379 | if (stat(path, &s) != 0) { | |
435 | struct stat s1; | 380 | free(dest); |
436 | rv = stat(cfg.homedir, &s1); | 381 | return 0; |
437 | if (rv < 0) { | 382 | } |
438 | fprintf(stderr, "Error: cannot find %s directory, full path name required\n", cfg.homedir); | 383 | |
439 | exit(1); | 384 | // check uid |
440 | } | 385 | if (s.st_uid != firejail_uid || s.st_gid != firejail_gid) { |
441 | if (s1.st_uid != s2.st_uid) { | 386 | free(dest); |
442 | printf("Error: --private-template directory should be owned by the current user\n"); | 387 | return 0; |
443 | exit(1); | 388 | } |
444 | } | 389 | |
390 | if ((s.st_size + size_cnt) > PRIVATE_COPY_LIMIT) { | ||
391 | size_limit_reached = 1; | ||
392 | free(dest); | ||
393 | return 0; | ||
394 | } | ||
395 | |||
396 | file_cnt++; | ||
397 | size_cnt += s.st_size; | ||
398 | |||
399 | if(ftype == FTW_F) | ||
400 | copy_file(path, dest, firejail_uid, firejail_gid, s.st_mode); | ||
401 | else if (ftype == FTW_D) { | ||
402 | if (mkdir(dest, s.st_mode) == -1) | ||
403 | errExit("mkdir"); | ||
404 | if (chmod(dest, s.st_mode) < 0) { | ||
405 | fprintf(stderr, "Error: cannot change mode for %s\n", path); | ||
406 | exit(1); | ||
407 | } | ||
408 | if (chown(dest, firejail_uid, firejail_gid) < 0) { | ||
409 | fprintf(stderr, "Error: cannot change ownership for %s\n", path); | ||
410 | exit(1); | ||
411 | } | ||
412 | |||
413 | #if 0 | ||
414 | struct stat s2; | ||
415 | if (stat(dest, &s2) == 0) { | ||
416 | printf("%s\t", dest); | ||
417 | printf((S_ISDIR(s.st_mode)) ? "d" : "-"); | ||
418 | printf((s.st_mode & S_IRUSR) ? "r" : "-"); | ||
419 | printf((s.st_mode & S_IWUSR) ? "w" : "-"); | ||
420 | printf((s.st_mode & S_IXUSR) ? "x" : "-"); | ||
421 | printf((s.st_mode & S_IRGRP) ? "r" : "-"); | ||
422 | printf((s.st_mode & S_IWGRP) ? "w" : "-"); | ||
423 | printf((s.st_mode & S_IXGRP) ? "x" : "-"); | ||
424 | printf((s.st_mode & S_IROTH) ? "r" : "-"); | ||
425 | printf((s.st_mode & S_IWOTH) ? "w" : "-"); | ||
426 | printf((s.st_mode & S_IXOTH) ? "x" : "-"); | ||
427 | printf("\n"); | ||
428 | } | ||
429 | #endif | ||
430 | |||
431 | fs_logger2("clone", path); | ||
432 | } | ||
433 | |||
434 | free(dest); | ||
435 | return(0); | ||
436 | } | ||
437 | |||
438 | static void duplicate(char *name) { | ||
439 | char *fname = check_dir_or_file(name); | ||
440 | |||
441 | if (arg_debug) | ||
442 | printf("Private home: duplicating %s\n", fname); | ||
443 | assert(strncmp(fname, cfg.homedir, strlen(cfg.homedir)) == 0); | ||
444 | |||
445 | struct stat s; | ||
446 | if (stat(fname, &s) == -1) { | ||
447 | free(fname); | ||
448 | return; | ||
449 | } | ||
450 | |||
451 | if(nftw(fname, fs_copydir, 1, FTW_PHYS) != 0) { | ||
452 | fprintf(stderr, "Error: unable to copy template dir\n"); | ||
453 | exit(1); | ||
454 | } | ||
455 | fs_logger_print(); // save the current log | ||
456 | |||
457 | free(fname); | ||
458 | } | ||
459 | |||
460 | |||
461 | |||
462 | static char *check_dir_or_file(const char *name) { | ||
463 | assert(name); | ||
464 | struct stat s; | ||
465 | |||
466 | // basic checks | ||
467 | invalid_filename(name); | ||
468 | |||
469 | if (arg_debug) | ||
470 | printf("Private home: checking %s\n", name); | ||
471 | |||
472 | // expand home directory | ||
473 | char *fname = expand_home(name, cfg.homedir); | ||
474 | if (!fname) { | ||
475 | fprintf(stderr, "Error: file %s not found.\n", name); | ||
476 | exit(1); | ||
477 | } | ||
478 | |||
479 | // If it doesn't start with '/', it must be relative to homedir | ||
480 | if (fname[0] != '/') { | ||
481 | char* tmp; | ||
482 | if (asprintf(&tmp, "%s/%s", cfg.homedir, fname) == -1) | ||
483 | errExit("asprintf"); | ||
484 | free(fname); | ||
485 | fname = tmp; | ||
486 | } | ||
487 | |||
488 | // check the file is in user home directory | ||
489 | char *rname = realpath(fname, NULL); | ||
490 | if (!rname) { | ||
491 | fprintf(stderr, "Error: invalid file %s\n", name); | ||
492 | exit(1); | ||
493 | } | ||
494 | if (strncmp(rname, cfg.homedir, strlen(cfg.homedir)) != 0) { | ||
495 | fprintf(stderr, "Error: file %s is not in user home directory\n", name); | ||
496 | exit(1); | ||
497 | } | ||
498 | |||
499 | // a full home directory is not allowed | ||
500 | if (strcmp(rname, cfg.homedir) == 0) { | ||
501 | fprintf(stderr, "Error: invalid directory %s\n", rname); | ||
502 | exit(1); | ||
503 | } | ||
504 | |||
505 | // only top files and directories in user home are allowed | ||
506 | char *ptr = rname + strlen(cfg.homedir); | ||
507 | if (*ptr == '\0') { | ||
508 | fprintf(stderr, "Error: invalid file %s\n", name); | ||
509 | exit(1); | ||
510 | } | ||
511 | ptr++; | ||
512 | ptr = strchr(ptr, '/'); | ||
513 | if (ptr) { | ||
514 | if (*ptr != '\0') { | ||
515 | fprintf(stderr, "Error: only top files and directories in user home are allowed\n"); | ||
516 | exit(1); | ||
517 | } | ||
518 | } | ||
519 | |||
520 | if (stat(fname, &s) == -1) { | ||
521 | fprintf(stderr, "Error: file %s not found.\n", fname); | ||
522 | exit(1); | ||
523 | } | ||
524 | |||
525 | // check uid | ||
526 | uid_t uid = getuid(); | ||
527 | gid_t gid = getgid(); | ||
528 | if (s.st_uid != uid || s.st_gid != gid) { | ||
529 | fprintf(stderr, "Error: only files or directories created by the current user are allowed.\n"); | ||
530 | exit(1); | ||
531 | } | ||
532 | |||
533 | // dir or regular file | ||
534 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode)) { | ||
535 | free(fname); | ||
536 | return rname; // regular exit from the function | ||
537 | } | ||
538 | |||
539 | fprintf(stderr, "Error: invalid file type, %s.\n", fname); | ||
540 | exit(1); | ||
541 | } | ||
542 | |||
543 | |||
544 | // check directory list specified by user (--private-home option) - exit if it fails | ||
545 | void fs_check_home_list(void) { | ||
546 | if (strstr(cfg.home_private_keep, "..")) { | ||
547 | fprintf(stderr, "Error: invalid private-home list\n"); | ||
548 | exit(1); | ||
549 | } | ||
550 | |||
551 | char *dlist = strdup(cfg.home_private_keep); | ||
552 | if (!dlist) | ||
553 | errExit("strdup"); | ||
554 | |||
555 | char *ptr = strtok(dlist, ","); | ||
556 | char *tmp = check_dir_or_file(ptr); | ||
557 | free(tmp); | ||
558 | |||
559 | while ((ptr = strtok(NULL, ",")) != NULL) { | ||
560 | tmp = check_dir_or_file(ptr); | ||
561 | free(tmp); | ||
562 | } | ||
563 | |||
564 | free(dlist); | ||
445 | } | 565 | } |
446 | 566 | ||
447 | 567 | ||
568 | |||
569 | // private mode (--private-home=list): | ||
570 | // mount homedir on top of /home/user, | ||
571 | // tmpfs on top of /root in nonroot mode, | ||
572 | // tmpfs on top of /tmp in root mode, | ||
573 | // set skel files, | ||
574 | // restore .Xauthority | ||
575 | void fs_private_home_list(void) { | ||
576 | char *homedir = cfg.homedir; | ||
577 | char *private_list = cfg.home_private_keep; | ||
578 | assert(homedir); | ||
579 | assert(private_list); | ||
580 | |||
581 | int xflag = store_xauthority(); | ||
582 | int aflag = store_asoundrc(); | ||
583 | |||
584 | uid_t u = firejail_uid; | ||
585 | gid_t g = firejail_gid; | ||
586 | struct stat s; | ||
587 | if (stat(homedir, &s) == -1) { | ||
588 | fprintf(stderr, "Error: cannot find user home directory\n"); | ||
589 | exit(1); | ||
590 | } | ||
591 | |||
592 | // create /tmp/firejail/mnt/home directory | ||
593 | fs_build_mnt_dir(); | ||
594 | int rv = mkdir(RUN_HOME_DIR, 0755); | ||
595 | if (rv == -1) | ||
596 | errExit("mkdir"); | ||
597 | if (chown(RUN_HOME_DIR, u, g) < 0) | ||
598 | errExit("chown"); | ||
599 | if (chmod(RUN_HOME_DIR, 0755) < 0) | ||
600 | errExit("chmod"); | ||
601 | ASSERT_PERMS(RUN_HOME_DIR, u, g, 0755); | ||
602 | |||
603 | fs_logger_print(); // save the current log | ||
604 | |||
605 | // copy the list of files in the new home directory | ||
606 | // using a new child process without root privileges | ||
607 | pid_t child = fork(); | ||
608 | if (child < 0) | ||
609 | errExit("fork"); | ||
610 | if (child == 0) { | ||
611 | if (arg_debug) | ||
612 | printf("Copying files in the new home:\n"); | ||
613 | |||
614 | // drop privileges | ||
615 | if (setgroups(0, NULL) < 0) | ||
616 | errExit("setgroups"); | ||
617 | if (setgid(getgid()) < 0) | ||
618 | errExit("setgid/getgid"); | ||
619 | if (setuid(getuid()) < 0) | ||
620 | errExit("setuid/getuid"); | ||
621 | |||
622 | // copy the list of files in the new home directory | ||
623 | char *dlist = strdup(cfg.home_private_keep); | ||
624 | if (!dlist) | ||
625 | errExit("strdup"); | ||
626 | |||
627 | char *ptr = strtok(dlist, ","); | ||
628 | duplicate(ptr); | ||
629 | while ((ptr = strtok(NULL, ",")) != NULL) | ||
630 | duplicate(ptr); | ||
631 | |||
632 | if (!arg_quiet) { | ||
633 | if (size_limit_reached) | ||
634 | fprintf(stderr, "Warning: private-home copy limit of %u MB reached, not all the files were copied\n", | ||
635 | PRIVATE_COPY_LIMIT / (1024 *1024)); | ||
636 | else | ||
637 | printf("Private home: %u files, total size %u bytes\n", file_cnt, size_cnt); | ||
638 | } | ||
639 | |||
640 | fs_logger_print(); // save the current log | ||
641 | free(dlist); | ||
642 | exit(0); | ||
643 | } | ||
644 | // wait for the child to finish | ||
645 | waitpid(child, NULL, 0); | ||
646 | |||
647 | if (arg_debug) | ||
648 | printf("Mount-bind %s on top of %s\n", RUN_HOME_DIR, homedir); | ||
649 | |||
650 | if (mount(RUN_HOME_DIR, homedir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
651 | errExit("mount bind"); | ||
652 | |||
653 | if (u != 0) { | ||
654 | // mask /root | ||
655 | if (arg_debug) | ||
656 | printf("Mounting a new /root directory\n"); | ||
657 | if (mount("tmpfs", "/root", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=700,gid=0") < 0) | ||
658 | errExit("mounting home directory"); | ||
659 | } | ||
660 | else { | ||
661 | // mask /home | ||
662 | if (arg_debug) | ||
663 | printf("Mounting a new /home directory\n"); | ||
664 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
665 | errExit("mounting home directory"); | ||
666 | } | ||
667 | |||
668 | skel(homedir, u, g); | ||
669 | if (xflag) | ||
670 | copy_xauthority(); | ||
671 | if (aflag) | ||
672 | copy_asoundrc(); | ||
673 | } | ||