From 39fef62bf3226abb6bb018fa8750afa0327e84b4 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Wed, 27 May 2026 12:38:16 +0200
Subject: [PATCH 11/12] libmount: add mount ID verification and man page TOCTOU
 note

Verify mount ID after re-opening the target fd to ensure the mount
landed on the expected target.  The expected ID is set from fd_tree
in hook_create_mount() (new mount API only).

Add WARNING to mount.8 about the inherent TOCTOU limitation of the
legacy mount(2) syscall for non-superuser mounts.

Signed-off-by: Karel Zak <kzak@redhat.com>
---
 libmount/src/context.c | 29 ++++++++++++++++++++++++++++-
 sys-utils/mount.8.adoc |  2 ++
 2 files changed, 30 insertions(+), 1 deletion(-)

Index: util-linux-2.40.4/libmount/src/context.c
===================================================================
--- util-linux-2.40.4.orig/libmount/src/context.c
+++ util-linux-2.40.4/libmount/src/context.c
@@ -406,15 +406,78 @@ int mnt_context_target_fd_required(struc
 	return mnt_context_is_restricted(cxt);
 }
 
+static int get_mnt_id(	int fd, const char *path,
+			uint64_t *uniq_id, int *id)
+{
+	int rc;
+	struct statx sx = { 0 };
+	int flags = AT_STATX_DONT_SYNC | AT_NO_AUTOMOUNT;
+
+	if (!path || !*path)
+		flags |= AT_EMPTY_PATH;
+
+	if (id) {
+		rc = statx(fd, path ? path : "", flags,
+				STATX_MNT_ID, &sx);
+		if (rc)
+			return rc;
+		*id = sx.stx_mnt_id;
+	}
+	if (uniq_id) {
+# ifdef STATX_MNT_ID_UNIQUE
+		errno = 0;
+		rc = statx(fd, path ? path : "", flags,
+				STATX_MNT_ID_UNIQUE, &sx);
+
+		if (rc && errno == EINVAL)
+			return -ENOSYS;		/* *_ID_UNIQUE unsupported? */
+		if (rc)
+			return rc;
+		*uniq_id = sx.stx_mnt_id;
+# else
+		return -ENOSYS;
+# endif
+	}
+	return 0;
+}
+static int mnt_id_from_fd(int fd, uint64_t *uniq_id, int *id)
+{
+	return get_mnt_id(fd, NULL, uniq_id, id);
+}
+
 int mnt_context_reopen_target_fd(struct libmnt_context *cxt)
 {
 	assert(cxt);
 
 	if (!mnt_context_target_fd_required(cxt))
 		return 0;
+
+	DBG(CXT, ul_debugobj(cxt, "reopen target fd"));
+
 	mnt_context_close_target_fd(cxt);
 	if (mnt_context_get_target_fd(cxt) < 0)
 		return -errno;
+
+	/* verify the mount landed on the expected target;
+	 * cxt->fs->id is set from fd_tree in hook_create_mount() */
+	if (cxt->fs && cxt->fs->id > 0) {
+		int id = 0;
+
+		if (mnt_id_from_fd(cxt->fd_target, NULL, &id) == 0
+		    && id != cxt->fs->id) {
+			const char *tgt = mnt_fs_get_target(cxt->fs);
+
+			DBG(CXT, ul_debugobj(cxt,
+				"target mount ID mismatch (expected %d, got %d), umounting",
+				cxt->fs->id, id));
+			if (tgt)
+				umount2(tgt, MNT_DETACH);
+			mnt_context_close_target_fd(cxt);
+			return -EPERM;
+		}
+		DBG(CXT, ul_debugobj(cxt, "target mount ID verified (%d)", id));
+	}
+
 	return 0;
 }
 
@@ -425,9 +488,12 @@ int mnt_context_get_target_fd(struct lib
 	if (cxt->fd_target < 0) {
 		const char *target = mnt_fs_get_target(cxt->fs);
 
-		if (target)
+		if (target) {
 			cxt->fd_target = ul_open_no_symlinks(target,
 						O_PATH | O_CLOEXEC, 0);
+			DBG(CXT, ul_debugobj(cxt, "open target fd=%d [%s]",
+						cxt->fd_target, target));
+		}
 	}
 	return cxt->fd_target;
 }
Index: util-linux-2.40.4/sys-utils/mount.8.adoc
===================================================================
--- util-linux-2.40.4.orig/sys-utils/mount.8.adoc
+++ util-linux-2.40.4/sys-utils/mount.8.adoc
@@ -186,6 +186,8 @@ For more details, see *fstab*(5). Only t
 
 The *user* mount option is accepted if no username is specified. If used in the format *user=someone*, the option is silently ignored and visible only for external mount helpers (/sbin/mount.<type>) for compatibility with some network filesystems.
 
+WARNING: When using the legacy *mount*(2) syscall (on older kernels without the new mount API), the mount target path is resolved by the kernel at syscall time. This means there is an inherent time-of-check-to-time-of-use (TOCTOU) window between the permission verification and the actual mount operation. If an ancestor directory of the mount target is writable by the unprivileged user, a path component could be swapped to redirect the mount to an unintended location. The new mount API (available since Linux 5.2) eliminates this issue by using file-descriptor-based target resolution. Administrators should ensure that mount target paths for *user* mounts do not traverse directories writable by unprivileged users.
+
 === Bind mount operation
 
 Remount part of the file hierarchy somewhere else. The call is:
