Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions SPECS/util-linux/CVE-2026-53612.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
From 897a08c2b11dfa66975c3d24d63c7bf5f5be1a7c Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Tue, 16 Jun 2026 11:15:19 +0200
Subject: [PATCH 2/2] libmount: use fd-based fchownat/chmod in hook_owner

Replace path-based lchown()/chmod() with fd-based operations in the
X-mount.{owner,group,mode} post-mount hook.

For restricted users the fd_target is pinned in prepare_target() and
re-opened after mount in hook_attach_target() to point to the mounted
filesystem root. For root a local O_PATH fd is opened. Ownership is
changed via fchownat(fd, "", ..., AT_EMPTY_PATH), mode via
/proc/self/fd/N.

This prevents TOCTOU attacks where an ancestor directory is swapped
between mount and the chmod/chown operations.

CVE-2026-53612

Reported-by: Xinyao Hu <ctf_0x01@foxmail.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
(cherry picked from commit 24da33905c7115c4cbccd0afb2a469804e96467a)
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/util-linux/util-linux/commit/897a08c2b11dfa66975c3d24d63c7bf5f5be1a7c.patch
---
libmount/src/hook_owner.c | 32 ++++++++++++++++++++++++--------
1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/libmount/src/hook_owner.c b/libmount/src/hook_owner.c
index 11b238c..99f0e70 100644
--- a/libmount/src/hook_owner.c
+++ b/libmount/src/hook_owner.c
@@ -17,6 +17,7 @@
#include <sched.h>

#include "mountP.h"
+#include "pathnames.h"
#include "fileutils.h"

