]> git.ipfire.org Git - thirdparty/util-linux.git/commit
loopdev: add LOOPDEV_FL_NOFOLLOW to prevent symlink attacks stable/v2.40
authorKarel Zak <kzak@redhat.com>
Thu, 19 Feb 2026 12:59:46 +0000 (13:59 +0100)
committervulgraph <216388001+vulgraph@users.noreply.github.com>
Wed, 22 Apr 2026 16:07:35 +0000 (00:07 +0800)
commit0000ca30646d03f9dfbab9f62a5ce21a939c1018
tree8334e3cf89a18ab67c04ba941b232980220717e7
parentdbcc687f6ab1568982cdf3fe391c0beb818b7e28
loopdev: add LOOPDEV_FL_NOFOLLOW to prevent symlink attacks

Add a new LOOPDEV_FL_NOFOLLOW flag for loop device context that
prevents symlink following in both path canonicalization and file open.

When set:
- loopcxt_set_backing_file() uses strdup() instead of
  ul_canonicalize_path() (which calls realpath() and follows symlinks)
- loopcxt_setup_device() adds O_NOFOLLOW to open() flags

The flag is set for non-root (restricted) mount operations in
libmount's loop device hook. This prevents a TOCTOU race condition
where an attacker could replace the backing file (specified in
/etc/fstab) with a symlink to an arbitrary root-owned file between
path resolution and open().

Vulnerable Code Flow:

  mount /mnt/point (non-root, SUID)
    mount.c: sanitize_paths() on user args (mountpoint only)
    mnt_context_mount()
      mnt_context_prepare_mount()
        mnt_context_apply_fstab()           <-- source path from fstab
        hooks run at MNT_STAGE_PREP_SOURCE
          hook_loopdev.c: setup_loopdev()
            backing_file = fstab source path ("/home/user/disk.img")
            loopcxt_set_backing_file()       <-- calls realpath() as ROOT
              ul_canonicalize_path()         <-- follows symlinks!
            loopcxt_setup_device()
              open(lc->filename, O_RDWR|O_CLOEXEC)  <-- no O_NOFOLLOW

Two vulnerabilities in the path:

1) loopcxt_set_backing_file() calls ul_canonicalize_path() which uses
   realpath() -- this follows symlinks as euid=0. If the attacker swaps
   the file to a symlink before this call, lc->filename becomes the
   resolved target path (e.g., /root/secret.img).

2) loopcxt_setup_device() opens lc->filename without O_NOFOLLOW. Even
   if canonicalization happened correctly, the file can be swapped to a
   symlink between canonicalize and open.

Addresses: https://github.com/util-linux/util-linux/security/advisories/GHSA-qq4x-vfq4-9h9g
Signed-off-by: Karel Zak <kzak@redhat.com>
(cherry picked from commit 5e390467b26a3cf3fecc04e1a0d482dff3162fc4)
include/loopdev.h
lib/loopdev.c
libmount/src/hook_loopdev.c