diff options
Diffstat (limited to 'src/firejail/util.c')
-rw-r--r-- | src/firejail/util.c | 227 |
1 files changed, 186 insertions, 41 deletions
diff --git a/src/firejail/util.c b/src/firejail/util.c index dc9fe2449..b8643ff60 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c | |||
@@ -394,11 +394,11 @@ void touch_file_as_user(const char *fname, mode_t mode) { | |||
394 | // drop privileges | 394 | // drop privileges |
395 | drop_privs(0); | 395 | drop_privs(0); |
396 | 396 | ||
397 | FILE *fp = fopen(fname, "wx"); | 397 | int fd = open(fname, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR); |
398 | if (fp) { | 398 | if (fd > -1) { |
399 | fprintf(fp, "\n"); | 399 | int err = fchmod(fd, mode); |
400 | SET_PERMS_STREAM(fp, -1, -1, mode); | 400 | (void) err; |
401 | fclose(fp); | 401 | close(fd); |
402 | } | 402 | } |
403 | else | 403 | else |
404 | fwarning("cannot create %s\n", fname); | 404 | fwarning("cannot create %s\n", fname); |
@@ -417,6 +417,13 @@ int is_dir(const char *fname) { | |||
417 | if (*fname == '\0') | 417 | if (*fname == '\0') |
418 | return 0; | 418 | return 0; |
419 | 419 | ||
420 | int called_as_root = 0; | ||
421 | if (geteuid() == 0) | ||
422 | called_as_root = 1; | ||
423 | |||
424 | if (called_as_root) | ||
425 | EUID_USER(); | ||
426 | |||
420 | // if fname doesn't end in '/', add one | 427 | // if fname doesn't end in '/', add one |
421 | int rv; | 428 | int rv; |
422 | struct stat s; | 429 | struct stat s; |
@@ -432,6 +439,9 @@ int is_dir(const char *fname) { | |||
432 | free(tmp); | 439 | free(tmp); |
433 | } | 440 | } |
434 | 441 | ||
442 | if (called_as_root) | ||
443 | EUID_ROOT(); | ||
444 | |||
435 | if (rv == -1) | 445 | if (rv == -1) |
436 | return 0; | 446 | return 0; |
437 | 447 | ||
@@ -447,6 +457,14 @@ int is_link(const char *fname) { | |||
447 | if (*fname == '\0') | 457 | if (*fname == '\0') |
448 | return 0; | 458 | return 0; |
449 | 459 | ||
460 | int called_as_root = 0; | ||
461 | if (geteuid() == 0) | ||
462 | called_as_root = 1; | ||
463 | |||
464 | if (called_as_root) | ||
465 | EUID_USER(); | ||
466 | |||
467 | // remove trailing '/' if any | ||
450 | char *tmp = strdup(fname); | 468 | char *tmp = strdup(fname); |
451 | if (!tmp) | 469 | if (!tmp) |
452 | errExit("strdup"); | 470 | errExit("strdup"); |
@@ -456,9 +474,66 @@ int is_link(const char *fname) { | |||
456 | ssize_t rv = readlink(tmp, &c, 1); | 474 | ssize_t rv = readlink(tmp, &c, 1); |
457 | free(tmp); | 475 | free(tmp); |
458 | 476 | ||
477 | if (called_as_root) | ||
478 | EUID_ROOT(); | ||
479 | |||
459 | return (rv != -1); | 480 | return (rv != -1); |
460 | } | 481 | } |
461 | 482 | ||
483 | char *realpath_as_user(const char *fname) { | ||
484 | assert(fname); | ||
485 | |||
486 | int called_as_root = 0; | ||
487 | if (geteuid() == 0) | ||
488 | called_as_root = 1; | ||
489 | |||
490 | if (called_as_root) | ||
491 | EUID_USER(); | ||
492 | |||
493 | char *rv = realpath(fname, NULL); | ||
494 | |||
495 | if (called_as_root) | ||
496 | EUID_ROOT(); | ||
497 | |||
498 | return rv; | ||
499 | } | ||
500 | |||
501 | int stat_as_user(const char *fname, struct stat *s) { | ||
502 | assert(fname); | ||
503 | |||
504 | int called_as_root = 0; | ||
505 | if (geteuid() == 0) | ||
506 | called_as_root = 1; | ||
507 | |||
508 | if (called_as_root) | ||
509 | EUID_USER(); | ||
510 | |||
511 | int rv = stat(fname, s); | ||
512 | |||
513 | if (called_as_root) | ||
514 | EUID_ROOT(); | ||
515 | |||
516 | return rv; | ||
517 | } | ||
518 | |||
519 | int lstat_as_user(const char *fname, struct stat *s) { | ||
520 | assert(fname); | ||
521 | |||
522 | int called_as_root = 0; | ||
523 | if (geteuid() == 0) | ||
524 | called_as_root = 1; | ||
525 | |||
526 | if (called_as_root) | ||
527 | EUID_USER(); | ||
528 | |||
529 | int rv = lstat(fname, s); | ||
530 | |||
531 | if (called_as_root) | ||
532 | EUID_ROOT(); | ||
533 | |||
534 | return rv; | ||
535 | } | ||
536 | |||
462 | // remove all slashes and single dots from the end of a path | 537 | // remove all slashes and single dots from the end of a path |
463 | // for example /foo/bar///././. -> /foo/bar | 538 | // for example /foo/bar///././. -> /foo/bar |
464 | void trim_trailing_slash_or_dot(char *path) { | 539 | void trim_trailing_slash_or_dot(char *path) { |
@@ -891,35 +966,37 @@ static int remove_callback(const char *fpath, const struct stat *sb, int typefla | |||
891 | 966 | ||
892 | int remove_overlay_directory(void) { | 967 | int remove_overlay_directory(void) { |
893 | EUID_ASSERT(); | 968 | EUID_ASSERT(); |
894 | struct stat s; | ||
895 | sleep(1); | 969 | sleep(1); |
896 | 970 | ||
897 | char *path; | 971 | char *path; |
898 | if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1) | 972 | if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1) |
899 | errExit("asprintf"); | 973 | errExit("asprintf"); |
900 | 974 | ||
901 | if (lstat(path, &s) == 0) { | 975 | if (access(path, F_OK) == 0) { |
902 | // deal with obvious problems such as symlinks and root ownership | ||
903 | if (!S_ISDIR(s.st_mode)) { | ||
904 | if (S_ISLNK(s.st_mode)) | ||
905 | fprintf(stderr, "Error: %s is a symbolic link\n", path); | ||
906 | else | ||
907 | fprintf(stderr, "Error: %s is not a directory\n", path); | ||
908 | exit(1); | ||
909 | } | ||
910 | if (s.st_uid != getuid()) { | ||
911 | fprintf(stderr, "Error: %s is not owned by the current user\n", path); | ||
912 | exit(1); | ||
913 | } | ||
914 | |||
915 | pid_t child = fork(); | 976 | pid_t child = fork(); |
916 | if (child < 0) | 977 | if (child < 0) |
917 | errExit("fork"); | 978 | errExit("fork"); |
918 | if (child == 0) { | 979 | if (child == 0) { |
919 | // open ~/.firejail, fails if there is any symlink | 980 | // open ~/.firejail |
920 | int fd = safer_openat(-1, path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | 981 | int fd = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
921 | if (fd == -1) | 982 | if (fd == -1) { |
922 | errExit("safer_openat"); | 983 | fprintf(stderr, "Error: cannot open %s\n", path); |
984 | _exit(1); | ||
985 | } | ||
986 | struct stat s; | ||
987 | if (fstat(fd, &s) == -1) | ||
988 | errExit("fstat"); | ||
989 | if (!S_ISDIR(s.st_mode)) { | ||
990 | if (S_ISLNK(s.st_mode)) | ||
991 | fprintf(stderr, "Error: %s is a symbolic link\n", path); | ||
992 | else | ||
993 | fprintf(stderr, "Error: %s is not a directory\n", path); | ||
994 | _exit(1); | ||
995 | } | ||
996 | if (s.st_uid != getuid()) { | ||
997 | fprintf(stderr, "Error: %s is not owned by the current user\n", path); | ||
998 | _exit(1); | ||
999 | } | ||
923 | // chdir to ~/.firejail | 1000 | // chdir to ~/.firejail |
924 | if (fchdir(fd) == -1) | 1001 | if (fchdir(fd) == -1) |
925 | errExit("fchdir"); | 1002 | errExit("fchdir"); |
@@ -942,7 +1019,7 @@ int remove_overlay_directory(void) { | |||
942 | // wait for the child to finish | 1019 | // wait for the child to finish |
943 | waitpid(child, NULL, 0); | 1020 | waitpid(child, NULL, 0); |
944 | // check if ~/.firejail was deleted | 1021 | // check if ~/.firejail was deleted |
945 | if (stat(path, &s) == 0) | 1022 | if (access(path, F_OK) == 0) |
946 | return 1; | 1023 | return 1; |
947 | } | 1024 | } |
948 | return 0; | 1025 | return 0; |
@@ -975,9 +1052,8 @@ void flush_stdin(void) { | |||
975 | int create_empty_dir_as_user(const char *dir, mode_t mode) { | 1052 | int create_empty_dir_as_user(const char *dir, mode_t mode) { |
976 | assert(dir); | 1053 | assert(dir); |
977 | mode &= 07777; | 1054 | mode &= 07777; |
978 | struct stat s; | ||
979 | 1055 | ||
980 | if (stat(dir, &s)) { | 1056 | if (access(dir, F_OK) != 0) { |
981 | if (arg_debug) | 1057 | if (arg_debug) |
982 | printf("Creating empty %s directory\n", dir); | 1058 | printf("Creating empty %s directory\n", dir); |
983 | pid_t child = fork(); | 1059 | pid_t child = fork(); |
@@ -988,8 +1064,8 @@ int create_empty_dir_as_user(const char *dir, mode_t mode) { | |||
988 | drop_privs(0); | 1064 | drop_privs(0); |
989 | 1065 | ||
990 | if (mkdir(dir, mode) == 0) { | 1066 | if (mkdir(dir, mode) == 0) { |
991 | if (chmod(dir, mode) == -1) | 1067 | int err = chmod(dir, mode); |
992 | {;} // do nothing | 1068 | (void) err; |
993 | } | 1069 | } |
994 | else if (arg_debug) | 1070 | else if (arg_debug) |
995 | printf("Directory %s not created: %s\n", dir, strerror(errno)); | 1071 | printf("Directory %s not created: %s\n", dir, strerror(errno)); |
@@ -999,7 +1075,7 @@ int create_empty_dir_as_user(const char *dir, mode_t mode) { | |||
999 | _exit(0); | 1075 | _exit(0); |
1000 | } | 1076 | } |
1001 | waitpid(child, NULL, 0); | 1077 | waitpid(child, NULL, 0); |
1002 | if (stat(dir, &s) == 0) | 1078 | if (access(dir, F_OK) == 0) |
1003 | return 1; | 1079 | return 1; |
1004 | } | 1080 | } |
1005 | return 0; | 1081 | return 0; |
@@ -1110,20 +1186,35 @@ unsigned extract_timeout(const char *str) { | |||
1110 | } | 1186 | } |
1111 | 1187 | ||
1112 | void disable_file_or_dir(const char *fname) { | 1188 | void disable_file_or_dir(const char *fname) { |
1189 | assert(fname); | ||
1190 | assert(geteuid() == 0); | ||
1191 | |||
1192 | EUID_USER(); | ||
1193 | int fd = open(fname, O_PATH|O_CLOEXEC); | ||
1194 | EUID_ROOT(); | ||
1195 | if (fd < 0) | ||
1196 | return; | ||
1197 | |||
1113 | struct stat s; | 1198 | struct stat s; |
1114 | if (stat(fname, &s) != -1) { | 1199 | if (fstat(fd, &s) < 0) { // FUSE |
1115 | if (arg_debug) | 1200 | if (errno != EACCES) |
1116 | printf("blacklist %s\n", fname); | 1201 | errExit("fstat"); |
1117 | if (is_dir(fname)) { | 1202 | close(fd); |
1118 | if (mount(RUN_RO_DIR, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | 1203 | return; |
1204 | } | ||
1205 | |||
1206 | if (arg_debug) | ||
1207 | printf("blacklist %s\n", fname); | ||
1208 | if (S_ISDIR(s.st_mode)) { | ||
1209 | if (bind_mount_path_to_fd(RUN_RO_DIR, fd) < 0) | ||
1119 | errExit("disable directory"); | 1210 | errExit("disable directory"); |
1120 | } | ||
1121 | else { | ||
1122 | if (mount(RUN_RO_FILE, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | ||
1123 | errExit("disable file"); | ||
1124 | } | ||
1125 | fs_logger2("blacklist", fname); | ||
1126 | } | 1211 | } |
1212 | else { | ||
1213 | if (bind_mount_path_to_fd(RUN_RO_FILE, fd) < 0) | ||
1214 | errExit("disable file"); | ||
1215 | } | ||
1216 | close(fd); | ||
1217 | fs_logger2("blacklist", fname); | ||
1127 | } | 1218 | } |
1128 | 1219 | ||
1129 | void disable_file_path(const char *path, const char *file) { | 1220 | void disable_file_path(const char *path, const char *file) { |
@@ -1210,6 +1301,60 @@ int safer_openat(int dirfd, const char *path, int flags) { | |||
1210 | return fd; | 1301 | return fd; |
1211 | } | 1302 | } |
1212 | 1303 | ||
1304 | int remount_by_fd(int dst, unsigned long mountflags) { | ||
1305 | char *proc; | ||
1306 | if (asprintf(&proc, "/proc/self/fd/%d", dst) < 0) | ||
1307 | errExit("asprintf"); | ||
1308 | |||
1309 | int rv = mount(NULL, proc, NULL, mountflags|MS_BIND|MS_REMOUNT, NULL); | ||
1310 | if (rv < 0 && arg_debug) | ||
1311 | printf("Failed mount: %s\n", strerror(errno)); | ||
1312 | |||
1313 | free(proc); | ||
1314 | return rv; | ||
1315 | } | ||
1316 | |||
1317 | int bind_mount_by_fd(int src, int dst) { | ||
1318 | char *proc_src, *proc_dst; | ||
1319 | if (asprintf(&proc_src, "/proc/self/fd/%d", src) < 0 || | ||
1320 | asprintf(&proc_dst, "/proc/self/fd/%d", dst) < 0) | ||
1321 | errExit("asprintf"); | ||
1322 | |||
1323 | int rv = mount(proc_src, proc_dst, NULL, MS_BIND|MS_REC, NULL); | ||
1324 | if (rv < 0 && arg_debug) | ||
1325 | printf("Failed mount: %s\n", strerror(errno)); | ||
1326 | |||
1327 | free(proc_src); | ||
1328 | free(proc_dst); | ||
1329 | return rv; | ||
1330 | } | ||
1331 | |||
1332 | int bind_mount_fd_to_path(int src, const char *destname) { | ||
1333 | char *proc; | ||
1334 | if (asprintf(&proc, "/proc/self/fd/%d", src) < 0) | ||
1335 | errExit("asprintf"); | ||
1336 | |||
1337 | int rv = mount(proc, destname, NULL, MS_BIND|MS_REC, NULL); | ||
1338 | if (rv < 0 && arg_debug) | ||
1339 | printf("Failed mount: %s\n", strerror(errno)); | ||
1340 | |||
1341 | free(proc); | ||
1342 | return rv; | ||
1343 | } | ||
1344 | |||
1345 | int bind_mount_path_to_fd(const char *srcname, int dst) { | ||
1346 | char *proc; | ||
1347 | if (asprintf(&proc, "/proc/self/fd/%d", dst) < 0) | ||
1348 | errExit("asprintf"); | ||
1349 | |||
1350 | int rv = mount(srcname, proc, NULL, MS_BIND|MS_REC, NULL); | ||
1351 | if (rv < 0 && arg_debug) | ||
1352 | printf("Failed mount: %s\n", strerror(errno)); | ||
1353 | |||
1354 | free(proc); | ||
1355 | return rv; | ||
1356 | } | ||
1357 | |||
1213 | int has_handler(pid_t pid, int signal) { | 1358 | int has_handler(pid_t pid, int signal) { |
1214 | if (signal > 0 && signal <= SIGRTMAX) { | 1359 | if (signal > 0 && signal <= SIGRTMAX) { |
1215 | char *fname; | 1360 | char *fname; |