]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: add mount ID verification and man page TOCTOU note
authorKarel Zak <kzak@redhat.com>
Wed, 27 May 2026 10:38:16 +0000 (12:38 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 16 Jun 2026 09:15:43 +0000 (11:15 +0200)
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
sys-utils/mount.8.adoc

index 1865513c3a5426c6584a2f12d792fee36d7588d0..d5d9151b38d8f5b95a4fb452c89212ded5a94587 100644 (file)
@@ -412,9 +412,33 @@ int mnt_context_reopen_target_fd(struct libmnt_context *cxt)
 
        if (!mnt_context_target_fd_required(cxt))
                return 0;
+
+       DBG_OBJ(CXT, cxt, ul_debug("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_OBJ(CXT, cxt, ul_debug(
+                               "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_OBJ(CXT, cxt, ul_debug("target mount ID verified (%d)", id));
+       }
+
        return 0;
 }
 
@@ -425,9 +449,12 @@ int mnt_context_get_target_fd(struct libmnt_context *cxt)
        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_OBJ(CXT, cxt, ul_debug("open target fd=%d [%s]",
+                                               cxt->fd_target, target));
+               }
        }
        return cxt->fd_target;
 }
index c600723d9274c0578c468f4e525d475d7e17d964..abcf08b527e9f59f716b1affaffb50ee62f008aa 100644 (file)
@@ -188,6 +188,8 @@ For more details, see *fstab*(5). Only the user that mounted a filesystem can un
 
 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: