From 5134e5462513c9dca8d3cb03ab4d2e3c43421af9 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 Dec 2023 15:32:30 +0000 Subject: [PATCH 1/2] basic: add fds_are_same_mount() helper --- src/basic/fd-util.c | 67 ++++++++++++++++++++++++----------------- src/basic/fd-util.h | 2 ++ src/test/test-fd-util.c | 18 +++++++++++ 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 0690bcd830..9904e3e484 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -900,10 +900,7 @@ int fd_get_diskseq(int fd, uint64_t *ret) { } int path_is_root_at(int dir_fd, const char *path) { - STRUCT_NEW_STATX_DEFINE(st); - STRUCT_NEW_STATX_DEFINE(pst); - _cleanup_close_ int fd = -EBADF; - int r; + _cleanup_close_ int fd = -EBADF, pfd = -EBADF; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); @@ -915,19 +912,9 @@ int path_is_root_at(int dir_fd, const char *path) { dir_fd = fd; } - r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx); - if (r == -ENOTDIR) - return false; - if (r < 0) - return r; - - r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx); - if (r < 0) - return r; - - /* First, compare inode. If these are different, the fd does not point to the root directory "/". */ - if (!statx_inode_same(&st.sx, &pst.sx)) - return false; + pfd = openat(dir_fd, "..", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (pfd < 0) + return errno == ENOTDIR ? false : -errno; /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", * and we also need to check that the mount ids are the same. Otherwise, a construct like the @@ -935,40 +922,64 @@ int path_is_root_at(int dir_fd, const char *path) { * * $ mkdir /tmp/x /tmp/x/y * $ mount --bind /tmp/x /tmp/x/y - * - * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old + */ + + return fds_are_same_mount(dir_fd, pfd); +} + +int fds_are_same_mount(int fd1, int fd2) { + STRUCT_NEW_STATX_DEFINE(st1); + STRUCT_NEW_STATX_DEFINE(st2); + int r; + + assert(fd1 >= 0); + assert(fd2 >= 0); + + r = statx_fallback(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st1.sx); + if (r < 0) + return r; + + r = statx_fallback(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st2.sx); + if (r < 0) + return r; + + /* First, compare inode. If these are different, the fd does not point to the root directory "/". */ + if (!statx_inode_same(&st1.sx, &st2.sx)) + return false; + + /* Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old * kernel is used. In that case, let's assume that we do not have such spurious mount points in an * early boot stage, and silently skip the following check. */ - if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(st1.nsx.stx_mask, STATX_MNT_ID)) { int mntid; - r = path_get_mnt_id_at_fallback(dir_fd, "", &mntid); + r = path_get_mnt_id_at_fallback(fd1, "", &mntid); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); - st.nsx.stx_mnt_id = mntid; - st.nsx.stx_mask |= STATX_MNT_ID; + st1.nsx.stx_mnt_id = mntid; + st1.nsx.stx_mask |= STATX_MNT_ID; } - if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(st2.nsx.stx_mask, STATX_MNT_ID)) { int mntid; - r = path_get_mnt_id_at_fallback(dir_fd, "..", &mntid); + r = path_get_mnt_id_at_fallback(fd2, "", &mntid); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); - pst.nsx.stx_mnt_id = mntid; - pst.nsx.stx_mask |= STATX_MNT_ID; + st2.nsx.stx_mnt_id = mntid; + st2.nsx.stx_mask |= STATX_MNT_ID; } - return statx_mount_same(&st.nsx, &pst.nsx); + return statx_mount_same(&st1.nsx, &st2.nsx); } const char *accmode_to_string(int flags) { diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index 5061e32196..64918a4861 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -117,6 +117,8 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL); } +int fds_are_same_mount(int fd1, int fd2); + /* The maximum length a buffer for a /proc/self/fd/ path needs */ #define PROC_FD_PATH_MAX \ (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 021d4b47c2..5dd9e48c80 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -646,6 +646,24 @@ TEST(dir_fd_is_root) { assert_se(dir_fd_is_root_or_cwd(fd) == 0); } +TEST(fds_are_same_mount) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF, fd3 = -EBADF, fd4 = -EBADF; + + fd1 = open("/sys", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + fd2 = open("/proc", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + fd3 = open("/proc", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + fd4 = open("/", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + + if (fd1 < 0 || fd2 < 0 || fd3 < 0 || fd4 < 0) + return (void) log_tests_skipped_errno(errno, "Failed to open /sys or /proc or /"); + + if (fds_are_same_mount(fd1, fd4) > 0 && fds_are_same_mount(fd2, fd4) > 0) + return (void) log_tests_skipped("Cannot test fds_are_same_mount() as /sys and /proc are not mounted"); + + assert_se(fds_are_same_mount(fd1, fd2) == 0); + assert_se(fds_are_same_mount(fd2, fd3) > 0); +} + TEST(fd_get_path) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; From f83a74dfb8210f0d67943912c539e51b989f85a4 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Tue, 5 Dec 2023 15:43:12 +0000 Subject: [PATCH 2/2] switch-root: also check that mount IDs are the same, not just inodes If /run/nextroot/ has been set up, use it, even if the inodes are the same. It could be a verity device that is reused, but with different sub-mounts or other differences. Or the same / tmpfs with different /usr/ mounts. If it was explicitly set up we should use it. Use the new helper to check that the mount IDs are also the same, not just the inodes. --- src/shared/switch-root.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index b620156c75..787fb79afb 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -63,11 +63,11 @@ int switch_root(const char *new_root, if (new_root_fd < 0) return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); - r = inode_same_at(old_root_fd, "", new_root_fd, "", AT_EMPTY_PATH); + r = fds_are_same_mount(old_root_fd, new_root_fd); if (r < 0) - return log_error_errno(r, "Failed to determine if old and new root directory are the same: %m"); + return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m"); if (r > 0) { - log_debug("Skipping switch root, as old and new root directory are the same."); + log_debug("Skipping switch root, as old and new root directories/mounts are the same."); return 0; }