diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index f04235001d21a..576f7e83aa192 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1808,63 +1808,81 @@ char* umount_and_unlink_and_free(char *p) { return mfree(p); } -static int path_get_mount_info( +static int path_get_mount_info_at( + int dir_fd, const char *path, char **ret_fstype, char **ret_options) { _cleanup_(mnt_free_tablep) struct libmnt_table *table = NULL; - _cleanup_free_ char *fstype = NULL, *options = NULL; - struct libmnt_fs *fs; - int r; + _cleanup_(mnt_free_iterp) struct libmnt_iter *iter = NULL; + int r, mnt_id; - assert(path); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - table = mnt_new_table(); - if (!table) - return -ENOMEM; + r = path_get_mnt_id_at(dir_fd, path, &mnt_id); + if (r < 0) + return log_debug_errno(r, "Failed to get mount ID: %m"); - r = mnt_table_parse_mtab(table, /* filename = */ NULL); + r = libmount_parse("/proc/self/mountinfo", NULL, &table, &iter); if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse /proc/self/mountinfo: %m"); - fs = mnt_table_find_mountpoint(table, path, MNT_ITER_FORWARD); - if (!fs) - return -EINVAL; + for (;;) { + struct libmnt_fs *fs; - if (ret_fstype) { - fstype = strdup(strempty(mnt_fs_get_fstype(fs))); - if (!fstype) - return -ENOMEM; - } + r = mnt_table_next_fs(table, iter, &fs); + if (r == 1) + break; /* EOF */ + if (r < 0) + return log_debug_errno(r, "Failed to get next entry from /proc/self/mountinfo: %m"); - if (ret_options) { - options = strdup(strempty(mnt_fs_get_options(fs))); - if (!options) - return -ENOMEM; - } + if (mnt_fs_get_id(fs) != mnt_id) + continue; - if (ret_fstype) - *ret_fstype = TAKE_PTR(fstype); - if (ret_options) - *ret_options = TAKE_PTR(options); + _cleanup_free_ char *fstype = NULL, *options = NULL; - return 0; + if (ret_fstype) { + fstype = strdup(strempty(mnt_fs_get_fstype(fs))); + if (!fstype) + return log_oom_debug(); + } + + if (ret_options) { + options = strdup(strempty(mnt_fs_get_options(fs))); + if (!options) + return log_oom_debug(); + } + + if (ret_fstype) + *ret_fstype = TAKE_PTR(fstype); + if (ret_options) + *ret_options = TAKE_PTR(options); + + return 0; + } + + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Cannot find mount ID %i from /proc/self/mountinfo.", mnt_id); } -int path_is_network_fs_harder(const char *path) { - _cleanup_free_ char *fstype = NULL, *options = NULL; - int r, ret; +int path_is_network_fs_harder_at(int dir_fd, const char *path) { + _cleanup_close_ int fd = -EBADF; + int r; - assert(path); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - ret = path_is_network_fs(path); - if (ret > 0) - return true; + fd = xopenat(dir_fd, path, O_PATH | O_CLOEXEC | O_NOFOLLOW); + if (fd < 0) + return fd; + + r = fd_is_network_fs(fd); + if (r != 0) + return r; - r = path_get_mount_info(path, &fstype, &options); + _cleanup_free_ char *fstype = NULL, *options = NULL; + r = path_get_mount_info_at(fd, /* path = */ NULL, &fstype, &options); if (r < 0) - return RET_GATHER(ret, r); + return r; if (fstype_is_network(fstype)) return true; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 067ed0e4d9427..496a95ab050ee 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -181,4 +181,7 @@ int mount_credentials_fs(const char *path, size_t size, bool ro); int make_fsmount(int error_log_level, const char *what, const char *type, unsigned long flags, const char *options, int userns_fd); -int path_is_network_fs_harder(const char *path); +int path_is_network_fs_harder_at(int dir_fd, const char *path); +static inline int path_is_network_fs_harder(const char *path) { + return path_is_network_fs_harder_at(AT_FDCWD, path); +} diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c index 469aa35913baa..4ac8f869d6614 100644 --- a/src/test/test-mount-util.c +++ b/src/test/test-mount-util.c @@ -538,9 +538,53 @@ TEST(bind_mount_submounts) { } TEST(path_is_network_fs_harder) { - ASSERT_OK_ZERO(path_is_network_fs_harder("/dev")); - ASSERT_OK_ZERO(path_is_network_fs_harder("/sys")); - ASSERT_OK_ZERO(path_is_network_fs_harder("/run")); + _cleanup_close_ int dir_fd = -EBADF; + int r; + + ASSERT_OK(dir_fd = open("/", O_PATH | O_CLOEXEC)); + FOREACH_STRING(s, + "/", "/dev/", "/proc/", "/run/", "/sys/", "/tmp/", "/usr/", "/var/tmp/", + "", ".", "../../../", "/this/path/should/not/exist/for/test-mount-util/") { + + r = path_is_network_fs_harder(s); + log_debug("path_is_network_fs_harder(%s) → %i: %s", s, r, r < 0 ? STRERROR(r) : yes_no(r)); + + const char *q = path_startswith(s, "/") ?: s; + r = path_is_network_fs_harder_at(dir_fd, q); + log_debug("path_is_network_fs_harder_at(root, %s) → %i: %s", q, r, r < 0 ? STRERROR(r) : yes_no(r)); + } + + if (geteuid() != 0 || have_effective_cap(CAP_SYS_ADMIN) <= 0) { + (void) log_tests_skipped("not running privileged"); + return; + } + + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + assert_se(mkdtemp_malloc("/tmp/test-mount-util.path_is_network_fs_harder.XXXXXXX", &t) >= 0); + + r = safe_fork("(make_mount-point)", + FORK_RESET_SIGNALS | + FORK_CLOSE_ALL_FDS | + FORK_DEATHSIG_SIGTERM | + FORK_WAIT | + FORK_REOPEN_LOG | + FORK_LOG | + FORK_NEW_MOUNTNS | + FORK_MOUNTNS_SLAVE, + NULL); + ASSERT_OK(r); + + if (r == 0) { + ASSERT_OK(mount_nofollow_verbose(LOG_INFO, "tmpfs", t, "tmpfs", 0, NULL)); + ASSERT_OK_ZERO(path_is_network_fs_harder(t)); + ASSERT_OK_ERRNO(umount(t)); + + ASSERT_OK(mount_nofollow_verbose(LOG_INFO, "tmpfs", t, "tmpfs", 0, "x-systemd-growfs,x-systemd-automount")); + ASSERT_OK_ZERO(path_is_network_fs_harder(t)); + ASSERT_OK_ERRNO(umount(t)); + + _exit(EXIT_SUCCESS); + } } DEFINE_TEST_MAIN(LOG_DEBUG);