struct hook_data {
@@ -48,7 +49,7 @@ static int hook_post(
{
struct hook_data *hd = (struct hook_data *) data;
const char *target;
- int rc = 0;
+ int rc = 0, fd;

assert(cxt);

@@ -59,18 +60,33 @@ static int hook_post(
if (!target)
return 0;

+ /* fd_target is pinned in restricted mode (see prepare_target()),
+ * for root open it here to keep chmod/chown fd-based too */
+ if (mnt_context_target_fd_required(cxt))
+ fd = mnt_context_get_target_fd(cxt);
+ else
+ fd = open(target, O_PATH | O_CLOEXEC);
+
+ if (fd < 0)
+ return -MNT_ERR_CHMOD;
+
if (hd->owner != (uid_t) -1 || hd->group != (uid_t) -1) {
- DBG(CXT, ul_debugobj(cxt, " lchown(%s, %u, %u)", target, hd->owner, hd->group));
- if (lchown(target, hd->owner, hd->group) == -1)
- return -MNT_ERR_CHOWN;
+ DBG(CXT, ul_debugobj(cxt, " fchownat(%s, %u, %u)", target, hd->owner, hd->group));
+ if (fchownat(fd, "", hd->owner, hd->group, AT_EMPTY_PATH) == -1)
+ rc = -MNT_ERR_CHOWN;
}

- if (hd->mode != (mode_t) -1) {
- DBG(CXT, ul_debugobj(cxt, " chmod(%s, %04o)", target, hd->mode));
- if (chmod(target, hd->mode) == -1)
- return -MNT_ERR_CHMOD;
+ if (!rc && hd->mode != (mode_t) -1) {
+ char buf[sizeof(_PATH_PROC_FDDIR) + 1 + sizeof(stringify_value(INT_MAX))];
+
+ snprintf(buf, sizeof(buf), _PATH_PROC_FDDIR "/%d", fd);
+ DBG(CXT, ul_debugobj(cxt, " chmod(%s, %04o)", buf, hd->mode));
+ if (chmod(buf, hd->mode) == -1)
+ rc = -MNT_ERR_CHMOD;
}

+ if (!mnt_context_target_fd_required(cxt))
+ close(fd);
return rc;
}

--
2.45.4

183 changes: 183 additions & 0 deletions SPECS/util-linux/CVE-2026-53613.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
From 0b010025a0e429bc80355c94db86a843395d49e2 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Tue, 16 Jun 2026 11:13:54 +0200
Subject: [PATCH] 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.

Backport note: CVE-2026-78410.patch already adds fileutils.h to context.c.
Omit only that duplicate include hunk; retain the upstream target-FD changes.

CVE-2026-53613

Signed-off-by: Karel Zak <kzak@redhat.com>
(cherry picked from commit 78a860982e036f38fe9c0b3344998df5ac2c2ff5)
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/util-linux/util-linux/commit/0b010025a0e429bc80355c94db86a843395d49e2.patch
---
libmount/src/context.c | 42 ++++++++++++++++++++++++++++++++
libmount/src/context_mount.c | 7 ++++++
libmount/src/hook_mount.c | 18 ++++++++++++-
libmount/src/hook_mount_legacy.c | 3 +++
libmount/src/mountP.h | 7 ++++++
5 files changed, 76 insertions(+), 1 deletion(-)

diff --git a/libmount/src/context.c b/libmount/src/context.c
--- a/libmount/src/context.c
+++ b/libmount/src/context.c
@@ -69,6 +69,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);
@@ -175,6 +176,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)
@@ -400,6 +402,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 28e01309d8c..77435684b4a 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(CXT, ul_debugobj(cxt, "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 37179fb59ad..50e656e04af 100644
--- a/libmount/src/hook_mount.c
+++ b/libmount/src/hook_mount.c
@@ -516,9 +516,25 @@ static int hook_attach_target(struct libmnt_context *cxt,
umount2(target, MNT_DETACH);
}

- rc = move_mount(api->fd_tree, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH);
+ /* 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;
+ rc = move_mount(api->fd_tree, "", fd, "",
+ MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH);
+ } else
+ rc = move_mount(api->fd_tree, "", AT_FDCWD, target,
+ MOVE_MOUNT_F_EMPTY_PATH);
+
hookset_set_syscall_status(cxt, "move_mount", rc == 0);

+ if (rc == 0) {
+ /* 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 94a8fc685eb..d6631b08864 100644
--- a/libmount/src/hook_mount_legacy.c
+++ b/libmount/src/hook_mount_legacy.c
@@ -243,6 +243,9 @@ static int hook_mount(struct libmnt_context *cxt,
return rc;
}

+ /* 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 a4ce7c04268..97d0bf8fa79 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -497,6 +497,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 */
};
@@ -678,6 +680,11 @@ extern int mnt_context_prepare_update(struct libmnt_context *cxt);
extern int mnt_context_merge_mflags(struct libmnt_context *cxt);
extern int mnt_context_update_tabs(struct libmnt_context *cxt);

+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);

84 changes: 84 additions & 0 deletions SPECS/util-linux/CVE-2026-53614.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
From cc81bbcec598cb91f0eb8456282f33eed820ed5f Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Tue, 16 Jun 2026 10:58:32 +0200
Subject: [PATCH] libmount: fix SUID bypass via LIBMOUNT_FORCE_MOUNT2 and
legacy mount path

Use safe_getenv() for LIBMOUNT_FORCE_MOUNT2 to ignore the variable
in SUID context, consistent with LIBMOUNT_FSTAB and other sensitive
environment variables.

Additionally, refuse multi-step mount(2) sequences (bind+remount and
propagation) for restricted (non-root) users in the legacy mount path.
The two-step approach has a window between syscalls where security
flags (nosuid, noexec, ...) are not yet applied. The new mount API
handles this atomically.

CVE-2026-53614

Reported-by: Xinyao Hu <ctf_0x01@foxmail.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
(cherry picked from commit 9cbfb823500f510b34767edabd3ffd5b436987b4)
Signed-off-by: Azure Linux Security Servicing Account <azurelinux-security@microsoft.com>
Upstream-reference: https://github.com/util-linux/util-linux/commit/cc81bbcec598cb91f0eb8456282f33eed820ed5f.patch
---
libmount/src/hook_mount.c | 3 ++-
libmount/src/hook_mount_legacy.c | 8 ++++++++
2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/libmount/src/hook_mount.c b/libmount/src/hook_mount.c
index 116046b..098b1ac 100644
--- a/libmount/src/hook_mount.c
+++ b/libmount/src/hook_mount.c
@@ -44,6 +44,7 @@
*/

#include "mountP.h"
+#include "env.h"
#include "fileutils.h" /* statx() fallback */
#include "strutils.h"
#include "mount-api-utils.h"
@@ -658,7 +659,7 @@ fail:

static int force_classic_mount(struct libmnt_context *cxt)
{
- const char *env = getenv("LIBMOUNT_FORCE_MOUNT2");
+ const char *env = safe_getenv("LIBMOUNT_FORCE_MOUNT2");

if (env) {
if (strcmp(env, "always") == 0)
diff --git a/libmount/src/hook_mount_legacy.c b/libmount/src/hook_mount_legacy.c
index ab8c2c2..b7882a1 100644
--- a/libmount/src/hook_mount_legacy.c
+++ b/libmount/src/hook_mount_legacy.c
@@ -285,6 +285,8 @@ static int hook_prepare(struct libmnt_context *cxt,

/* add extra mount(2) calls for each propagation flag */
if (flags & MS_PROPAGATION) {
+ if (mnt_context_is_restricted(cxt))
+ goto eperm;
rc = prepare_propagation(cxt, hs);
if (rc)
return rc;
@@ -294,12 +296,18 @@ static int hook_prepare(struct libmnt_context *cxt,
if ((flags & MS_BIND)
&& (flags & MNT_BIND_SETTABLE)
&& !(flags & MS_REMOUNT)) {
+ if (mnt_context_is_restricted(cxt))
+ goto eperm;
rc = prepare_bindremount(cxt, hs);
if (rc)
return rc;
}

return rc;
+eperm:
+ DBG(HOOK, ul_debugobj(hs,
+ "multi-step mount(2) refused for non-root user"));
+ return -EPERM;
}

const struct libmnt_hookset hookset_mount_legacy =
--
2.45.4

Loading
Loading