diff options
author | Reiner Herrmann <reiner@reiner-h.de> | 2021-06-21 23:10:09 +0200 |
---|---|---|
committer | Reiner Herrmann <reiner@reiner-h.de> | 2021-06-21 23:10:09 +0200 |
commit | 0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2 (patch) | |
tree | 0875693a6ceef54818511972601d587a09a1aab4 /src/firejail/util.c | |
parent | style: grammer and codestyle improvements (diff) | |
parent | creating alpine.profile (#4350) (diff) | |
download | firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.tar.gz firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.tar.zst firejail-0f0325459e211ff31895ed7cbbbaae6c2c6ae9a2.zip |
Merge branch 'master' into kuesji/master
Diffstat (limited to 'src/firejail/util.c')
-rw-r--r-- | src/firejail/util.c | 258 |
1 files changed, 204 insertions, 54 deletions
diff --git a/src/firejail/util.c b/src/firejail/util.c index e2a4a1d90..68b76b8e8 100644 --- a/src/firejail/util.c +++ b/src/firejail/util.c | |||
@@ -44,6 +44,10 @@ | |||
44 | #include <linux/openat2.h> | 44 | #include <linux/openat2.h> |
45 | #endif | 45 | #endif |
46 | 46 | ||
47 | #ifdef HAVE_GCOV | ||
48 | #include <gcov.h> | ||
49 | #endif | ||
50 | |||
47 | #define MAX_GROUPS 1024 | 51 | #define MAX_GROUPS 1024 |
48 | #define MAXBUF 4098 | 52 | #define MAXBUF 4098 |
49 | #define EMPTY_STRING ("") | 53 | #define EMPTY_STRING ("") |
@@ -435,11 +439,11 @@ void touch_file_as_user(const char *fname, mode_t mode) { | |||
435 | // drop privileges | 439 | // drop privileges |
436 | drop_privs(0); | 440 | drop_privs(0); |
437 | 441 | ||
438 | FILE *fp = fopen(fname, "wx"); | 442 | int fd = open(fname, O_RDONLY|O_CREAT|O_EXCL|O_CLOEXEC, S_IRUSR | S_IWUSR); |
439 | if (fp) { | 443 | if (fd > -1) { |
440 | fprintf(fp, "\n"); | 444 | int err = fchmod(fd, mode); |
441 | SET_PERMS_STREAM(fp, -1, -1, mode); | 445 | (void) err; |
442 | fclose(fp); | 446 | close(fd); |
443 | } | 447 | } |
444 | else | 448 | else |
445 | fwarning("cannot create %s\n", fname); | 449 | fwarning("cannot create %s\n", fname); |
@@ -458,6 +462,13 @@ int is_dir(const char *fname) { | |||
458 | if (*fname == '\0') | 462 | if (*fname == '\0') |
459 | return 0; | 463 | return 0; |
460 | 464 | ||
465 | int called_as_root = 0; | ||
466 | if (geteuid() == 0) | ||
467 | called_as_root = 1; | ||
468 | |||
469 | if (called_as_root) | ||
470 | EUID_USER(); | ||
471 | |||
461 | // if fname doesn't end in '/', add one | 472 | // if fname doesn't end in '/', add one |
462 | int rv; | 473 | int rv; |
463 | struct stat s; | 474 | struct stat s; |
@@ -473,6 +484,9 @@ int is_dir(const char *fname) { | |||
473 | free(tmp); | 484 | free(tmp); |
474 | } | 485 | } |
475 | 486 | ||
487 | if (called_as_root) | ||
488 | EUID_ROOT(); | ||
489 | |||
476 | if (rv == -1) | 490 | if (rv == -1) |
477 | return 0; | 491 | return 0; |
478 | 492 | ||
@@ -488,18 +502,83 @@ int is_link(const char *fname) { | |||
488 | if (*fname == '\0') | 502 | if (*fname == '\0') |
489 | return 0; | 503 | return 0; |
490 | 504 | ||
491 | char *dup = strdup(fname); | 505 | int called_as_root = 0; |
492 | if (!dup) | 506 | if (geteuid() == 0) |
507 | called_as_root = 1; | ||
508 | |||
509 | if (called_as_root) | ||
510 | EUID_USER(); | ||
511 | |||
512 | // remove trailing '/' if any | ||
513 | char *tmp = strdup(fname); | ||
514 | if (!tmp) | ||
493 | errExit("strdup"); | 515 | errExit("strdup"); |
494 | trim_trailing_slash_or_dot(dup); | 516 | trim_trailing_slash_or_dot(tmp); |
495 | 517 | ||
496 | char c; | 518 | char c; |
497 | ssize_t rv = readlink(dup, &c, 1); | 519 | ssize_t rv = readlink(tmp, &c, 1); |
520 | free(tmp); | ||
521 | |||
522 | if (called_as_root) | ||
523 | EUID_ROOT(); | ||
498 | 524 | ||
499 | free(dup); | ||
500 | return (rv != -1); | 525 | return (rv != -1); |
501 | } | 526 | } |
502 | 527 | ||
528 | char *realpath_as_user(const char *fname) { | ||
529 | assert(fname); | ||
530 | |||
531 | int called_as_root = 0; | ||
532 | if (geteuid() == 0) | ||
533 | called_as_root = 1; | ||
534 | |||
535 | if (called_as_root) | ||
536 | EUID_USER(); | ||
537 | |||
538 | char *rv = realpath(fname, NULL); | ||
539 | |||
540 | if (called_as_root) | ||
541 | EUID_ROOT(); | ||
542 | |||
543 | return rv; | ||
544 | } | ||
545 | |||
546 | int stat_as_user(const char *fname, struct stat *s) { | ||
547 | assert(fname); | ||
548 | |||
549 | int called_as_root = 0; | ||
550 | if (geteuid() == 0) | ||
551 | called_as_root = 1; | ||
552 | |||
553 | if (called_as_root) | ||
554 | EUID_USER(); | ||
555 | |||
556 | int rv = stat(fname, s); | ||
557 | |||
558 | if (called_as_root) | ||
559 | EUID_ROOT(); | ||
560 | |||
561 | return rv; | ||
562 | } | ||
563 | |||
564 | int lstat_as_user(const char *fname, struct stat *s) { | ||
565 | assert(fname); | ||
566 | |||
567 | int called_as_root = 0; | ||
568 | if (geteuid() == 0) | ||
569 | called_as_root = 1; | ||
570 | |||
571 | if (called_as_root) | ||
572 | EUID_USER(); | ||
573 | |||
574 | int rv = lstat(fname, s); | ||
575 | |||
576 | if (called_as_root) | ||
577 | EUID_ROOT(); | ||
578 | |||
579 | return rv; | ||
580 | } | ||
581 | |||
503 | // remove all slashes and single dots from the end of a path | 582 | // remove all slashes and single dots from the end of a path |
504 | // for example /foo/bar///././. -> /foo/bar | 583 | // for example /foo/bar///././. -> /foo/bar |
505 | void trim_trailing_slash_or_dot(char *path) { | 584 | void trim_trailing_slash_or_dot(char *path) { |
@@ -688,9 +767,11 @@ int find_child(pid_t parent, pid_t *child) { | |||
688 | if (parent == atoi(ptr)) { | 767 | if (parent == atoi(ptr)) { |
689 | // we don't want /usr/bin/xdg-dbus-proxy! | 768 | // we don't want /usr/bin/xdg-dbus-proxy! |
690 | char *cmdline = pid_proc_cmdline(pid); | 769 | char *cmdline = pid_proc_cmdline(pid); |
691 | if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) | 770 | if (cmdline) { |
692 | *child = pid; | 771 | if (strncmp(cmdline, XDG_DBUS_PROXY_PATH, strlen(XDG_DBUS_PROXY_PATH)) != 0) |
693 | free(cmdline); | 772 | *child = pid; |
773 | free(cmdline); | ||
774 | } | ||
694 | } | 775 | } |
695 | break; // stop reading the file | 776 | break; // stop reading the file |
696 | } | 777 | } |
@@ -930,35 +1011,37 @@ static int remove_callback(const char *fpath, const struct stat *sb, int typefla | |||
930 | 1011 | ||
931 | int remove_overlay_directory(void) { | 1012 | int remove_overlay_directory(void) { |
932 | EUID_ASSERT(); | 1013 | EUID_ASSERT(); |
933 | struct stat s; | ||
934 | sleep(1); | 1014 | sleep(1); |
935 | 1015 | ||
936 | char *path; | 1016 | char *path; |
937 | if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1) | 1017 | if (asprintf(&path, "%s/.firejail", cfg.homedir) == -1) |
938 | errExit("asprintf"); | 1018 | errExit("asprintf"); |
939 | 1019 | ||
940 | if (lstat(path, &s) == 0) { | 1020 | if (access(path, F_OK) == 0) { |
941 | // deal with obvious problems such as symlinks and root ownership | ||
942 | if (!S_ISDIR(s.st_mode)) { | ||
943 | if (S_ISLNK(s.st_mode)) | ||
944 | fprintf(stderr, "Error: %s is a symbolic link\n", path); | ||
945 | else | ||
946 | fprintf(stderr, "Error: %s is not a directory\n", path); | ||
947 | exit(1); | ||
948 | } | ||
949 | if (s.st_uid != getuid()) { | ||
950 | fprintf(stderr, "Error: %s is not owned by the current user\n", path); | ||
951 | exit(1); | ||
952 | } | ||
953 | |||
954 | pid_t child = fork(); | 1021 | pid_t child = fork(); |
955 | if (child < 0) | 1022 | if (child < 0) |
956 | errExit("fork"); | 1023 | errExit("fork"); |
957 | if (child == 0) { | 1024 | if (child == 0) { |
958 | // open ~/.firejail, fails if there is any symlink | 1025 | // open ~/.firejail |
959 | int fd = safer_openat(-1, path, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | 1026 | int fd = safer_openat(-1, path, O_PATH|O_NOFOLLOW|O_CLOEXEC); |
960 | if (fd == -1) | 1027 | if (fd == -1) { |
961 | errExit("safer_openat"); | 1028 | fprintf(stderr, "Error: cannot open %s\n", path); |
1029 | exit(1); | ||
1030 | } | ||
1031 | struct stat s; | ||
1032 | if (fstat(fd, &s) == -1) | ||
1033 | errExit("fstat"); | ||
1034 | if (!S_ISDIR(s.st_mode)) { | ||
1035 | if (S_ISLNK(s.st_mode)) | ||
1036 | fprintf(stderr, "Error: %s is a symbolic link\n", path); | ||
1037 | else | ||
1038 | fprintf(stderr, "Error: %s is not a directory\n", path); | ||
1039 | exit(1); | ||
1040 | } | ||
1041 | if (s.st_uid != getuid()) { | ||
1042 | fprintf(stderr, "Error: %s is not owned by the current user\n", path); | ||
1043 | exit(1); | ||
1044 | } | ||
962 | // chdir to ~/.firejail | 1045 | // chdir to ~/.firejail |
963 | if (fchdir(fd) == -1) | 1046 | if (fchdir(fd) == -1) |
964 | errExit("fchdir"); | 1047 | errExit("fchdir"); |
@@ -981,7 +1064,7 @@ int remove_overlay_directory(void) { | |||
981 | // wait for the child to finish | 1064 | // wait for the child to finish |
982 | waitpid(child, NULL, 0); | 1065 | waitpid(child, NULL, 0); |
983 | // check if ~/.firejail was deleted | 1066 | // check if ~/.firejail was deleted |
984 | if (stat(path, &s) == 0) | 1067 | if (access(path, F_OK) == 0) |
985 | return 1; | 1068 | return 1; |
986 | } | 1069 | } |
987 | return 0; | 1070 | return 0; |
@@ -1014,9 +1097,8 @@ void flush_stdin(void) { | |||
1014 | int create_empty_dir_as_user(const char *dir, mode_t mode) { | 1097 | int create_empty_dir_as_user(const char *dir, mode_t mode) { |
1015 | assert(dir); | 1098 | assert(dir); |
1016 | mode &= 07777; | 1099 | mode &= 07777; |
1017 | struct stat s; | ||
1018 | 1100 | ||
1019 | if (stat(dir, &s)) { | 1101 | if (access(dir, F_OK) != 0) { |
1020 | if (arg_debug) | 1102 | if (arg_debug) |
1021 | printf("Creating empty %s directory\n", dir); | 1103 | printf("Creating empty %s directory\n", dir); |
1022 | pid_t child = fork(); | 1104 | pid_t child = fork(); |
@@ -1027,8 +1109,8 @@ int create_empty_dir_as_user(const char *dir, mode_t mode) { | |||
1027 | drop_privs(0); | 1109 | drop_privs(0); |
1028 | 1110 | ||
1029 | if (mkdir(dir, mode) == 0) { | 1111 | if (mkdir(dir, mode) == 0) { |
1030 | if (chmod(dir, mode) == -1) | 1112 | int err = chmod(dir, mode); |
1031 | {;} // do nothing | 1113 | (void) err; |
1032 | } | 1114 | } |
1033 | else if (arg_debug) | 1115 | else if (arg_debug) |
1034 | printf("Directory %s not created: %s\n", dir, strerror(errno)); | 1116 | printf("Directory %s not created: %s\n", dir, strerror(errno)); |
@@ -1038,7 +1120,7 @@ int create_empty_dir_as_user(const char *dir, mode_t mode) { | |||
1038 | _exit(0); | 1120 | _exit(0); |
1039 | } | 1121 | } |
1040 | waitpid(child, NULL, 0); | 1122 | waitpid(child, NULL, 0); |
1041 | if (stat(dir, &s) == 0) | 1123 | if (access(dir, F_OK) == 0) |
1042 | return 1; | 1124 | return 1; |
1043 | } | 1125 | } |
1044 | return 0; | 1126 | return 0; |
@@ -1149,20 +1231,34 @@ unsigned extract_timeout(const char *str) { | |||
1149 | } | 1231 | } |
1150 | 1232 | ||
1151 | void disable_file_or_dir(const char *fname) { | 1233 | void disable_file_or_dir(const char *fname) { |
1234 | assert(fname); | ||
1235 | |||
1236 | EUID_USER(); | ||
1237 | int fd = open(fname, O_PATH|O_CLOEXEC); | ||
1238 | EUID_ROOT(); | ||
1239 | if (fd < 0) | ||
1240 | return; | ||
1241 | |||
1152 | struct stat s; | 1242 | struct stat s; |
1153 | if (stat(fname, &s) != -1) { | 1243 | if (fstat(fd, &s) < 0) { // FUSE |
1154 | if (arg_debug) | 1244 | if (errno != EACCES) |
1155 | printf("blacklist %s\n", fname); | 1245 | errExit("fstat"); |
1156 | if (is_dir(fname)) { | 1246 | close(fd); |
1157 | if (mount(RUN_RO_DIR, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | 1247 | return; |
1158 | errExit("disable directory"); | ||
1159 | } | ||
1160 | else { | ||
1161 | if (mount(RUN_RO_FILE, fname, "none", MS_BIND, "mode=400,gid=0") < 0) | ||
1162 | errExit("disable file"); | ||
1163 | } | ||
1164 | fs_logger2("blacklist", fname); | ||
1165 | } | 1248 | } |
1249 | |||
1250 | if (arg_debug) | ||
1251 | printf("blacklist %s\n", fname); | ||
1252 | if (S_ISDIR(s.st_mode)) { | ||
1253 | if (bind_mount_path_to_fd(RUN_RO_DIR, fd) < 0) | ||
1254 | errExit("disable directory"); | ||
1255 | } | ||
1256 | else { | ||
1257 | if (bind_mount_path_to_fd(RUN_RO_FILE, fd) < 0) | ||
1258 | errExit("disable file"); | ||
1259 | } | ||
1260 | close(fd); | ||
1261 | fs_logger2("blacklist", fname); | ||
1166 | } | 1262 | } |
1167 | 1263 | ||
1168 | void disable_file_path(const char *path, const char *file) { | 1264 | void disable_file_path(const char *path, const char *file) { |
@@ -1249,6 +1345,60 @@ int safer_openat(int dirfd, const char *path, int flags) { | |||
1249 | return fd; | 1345 | return fd; |
1250 | } | 1346 | } |
1251 | 1347 | ||
1348 | int remount_by_fd(int dst, unsigned long mountflags) { | ||
1349 | char *proc; | ||
1350 | if (asprintf(&proc, "/proc/self/fd/%d", dst) < 0) | ||
1351 | errExit("asprintf"); | ||
1352 | |||
1353 | int rv = mount(NULL, proc, NULL, mountflags|MS_BIND|MS_REMOUNT, NULL); | ||
1354 | if (rv < 0 && arg_debug) | ||
1355 | printf("Failed mount: %s\n", strerror(errno)); | ||
1356 | |||
1357 | free(proc); | ||
1358 | return rv; | ||
1359 | } | ||
1360 | |||
1361 | int bind_mount_by_fd(int src, int dst) { | ||
1362 | char *proc_src, *proc_dst; | ||
1363 | if (asprintf(&proc_src, "/proc/self/fd/%d", src) < 0 || | ||
1364 | asprintf(&proc_dst, "/proc/self/fd/%d", dst) < 0) | ||
1365 | errExit("asprintf"); | ||
1366 | |||
1367 | int rv = mount(proc_src, proc_dst, NULL, MS_BIND|MS_REC, NULL); | ||
1368 | if (rv < 0 && arg_debug) | ||
1369 | printf("Failed mount: %s\n", strerror(errno)); | ||
1370 | |||
1371 | free(proc_src); | ||
1372 | free(proc_dst); | ||
1373 | return rv; | ||
1374 | } | ||
1375 | |||
1376 | int bind_mount_fd_to_path(int src, const char *destname) { | ||
1377 | char *proc; | ||
1378 | if (asprintf(&proc, "/proc/self/fd/%d", src) < 0) | ||
1379 | errExit("asprintf"); | ||
1380 | |||
1381 | int rv = mount(proc, destname, NULL, MS_BIND|MS_REC, NULL); | ||
1382 | if (rv < 0 && arg_debug) | ||
1383 | printf("Failed mount: %s\n", strerror(errno)); | ||
1384 | |||
1385 | free(proc); | ||
1386 | return rv; | ||
1387 | } | ||
1388 | |||
1389 | int bind_mount_path_to_fd(const char *srcname, int dst) { | ||
1390 | char *proc; | ||
1391 | if (asprintf(&proc, "/proc/self/fd/%d", dst) < 0) | ||
1392 | errExit("asprintf"); | ||
1393 | |||
1394 | int rv = mount(srcname, proc, NULL, MS_BIND|MS_REC, NULL); | ||
1395 | if (rv < 0 && arg_debug) | ||
1396 | printf("Failed mount: %s\n", strerror(errno)); | ||
1397 | |||
1398 | free(proc); | ||
1399 | return rv; | ||
1400 | } | ||
1401 | |||
1252 | int has_handler(pid_t pid, int signal) { | 1402 | int has_handler(pid_t pid, int signal) { |
1253 | if (signal > 0 && signal <= SIGRTMAX) { | 1403 | if (signal > 0 && signal <= SIGRTMAX) { |
1254 | char *fname; | 1404 | char *fname; |
@@ -1358,14 +1508,14 @@ static int has_link(const char *dir) { | |||
1358 | return 0; | 1508 | return 0; |
1359 | } | 1509 | } |
1360 | 1510 | ||
1361 | void check_homedir(void) { | 1511 | void check_homedir(const char *dir) { |
1362 | assert(cfg.homedir); | 1512 | assert(dir); |
1363 | if (cfg.homedir[0] != '/') { | 1513 | if (dir[0] != '/') { |
1364 | fprintf(stderr, "Error: invalid user directory \"%s\"\n", cfg.homedir); | 1514 | fprintf(stderr, "Error: invalid user directory \"%s\"\n", cfg.homedir); |
1365 | exit(1); | 1515 | exit(1); |
1366 | } | 1516 | } |
1367 | // symlinks are rejected in many places | 1517 | // symlinks are rejected in many places |
1368 | if (has_link(cfg.homedir)) { | 1518 | if (has_link(dir)) { |
1369 | fprintf(stderr, "No full support for symbolic links in path of user directory.\n" | 1519 | fprintf(stderr, "No full support for symbolic links in path of user directory.\n" |
1370 | "Please provide resolved path in password database (/etc/passwd).\n\n"); | 1520 | "Please provide resolved path in password database (/etc/passwd).\n\n"); |
1371 | } | 1521 | } |