]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: add fd_target to context for TOCTOU prevention
authorKarel Zak <kzak@redhat.com>
Tue, 16 Jun 2026 09:13:54 +0000 (11:13 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 16 Jun 2026 09:13:54 +0000 (11:13 +0200)
Add a pinned O_PATH target fd to libmnt_context with lazy-open getter
mnt_context_get_target_fd() and mnt_context_close_target_fd().

The fd is opened via ul_open_no_symlinks() (RESOLVE_NO_SYMLINKS) to
reject symlinks at any path component.  The fd is closed on context
reset.

CVE-2026-53613

Signed-off-by: Karel Zak <kzak@redhat.com>
libmount/src/context.c
libmount/src/context_mount.c
libmount/src/hook_mount.c
libmount/src/hook_mount_legacy.c
libmount/src/mountP.h

index dccf8bdd5056693348d97c3039dbaee50f01fbb9..cfd5cd3db1e6548f1a1b7ba697e96262cb3added 100644 (file)
@@ -37,6 +37,7 @@
  */
 
 #include "mountP.h"
+#include "fileutils.h"
 #include "strutils.h"
 #include "namespace.h"
 #include "match.h"
@@ -67,6 +68,7 @@ struct libmnt_context *mnt_new_context(void)
        cxt->ns_orig.fd = -1;
        cxt->ns_tgt.fd = -1;
        cxt->ns_cur = &cxt->ns_orig;
+       cxt->fd_target = -1;
 
        cxt->map_linux = mnt_get_builtin_optmap(MNT_LINUX_MAP);
        cxt->map_userspace = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
@@ -173,6 +175,7 @@ int mnt_reset_context(struct libmnt_context *cxt)
        cxt->map_userspace = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
 
        mnt_context_reset_status(cxt);
+       mnt_context_close_target_fd(cxt);
        mnt_context_deinit_hooksets(cxt);
 
        if (cxt->table_fltrcb)
@@ -398,6 +401,46 @@ int mnt_context_is_restricted(struct libmnt_context *cxt)
        return cxt->restricted;
 }
 
+int mnt_context_target_fd_required(struct libmnt_context *cxt)
+{
+       return mnt_context_is_restricted(cxt);
+}
+
+int mnt_context_reopen_target_fd(struct libmnt_context *cxt)
+{
+       assert(cxt);
+
+       if (!mnt_context_target_fd_required(cxt))
+               return 0;
+       mnt_context_close_target_fd(cxt);
+       if (mnt_context_get_target_fd(cxt) < 0)
+               return -errno;
+       return 0;
+}
+
+int mnt_context_get_target_fd(struct libmnt_context *cxt)
+{
+       assert(cxt);
+
+       if (cxt->fd_target < 0) {
+               const char *target = mnt_fs_get_target(cxt->fs);
+
+               if (target)
+                       cxt->fd_target = ul_open_no_symlinks(target,
+                                               O_PATH | O_CLOEXEC, 0);
+       }
+       return cxt->fd_target;
+}
+
+void mnt_context_close_target_fd(struct libmnt_context *cxt)
+{
+       assert(cxt);
+
+       if (cxt->fd_target >= 0)
+               close(cxt->fd_target);
+       cxt->fd_target = -1;
+}
+
 /**
  * mnt_context_force_unrestricted:
  * @cxt: mount context
index 1d1f851ec2b229e05b7148ea01efe08585a4dd4f..3e81ab74b181e864f7f0bccd32aa38a9b7616e81 100644 (file)
@@ -740,6 +740,13 @@ static int prepare_target(struct libmnt_context *cxt)
        if (rc == 0)
                rc = mnt_context_call_hooks(cxt, MNT_STAGE_PREP_TARGET);
 
+       if (rc == 0
+           && mnt_context_target_fd_required(cxt)
+           && mnt_context_get_target_fd(cxt) < 0) {
+               DBG_OBJ(CXT, cxt, ul_debug("failed to pin target"));
+               rc = -errno;
+       }
+
        if (!mnt_context_switch_ns(cxt, ns_old))
                return -MNT_ERR_NAMESPACE;
 
index d46313646f6bbdd2f5e08560a9a8c5833782a04e..1dab62c68798688ecef32736598cd170b2c13955 100644 (file)
@@ -548,7 +548,17 @@ static int hook_attach_target(struct libmnt_context *cxt,
        if (mnt_context_is_beneath(cxt))
                flags |= MOVE_MOUNT_BENEATH;
 
-       rc = move_mount(api->fd_tree, "", AT_FDCWD, target, flags);
+       /* fd_target is open in restricted mode (see prepare_target()) */
+       if (mnt_context_target_fd_required(cxt)) {
+               int fd = mnt_context_get_target_fd(cxt);
+
+               if (fd < 0)
+                       return -errno;
+               flags |= MOVE_MOUNT_T_EMPTY_PATH;
+               rc = move_mount(api->fd_tree, "", fd, "", flags);
+       } else
+               rc = move_mount(api->fd_tree, "", AT_FDCWD, target, flags);
+
        hookset_set_syscall_status(cxt, "move_mount", rc == 0);
 
        if (rc == 0) {
@@ -558,6 +568,9 @@ static int hook_attach_target(struct libmnt_context *cxt,
                        mnt_fs_mark_moved(cxt->fs);
                else
                        mnt_fs_mark_attached(cxt->fs);
+
+               /* re-open to point to the mounted filesystem root */
+               rc = mnt_context_reopen_target_fd(cxt);
        }
 
        return rc == 0 ? 0 : -errno;
index 9b2485a74193f96f26af564b03e46d8598026332..86b1c9f06282edf23136fc027a97a21c578f7abb 100644 (file)
@@ -251,6 +251,9 @@ static int hook_mount(struct libmnt_context *cxt,
        else
                mnt_fs_mark_attached(cxt->fs);
 
+       /* re-open to point to the mounted filesystem root */
+       rc = mnt_context_reopen_target_fd(cxt);
+
        cxt->syscall_status = 0;
        return rc;
 }
index 094c9a928804d15422e22f5f6da005b57b63ec37..01af14f3fe14507678ce9d239468ade89c29644a 100644 (file)
@@ -513,6 +513,8 @@ struct libmnt_context
        unsigned int    has_selinux_opt : 1;    /* temporary for broken fsconfig() syscall */
        unsigned int    force_clone : 1;        /* OPEN_TREE_CLONE */
 
+       int             fd_target;      /* pinned target fd (RESOLVE_NO_SYMLINKS) */
+
        struct list_head        hooksets_datas; /* global hooksets data */
        struct list_head        hooksets_hooks; /* global hooksets data */
 };
@@ -698,6 +700,11 @@ extern int mnt_context_update_tabs(struct libmnt_context *cxt);
 
 extern void mnt_cache_enable_noprobe(struct libmnt_cache *cache, int enable);
 
+extern int mnt_context_target_fd_required(struct libmnt_context *cxt);
+extern int mnt_context_get_target_fd(struct libmnt_context *cxt);
+extern void mnt_context_close_target_fd(struct libmnt_context *cxt);
+extern int mnt_context_reopen_target_fd(struct libmnt_context *cxt);
+
 extern int mnt_context_umount_setopt(struct libmnt_context *cxt, int c, char *arg);
 extern int mnt_context_mount_setopt(struct libmnt_context *cxt, int c, char *arg);