From: Karel Zak Date: Tue, 16 Jun 2026 09:13:54 +0000 (+0200) Subject: libmount: add fd_target to context for TOCTOU prevention X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=78a860982e036f38fe9c0b3344998df5ac2c2ff5;p=thirdparty%2Futil-linux.git libmount: add fd_target to context for TOCTOU prevention 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 --- diff --git a/libmount/src/context.c b/libmount/src/context.c index dccf8bdd5..cfd5cd3db 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -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 diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c index 1d1f851ec..3e81ab74b 100644 --- a/libmount/src/context_mount.c +++ b/libmount/src/context_mount.c @@ -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; diff --git a/libmount/src/hook_mount.c b/libmount/src/hook_mount.c index d46313646..1dab62c68 100644 --- a/libmount/src/hook_mount.c +++ b/libmount/src/hook_mount.c @@ -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; diff --git a/libmount/src/hook_mount_legacy.c b/libmount/src/hook_mount_legacy.c index 9b2485a74..86b1c9f06 100644 --- a/libmount/src/hook_mount_legacy.c +++ b/libmount/src/hook_mount_legacy.c @@ -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; } diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h index 094c9a928..01af14f3f 100644 --- a/libmount/src/mountP.h +++ b/libmount/src/mountP.h @@ -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);