From d90a74086699b4344457abdad2f33f77e7a1ae78 Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Fri, 16 Oct 2026 04:19:58 +0000 Subject: [PATCH 1/5] Patch util-linux for CVE-2026-53615 --- SPECS/util-linux/CVE-2026-53615.patch | 110 ++++++++++++++++++ SPECS/util-linux/util-linux.spec | 6 +- .../manifests/package/pkggen_core_aarch64.txt | 6 +- .../manifests/package/pkggen_core_x86_64.txt | 6 +- .../manifests/package/toolchain_aarch64.txt | 12 +- .../manifests/package/toolchain_x86_64.txt | 12 +- 6 files changed, 133 insertions(+), 19 deletions(-) create mode 100644 SPECS/util-linux/CVE-2026-53615.patch diff --git a/SPECS/util-linux/CVE-2026-53615.patch b/SPECS/util-linux/CVE-2026-53615.patch new file mode 100644 index 00000000000..28b4ed60492 --- /dev/null +++ b/SPECS/util-linux/CVE-2026-53615.patch @@ -0,0 +1,110 @@ +From b9e3490fe7d495fc85d75a4542d73f056a6efb8c Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Wed, 25 Mar 2026 11:54:28 +0100 +Subject: [PATCH] libblkid: dos: validate EBR data and links within extended + partition + +The parse_dos_extended() function processes EBR (Extended Boot Record) +chains, but does not properly validate that partitions and links stay +within the master extended partition area [ex_start, ex_start+ex_size). + +For EBR data partitions at index 0 and 1, there are no bounds checks +at all, unlike entries at index 2 and 3. This allows a crafted disk +image to register partitions at arbitrary sectors via uint32_t overflow +in the abs_start calculation (cur_start + start wraps to a small value). + +Fix by: + - using 64-bit arithmetic for the abs_start calculation to avoid + uint32_t wraparound + - applying the extended partition area bounds check to all EBR data + entries, not just index >= 2 + - validating EBR link targets stay within the extended area and + advance forward (preventing backward links that could loop) + +Reported-by: Michele Piccinni +Signed-off-by: Karel Zak +(cherry picked from commit e9d544d013267ea86b70ee8ff78abd280e6641af) +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/util-linux/util-linux/commit/05c2dbadf34cc74192287f377904afaaf4061623.patch +--- + libblkid/src/partitions/dos.c | 38 +++++++++++++++++++++++++++-------- + 1 file changed, 30 insertions(+), 8 deletions(-) + +diff --git a/libblkid/src/partitions/dos.c b/libblkid/src/partitions/dos.c +index d5a8aa3..5c78aac 100644 +--- a/libblkid/src/partitions/dos.c ++++ b/libblkid/src/partitions/dos.c +@@ -46,6 +46,7 @@ static int parse_dos_extended(blkid_probe pr, blkid_parttable tab, + { + blkid_partlist ls = blkid_probe_get_partlist(pr); + uint32_t cur_start = ex_start, cur_size = ex_size; ++ uint64_t ex_end = (uint64_t) ex_start + ex_size; + const unsigned char *data; + int ct_nodata = 0; /* count ext.partitions without data partitions */ + int i; +@@ -88,24 +89,31 @@ static int parse_dos_extended(blkid_probe pr, blkid_parttable tab, + /* Parse data partition */ + for (p = p0, i = 0; i < 4; i++, p++) { + uint32_t abs_start; ++ uint64_t abs; + blkid_partition par; + + /* the start is relative to the parental ext.partition */ + start = dos_partition_get_start(p) * ssf; + size = dos_partition_get_size(p) * ssf; +- abs_start = cur_start + start; /* absolute start */ + + if (!size || is_extended(p)) + continue; ++ ++ abs = (uint64_t) cur_start + start; ++ ++ /* data partition must be within the extended area */ ++ if (abs < ex_start || abs + size > ex_end) { ++ DBG(LOWPROBE, ul_debug("#%d: EBR data partition outside " ++ "extended -- ignore", i + 1)); ++ continue; ++ } ++ abs_start = (uint32_t) abs; ++ + if (i >= 2) { +- /* extra checks to detect real data on ++ /* extra check to detect real data on + * 3rd and 4th entries */ + if (start + size > cur_size) + continue; +- if (abs_start < ex_start) +- continue; +- if (abs_start + size > ex_start + ex_size) +- continue; + } + + /* Avoid recursive non-empty links, see ct_nodata counter */ +@@ -142,8 +150,22 @@ static int parse_dos_extended(blkid_probe pr, blkid_parttable tab, + if (i == 4) + goto leave; + +- cur_start = ex_start + start; +- cur_size = size; ++ { ++ uint64_t next = (uint64_t) ex_start + start; ++ ++ if (next + size > ex_end) { ++ DBG(LOWPROBE, ul_debug("EBR link outside " ++ "extended area -- leave")); ++ goto leave; ++ } ++ if (next <= cur_start) { ++ DBG(LOWPROBE, ul_debug("EBR link does not " ++ "advance -- leave")); ++ goto leave; ++ } ++ cur_start = (uint32_t) next; ++ cur_size = size; ++ } + } + leave: + return BLKID_PROBE_OK; +-- +2.45.4 + diff --git a/SPECS/util-linux/util-linux.spec b/SPECS/util-linux/util-linux.spec index 6b461df59b4..ee0388393b2 100644 --- a/SPECS/util-linux/util-linux.spec +++ b/SPECS/util-linux/util-linux.spec @@ -5,7 +5,7 @@ Summary: Utilities for file systems, consoles, partitions, and messages Name: util-linux Version: 2.40.2 -Release: 6%{?dist} +Release: 7%{?dist} License: GPLv2+ Vendor: Microsoft Corporation Distribution: Azure Linux @@ -23,6 +23,7 @@ Patch3: CVE-2026-13595.patch Patch4: CVE-2026-76642.patch Patch5: CVE-2026-78408.patch Patch6: CVE-2026-78410.patch +Patch7: CVE-2026-53615.patch BuildRequires: audit-devel BuildRequires: libcap-ng-devel BuildRequires: libselinux-devel @@ -178,6 +179,9 @@ rm -rf %{buildroot}/lib/systemd/system %{_mandir}/man3/* %changelog +* Fri Oct 16 2026 Azure Linux Security Servicing Account - 2.40.2-7 +- Patch for CVE-2026-53615 + * Mon Sep 07 2026 Azure Linux Security Servicing Account - 2.40.2-6 - Patch for CVE-2026-78410, CVE-2026-78408, CVE-2026-76642 diff --git a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt index ef4d10a979c..5010c39c396 100644 --- a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt +++ b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt @@ -70,9 +70,9 @@ make-4.4.1-2.azl3.aarch64.rpm patch-2.7.6-10.azl3.aarch64.rpm libcap-ng-0.8.4-1.azl3.aarch64.rpm libcap-ng-devel-0.8.4-1.azl3.aarch64.rpm -util-linux-2.40.2-6.azl3.aarch64.rpm -util-linux-devel-2.40.2-6.azl3.aarch64.rpm -util-linux-libs-2.40.2-6.azl3.aarch64.rpm +util-linux-2.40.2-7.azl3.aarch64.rpm +util-linux-devel-2.40.2-7.azl3.aarch64.rpm +util-linux-libs-2.40.2-7.azl3.aarch64.rpm tar-1.35-2.azl3.aarch64.rpm xz-5.4.4-3.azl3.aarch64.rpm xz-devel-5.4.4-3.azl3.aarch64.rpm diff --git a/toolkit/resources/manifests/package/pkggen_core_x86_64.txt b/toolkit/resources/manifests/package/pkggen_core_x86_64.txt index 49d75af033b..3a3b84bcfa1 100644 --- a/toolkit/resources/manifests/package/pkggen_core_x86_64.txt +++ b/toolkit/resources/manifests/package/pkggen_core_x86_64.txt @@ -70,9 +70,9 @@ make-4.4.1-2.azl3.x86_64.rpm patch-2.7.6-10.azl3.x86_64.rpm libcap-ng-0.8.4-1.azl3.x86_64.rpm libcap-ng-devel-0.8.4-1.azl3.x86_64.rpm -util-linux-2.40.2-6.azl3.x86_64.rpm -util-linux-devel-2.40.2-6.azl3.x86_64.rpm -util-linux-libs-2.40.2-6.azl3.x86_64.rpm +util-linux-2.40.2-7.azl3.x86_64.rpm +util-linux-devel-2.40.2-7.azl3.x86_64.rpm +util-linux-libs-2.40.2-7.azl3.x86_64.rpm tar-1.35-2.azl3.x86_64.rpm xz-5.4.4-3.azl3.x86_64.rpm xz-devel-5.4.4-3.azl3.x86_64.rpm diff --git a/toolkit/resources/manifests/package/toolchain_aarch64.txt b/toolkit/resources/manifests/package/toolchain_aarch64.txt index 3df14d35e96..6d5c4218046 100644 --- a/toolkit/resources/manifests/package/toolchain_aarch64.txt +++ b/toolkit/resources/manifests/package/toolchain_aarch64.txt @@ -542,7 +542,7 @@ python3-flit-core-3.9.0-1.azl3.noarch.rpm python3-gpg-1.23.2-2.azl3.aarch64.rpm python3-jinja2-3.1.2-3.azl3.noarch.rpm python3-libcap-ng-0.8.4-1.azl3.aarch64.rpm -python3-libmount-2.40.2-6.azl3.aarch64.rpm +python3-libmount-2.40.2-7.azl3.aarch64.rpm python3-libs-3.12.15-1.azl3.aarch64.rpm python3-libxml2-2.11.5-11.azl3.aarch64.rpm python3-lxml-4.9.3-3.azl3.aarch64.rpm @@ -599,11 +599,11 @@ texinfo-7.0.3-1.azl3.aarch64.rpm texinfo-debuginfo-7.0.3-1.azl3.aarch64.rpm unzip-6.0-22.azl3.aarch64.rpm unzip-debuginfo-6.0-22.azl3.aarch64.rpm -util-linux-2.40.2-6.azl3.aarch64.rpm -util-linux-debuginfo-2.40.2-6.azl3.aarch64.rpm -util-linux-devel-2.40.2-6.azl3.aarch64.rpm -util-linux-lang-2.40.2-6.azl3.aarch64.rpm -util-linux-libs-2.40.2-6.azl3.aarch64.rpm +util-linux-2.40.2-7.azl3.aarch64.rpm +util-linux-debuginfo-2.40.2-7.azl3.aarch64.rpm +util-linux-devel-2.40.2-7.azl3.aarch64.rpm +util-linux-lang-2.40.2-7.azl3.aarch64.rpm +util-linux-libs-2.40.2-7.azl3.aarch64.rpm which-2.21-8.azl3.aarch64.rpm which-debuginfo-2.21-8.azl3.aarch64.rpm xz-5.4.4-3.azl3.aarch64.rpm diff --git a/toolkit/resources/manifests/package/toolchain_x86_64.txt b/toolkit/resources/manifests/package/toolchain_x86_64.txt index 1d179e6ede8..d7c21ba66d9 100644 --- a/toolkit/resources/manifests/package/toolchain_x86_64.txt +++ b/toolkit/resources/manifests/package/toolchain_x86_64.txt @@ -550,7 +550,7 @@ python3-flit-core-3.9.0-1.azl3.noarch.rpm python3-gpg-1.23.2-2.azl3.x86_64.rpm python3-jinja2-3.1.2-3.azl3.noarch.rpm python3-libcap-ng-0.8.4-1.azl3.x86_64.rpm -python3-libmount-2.40.2-6.azl3.x86_64.rpm +python3-libmount-2.40.2-7.azl3.x86_64.rpm python3-libs-3.12.15-1.azl3.x86_64.rpm python3-libxml2-2.11.5-11.azl3.x86_64.rpm python3-lxml-4.9.3-3.azl3.x86_64.rpm @@ -607,11 +607,11 @@ texinfo-7.0.3-1.azl3.x86_64.rpm texinfo-debuginfo-7.0.3-1.azl3.x86_64.rpm unzip-6.0-22.azl3.x86_64.rpm unzip-debuginfo-6.0-22.azl3.x86_64.rpm -util-linux-2.40.2-6.azl3.x86_64.rpm -util-linux-debuginfo-2.40.2-6.azl3.x86_64.rpm -util-linux-devel-2.40.2-6.azl3.x86_64.rpm -util-linux-lang-2.40.2-6.azl3.x86_64.rpm -util-linux-libs-2.40.2-6.azl3.x86_64.rpm +util-linux-2.40.2-7.azl3.x86_64.rpm +util-linux-debuginfo-2.40.2-7.azl3.x86_64.rpm +util-linux-devel-2.40.2-7.azl3.x86_64.rpm +util-linux-lang-2.40.2-7.azl3.x86_64.rpm +util-linux-libs-2.40.2-7.azl3.x86_64.rpm which-2.21-8.azl3.x86_64.rpm which-debuginfo-2.21-8.azl3.x86_64.rpm xz-5.4.4-3.azl3.x86_64.rpm From b483464d7932f2e93758ba8af54c1d1b80231ced Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Mon, 19 Oct 2026 10:09:59 +0000 Subject: [PATCH 2/5] Patch util-linux for CVE-2026-53612 --- SPECS/util-linux/CVE-2026-53612.patch | 284 ++++++++++++++++++++++++++ SPECS/util-linux/util-linux.spec | 3 +- 2 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 SPECS/util-linux/CVE-2026-53612.patch diff --git a/SPECS/util-linux/CVE-2026-53612.patch b/SPECS/util-linux/CVE-2026-53612.patch new file mode 100644 index 00000000000..fc3fe308952 --- /dev/null +++ b/SPECS/util-linux/CVE-2026-53612.patch @@ -0,0 +1,284 @@ +From ada18da7c31f1d143356b2f1b61db3da1cbcb23e Mon Sep 17 00:00:00 2001 +From: Karel Zak +Date: Tue, 16 Jun 2026 11:13:54 +0200 +Subject: [PATCH 1/2] 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. + +CVE-2026-53613 + +Signed-off-by: Karel Zak +(cherry picked from commit 78a860982e036f38fe9c0b3344998df5ac2c2ff5) +--- + libmount/src/context.c | 43 ++++++++++++++++++++++++++++++++ + 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, 77 insertions(+), 1 deletion(-) + +diff --git a/libmount/src/context.c b/libmount/src/context.c +index 3d57dc8..a2da0d2 100644 +--- a/libmount/src/context.c ++++ b/libmount/src/context.c +@@ -37,6 +37,7 @@ + */ + + #include "mountP.h" ++#include "fileutils.h" + #include "strutils.h" + #include "fileutils.h" + #include "namespace.h" +@@ -68,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); +@@ -174,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) +@@ -399,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 b5c7c94..08a5920 100644 +--- a/libmount/src/context_mount.c ++++ b/libmount/src/context_mount.c +@@ -743,6 +743,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 af84f75..116046b 100644 +--- a/libmount/src/hook_mount.c ++++ b/libmount/src/hook_mount.c +@@ -541,9 +541,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 7e62864..ab8c2c2 100644 +--- a/libmount/src/hook_mount_legacy.c ++++ b/libmount/src/hook_mount_legacy.c +@@ -248,6 +248,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 9e00f7e..1ed63a2 100644 +--- a/libmount/src/mountP.h ++++ b/libmount/src/mountP.h +@@ -443,6 +443,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 */ + }; +@@ -640,6 +642,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); + +-- +2.45.4 + + +From 6749dc1c302b06feff10504efe24ca395868c0aa Mon Sep 17 00:00:00 2001 +From: Karel Zak +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 +Signed-off-by: Karel Zak +(cherry picked from commit 24da33905c7115c4cbccd0afb2a469804e96467a) +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/util-linux/util-linux/commit/0b010025a0e429bc80355c94db86a843395d49e2.patch 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 + + #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 + diff --git a/SPECS/util-linux/util-linux.spec b/SPECS/util-linux/util-linux.spec index ee0388393b2..e714d45d7d7 100644 --- a/SPECS/util-linux/util-linux.spec +++ b/SPECS/util-linux/util-linux.spec @@ -24,6 +24,7 @@ Patch4: CVE-2026-76642.patch Patch5: CVE-2026-78408.patch Patch6: CVE-2026-78410.patch Patch7: CVE-2026-53615.patch +Patch8: CVE-2026-53612.patch BuildRequires: audit-devel BuildRequires: libcap-ng-devel BuildRequires: libselinux-devel @@ -180,7 +181,7 @@ rm -rf %{buildroot}/lib/systemd/system %changelog * Fri Oct 16 2026 Azure Linux Security Servicing Account - 2.40.2-7 -- Patch for CVE-2026-53615 +- Patch for CVE-2026-53615, CVE-2026-53612 * Mon Sep 07 2026 Azure Linux Security Servicing Account - 2.40.2-6 - Patch for CVE-2026-78410, CVE-2026-78408, CVE-2026-76642 From a3d5ffd0d35ecbc2be61e8b197725dee062e113c Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Tue, 20 Oct 2026 11:22:02 +0000 Subject: [PATCH 3/5] Patch util-linux for CVE-2026-53613 --- SPECS/util-linux/CVE-2026-53613.patch | 148 ++++++++++++++++++++++++++ SPECS/util-linux/util-linux.spec | 3 +- 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 SPECS/util-linux/CVE-2026-53613.patch diff --git a/SPECS/util-linux/CVE-2026-53613.patch b/SPECS/util-linux/CVE-2026-53613.patch new file mode 100644 index 00000000000..89beee2fde5 --- /dev/null +++ b/SPECS/util-linux/CVE-2026-53613.patch @@ -0,0 +1,148 @@ +From 5470938a21c1750a1829c540e26561199cfecf42 Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Mon, 19 Oct 2026 18:49:36 +0000 +Subject: [PATCH] libmount: backport non-root subdir/idmap target-fd safety + fixes + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport from existing Build 1230221 of https://github.com/util-linux/util-linux/commit/8b6454b84e11.patch https://github.com/util-linux/util-linux/commit/4ca5d5c502ad.patch https://github.com/util-linux/util-linux/commit/a9508d216eb8977979102782672e0470cc9bf2a2.patch +--- + libmount/src/context.c | 29 ++++++++++++++++++++++++++++- + libmount/src/hook_idmap.c | 25 ++++++++++++++++++++++--- + libmount/src/hook_subdir.c | 6 ++++++ + sys-utils/mount.8.adoc | 4 ++++ + 4 files changed, 60 insertions(+), 4 deletions(-) + +diff --git a/libmount/src/context.c b/libmount/src/context.c +index a2da0d2..d5de73b 100644 +--- a/libmount/src/context.c ++++ b/libmount/src/context.c +@@ -413,9 +413,33 @@ int mnt_context_reopen_target_fd(struct libmnt_context *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; + } + +@@ -426,9 +450,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(CXT, ul_debugobj(cxt,"open target fd=%d [%s]", ++ cxt->fd_target, target)); ++ } + } + return cxt->fd_target; + } +diff --git a/libmount/src/hook_idmap.c b/libmount/src/hook_idmap.c +index cfb649e..fc6bbfc 100644 +--- a/libmount/src/hook_idmap.c ++++ b/libmount/src/hook_idmap.c +@@ -352,12 +352,31 @@ static int hook_mount_post( + + /* Attach the idmapped mount. */ + if (is_private) { ++ unsigned int mmflags = MOVE_MOUNT_F_EMPTY_PATH; ++ + /* Unmount the old, non-idmapped mount we just cloned and idmapped. */ + umount2(target, MNT_DETACH); + +- rc = move_mount(fd_tree, "", -1, target, MOVE_MOUNT_F_EMPTY_PATH); +- if (rc) +- DBG(HOOK, ul_debugobj(hs, " failed to set move mount")); ++ if (mnt_context_target_fd_required(cxt)) { ++ int fd_tgt = mnt_context_get_target_fd(cxt); ++ ++ if (fd_tgt < 0) { ++ rc = -errno; ++ goto done; ++ } ++ mmflags |= MOVE_MOUNT_T_EMPTY_PATH; ++ rc = move_mount(fd_tree, "", fd_tgt, "", mmflags); ++ } else ++ rc = move_mount(fd_tree, "", AT_FDCWD, target, mmflags); ++ ++ if (rc == 0) ++ rc = mnt_context_reopen_target_fd(cxt); ++ if (rc < 0) { ++ mnt_context_syscall_save_status(cxt, "move_mount", 0); ++ if (!mnt_context_read_mesgs(cxt, fd_tree)) { ++ DBG(HOOK, ul_debugobj(hs, " failed to set move mount")); ++ } ++ } + } + done: + if (is_private) +diff --git a/libmount/src/hook_subdir.c b/libmount/src/hook_subdir.c +index 2a2b8c2..672db5b 100644 +--- a/libmount/src/hook_subdir.c ++++ b/libmount/src/hook_subdir.c +@@ -290,6 +290,12 @@ static int hook_mount_pre( + if (!hsd) + return 0; + ++ if (mnt_context_target_fd_required(cxt)) { ++ DBG(HOOK, ul_debugobj(hs, ++ "subdir mount refused for non-root user")); ++ return -ENOTSUP; ++ } ++ + /* create unhared temporary target */ + hsd->org_target = strdup(mnt_fs_get_target(cxt->fs)); + if (!hsd->org_target) +diff --git a/sys-utils/mount.8.adoc b/sys-utils/mount.8.adoc +index ba8b45a..1befff8 100644 +--- a/sys-utils/mount.8.adoc ++++ b/sys-utils/mount.8.adoc +@@ -186,6 +186,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.) 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: +@@ -717,6 +719,8 @@ Allow mounting sub-directory from a filesystem instead of the root directory. Fo + + + Note that this feature will not work in session with an unshared private mount namespace (after *unshare --mount*) on old kernels or with *mount*(8) without support for file-descriptors-based mount kernel API. In this case, you need *unshare --mount --propagation shared*. + + ++For unprivileged (non-root) users, this feature requires Linux >= 6.15 where the subdirectory is opened directly on the detached mount tree. +++ + This feature is EXPERIMENTAL. + + *X-mount.owner*=_username_|_UID_, *X-mount.group*=_group_|_GID_:: +-- +2.45.4 + diff --git a/SPECS/util-linux/util-linux.spec b/SPECS/util-linux/util-linux.spec index e714d45d7d7..da0e09c9b9c 100644 --- a/SPECS/util-linux/util-linux.spec +++ b/SPECS/util-linux/util-linux.spec @@ -25,6 +25,7 @@ Patch5: CVE-2026-78408.patch Patch6: CVE-2026-78410.patch Patch7: CVE-2026-53615.patch Patch8: CVE-2026-53612.patch +Patch9: CVE-2026-53613.patch BuildRequires: audit-devel BuildRequires: libcap-ng-devel BuildRequires: libselinux-devel @@ -181,7 +182,7 @@ rm -rf %{buildroot}/lib/systemd/system %changelog * Fri Oct 16 2026 Azure Linux Security Servicing Account - 2.40.2-7 -- Patch for CVE-2026-53615, CVE-2026-53612 +- Patch for CVE-2026-53615, CVE-2026-53612, CVE-2026-53613 * Mon Sep 07 2026 Azure Linux Security Servicing Account - 2.40.2-6 - Patch for CVE-2026-78410, CVE-2026-78408, CVE-2026-76642 From e7579996d085861f691ee3b3e026a2a9c097ffdb Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Wed, 21 Oct 2026 18:05:48 +0000 Subject: [PATCH 4/5] Patch util-linux for CVE-2026-53614 --- SPECS/util-linux/CVE-2026-53614.patch | 84 +++++++++++++++++++++++++++ SPECS/util-linux/util-linux.spec | 3 +- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 SPECS/util-linux/CVE-2026-53614.patch diff --git a/SPECS/util-linux/CVE-2026-53614.patch b/SPECS/util-linux/CVE-2026-53614.patch new file mode 100644 index 00000000000..d9227e4ec17 --- /dev/null +++ b/SPECS/util-linux/CVE-2026-53614.patch @@ -0,0 +1,84 @@ +From bab1fcac296bf7a9b50fe94dc116cdccb2a12865 Mon Sep 17 00:00:00 2001 +From: Karel Zak +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 +Signed-off-by: Karel Zak +(cherry picked from commit 9cbfb823500f510b34767edabd3ffd5b436987b4) +Signed-off-by: Azure Linux Security Servicing Account +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 + diff --git a/SPECS/util-linux/util-linux.spec b/SPECS/util-linux/util-linux.spec index da0e09c9b9c..ff0f4739a65 100644 --- a/SPECS/util-linux/util-linux.spec +++ b/SPECS/util-linux/util-linux.spec @@ -26,6 +26,7 @@ Patch6: CVE-2026-78410.patch Patch7: CVE-2026-53615.patch Patch8: CVE-2026-53612.patch Patch9: CVE-2026-53613.patch +Patch10: CVE-2026-53614.patch BuildRequires: audit-devel BuildRequires: libcap-ng-devel BuildRequires: libselinux-devel @@ -182,7 +183,7 @@ rm -rf %{buildroot}/lib/systemd/system %changelog * Fri Oct 16 2026 Azure Linux Security Servicing Account - 2.40.2-7 -- Patch for CVE-2026-53615, CVE-2026-53612, CVE-2026-53613 +- Patch for CVE-2026-53615, CVE-2026-53612, CVE-2026-53613, CVE-2026-53614 * Mon Sep 07 2026 Azure Linux Security Servicing Account - 2.40.2-6 - Patch for CVE-2026-78410, CVE-2026-78408, CVE-2026-76642 From 840c5bed6a68850829a6d2718dbe0a5339cd5ee3 Mon Sep 17 00:00:00 2001 From: vijayender Putta Date: Fri, 23 Oct 2026 07:00:59 +0000 Subject: [PATCH 5/5] Update patches for CVE-2026-53612, CVE-2026-53613, CVE-2026-53614 --- SPECS/util-linux/CVE-2026-53612.patch | 195 +----------------- SPECS/util-linux/CVE-2026-53613.patch | 273 +++++++++++++++----------- SPECS/util-linux/CVE-2026-53614.patch | 2 +- SPECS/util-linux/util-linux.spec | 8 +- 4 files changed, 161 insertions(+), 317 deletions(-) diff --git a/SPECS/util-linux/CVE-2026-53612.patch b/SPECS/util-linux/CVE-2026-53612.patch index fc3fe308952..f62b49f96a8 100644 --- a/SPECS/util-linux/CVE-2026-53612.patch +++ b/SPECS/util-linux/CVE-2026-53612.patch @@ -1,195 +1,4 @@ -From ada18da7c31f1d143356b2f1b61db3da1cbcb23e Mon Sep 17 00:00:00 2001 -From: Karel Zak -Date: Tue, 16 Jun 2026 11:13:54 +0200 -Subject: [PATCH 1/2] 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. - -CVE-2026-53613 - -Signed-off-by: Karel Zak -(cherry picked from commit 78a860982e036f38fe9c0b3344998df5ac2c2ff5) ---- - libmount/src/context.c | 43 ++++++++++++++++++++++++++++++++ - 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, 77 insertions(+), 1 deletion(-) - -diff --git a/libmount/src/context.c b/libmount/src/context.c -index 3d57dc8..a2da0d2 100644 ---- a/libmount/src/context.c -+++ b/libmount/src/context.c -@@ -37,6 +37,7 @@ - */ - - #include "mountP.h" -+#include "fileutils.h" - #include "strutils.h" - #include "fileutils.h" - #include "namespace.h" -@@ -68,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); -@@ -174,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) -@@ -399,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 b5c7c94..08a5920 100644 ---- a/libmount/src/context_mount.c -+++ b/libmount/src/context_mount.c -@@ -743,6 +743,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 af84f75..116046b 100644 ---- a/libmount/src/hook_mount.c -+++ b/libmount/src/hook_mount.c -@@ -541,9 +541,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 7e62864..ab8c2c2 100644 ---- a/libmount/src/hook_mount_legacy.c -+++ b/libmount/src/hook_mount_legacy.c -@@ -248,6 +248,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 9e00f7e..1ed63a2 100644 ---- a/libmount/src/mountP.h -+++ b/libmount/src/mountP.h -@@ -443,6 +443,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 */ - }; -@@ -640,6 +642,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); - --- -2.45.4 - - -From 6749dc1c302b06feff10504efe24ca395868c0aa Mon Sep 17 00:00:00 2001 +From 897a08c2b11dfa66975c3d24d63c7bf5f5be1a7c Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Tue, 16 Jun 2026 11:15:19 +0200 Subject: [PATCH 2/2] libmount: use fd-based fchownat/chmod in hook_owner @@ -212,7 +21,7 @@ Reported-by: Xinyao Hu Signed-off-by: Karel Zak (cherry picked from commit 24da33905c7115c4cbccd0afb2a469804e96467a) Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: https://github.com/util-linux/util-linux/commit/0b010025a0e429bc80355c94db86a843395d49e2.patch https://github.com/util-linux/util-linux/commit/897a08c2b11dfa66975c3d24d63c7bf5f5be1a7c.patch +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/SPECS/util-linux/CVE-2026-53613.patch b/SPECS/util-linux/CVE-2026-53613.patch index 89beee2fde5..a9939425de9 100644 --- a/SPECS/util-linux/CVE-2026-53613.patch +++ b/SPECS/util-linux/CVE-2026-53613.patch @@ -1,148 +1,183 @@ -From 5470938a21c1750a1829c540e26561199cfecf42 Mon Sep 17 00:00:00 2001 -From: AllSpark -Date: Mon, 19 Oct 2026 18:49:36 +0000 -Subject: [PATCH] libmount: backport non-root subdir/idmap target-fd safety - fixes +From 0b010025a0e429bc80355c94db86a843395d49e2 Mon Sep 17 00:00:00 2001 +From: Karel Zak +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 +(cherry picked from commit 78a860982e036f38fe9c0b3344998df5ac2c2ff5) Signed-off-by: Azure Linux Security Servicing Account -Upstream-reference: AI Backport from existing Build 1230221 of https://github.com/util-linux/util-linux/commit/8b6454b84e11.patch https://github.com/util-linux/util-linux/commit/4ca5d5c502ad.patch https://github.com/util-linux/util-linux/commit/a9508d216eb8977979102782672e0470cc9bf2a2.patch +Upstream-reference: https://github.com/util-linux/util-linux/commit/0b010025a0e429bc80355c94db86a843395d49e2.patch --- - libmount/src/context.c | 29 ++++++++++++++++++++++++++++- - libmount/src/hook_idmap.c | 25 ++++++++++++++++++++++--- - libmount/src/hook_subdir.c | 6 ++++++ - sys-utils/mount.8.adoc | 4 ++++ - 4 files changed, 60 insertions(+), 4 deletions(-) + 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 -index a2da0d2..d5de73b 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c -@@ -413,9 +413,33 @@ int mnt_context_reopen_target_fd(struct libmnt_context *cxt) +@@ -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; + } - if (!mnt_context_target_fd_required(cxt)) - return 0; ++int mnt_context_target_fd_required(struct libmnt_context *cxt) ++{ ++ return mnt_context_is_restricted(cxt); ++} + -+ DBG(CXT, ul_debugobj(cxt,"reopen target fd")); ++int mnt_context_reopen_target_fd(struct libmnt_context *cxt) ++{ ++ assert(cxt); + - mnt_context_close_target_fd(cxt); - if (mnt_context_get_target_fd(cxt) < 0) - return -errno; ++ 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; ++} + -+ /* 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; ++int mnt_context_get_target_fd(struct libmnt_context *cxt) ++{ ++ assert(cxt); + -+ if (mnt_id_from_fd(cxt->fd_target, NULL, &id) == 0 -+ && id != cxt->fs->id) { -+ const char *tgt = mnt_fs_get_target(cxt->fs); ++ if (cxt->fd_target < 0) { ++ const char *target = 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)); ++ if (target) ++ cxt->fd_target = ul_open_no_symlinks(target, ++ O_PATH | O_CLOEXEC, 0); + } ++ return cxt->fd_target; ++} + - return 0; - } - -@@ -426,9 +450,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(CXT, ul_debugobj(cxt,"open target fd=%d [%s]", -+ cxt->fd_target, target)); -+ } - } - return cxt->fd_target; - } -diff --git a/libmount/src/hook_idmap.c b/libmount/src/hook_idmap.c -index cfb649e..fc6bbfc 100644 ---- a/libmount/src/hook_idmap.c -+++ b/libmount/src/hook_idmap.c -@@ -352,12 +352,31 @@ static int hook_mount_post( ++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); - /* Attach the idmapped mount. */ - if (is_private) { -+ unsigned int mmflags = MOVE_MOUNT_F_EMPTY_PATH; ++ 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; ++ } + - /* Unmount the old, non-idmapped mount we just cloned and idmapped. */ + 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(fd_tree, "", -1, target, MOVE_MOUNT_F_EMPTY_PATH); -- if (rc) -- DBG(HOOK, ul_debugobj(hs, " failed to set move mount")); -+ if (mnt_context_target_fd_required(cxt)) { -+ int fd_tgt = mnt_context_get_target_fd(cxt); +- 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_tgt < 0) { -+ rc = -errno; -+ goto done; -+ } -+ mmflags |= MOVE_MOUNT_T_EMPTY_PATH; -+ rc = move_mount(fd_tree, "", fd_tgt, "", mmflags); -+ } else -+ rc = move_mount(fd_tree, "", AT_FDCWD, target, mmflags); ++ 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); + -+ if (rc == 0) -+ rc = mnt_context_reopen_target_fd(cxt); -+ if (rc < 0) { -+ mnt_context_syscall_save_status(cxt, "move_mount", 0); -+ if (!mnt_context_read_mesgs(cxt, fd_tree)) { -+ DBG(HOOK, ul_debugobj(hs, " failed to set move mount")); -+ } -+ } - } - done: - if (is_private) -diff --git a/libmount/src/hook_subdir.c b/libmount/src/hook_subdir.c -index 2a2b8c2..672db5b 100644 ---- a/libmount/src/hook_subdir.c -+++ b/libmount/src/hook_subdir.c -@@ -290,6 +290,12 @@ static int hook_mount_pre( - if (!hsd) - return 0; + hookset_set_syscall_status(cxt, "move_mount", rc == 0); -+ if (mnt_context_target_fd_required(cxt)) { -+ DBG(HOOK, ul_debugobj(hs, -+ "subdir mount refused for non-root user")); -+ return -ENOTSUP; ++ if (rc == 0) { ++ /* re-open to point to the mounted filesystem root */ ++ rc = mnt_context_reopen_target_fd(cxt); + } + - /* create unhared temporary target */ - hsd->org_target = strdup(mnt_fs_get_target(cxt->fs)); - if (!hsd->org_target) -diff --git a/sys-utils/mount.8.adoc b/sys-utils/mount.8.adoc -index ba8b45a..1befff8 100644 ---- a/sys-utils/mount.8.adoc -+++ b/sys-utils/mount.8.adoc -@@ -186,6 +186,8 @@ For more details, see *fstab*(5). Only the user that mounted a filesystem can un + return rc == 0 ? 0 : -errno; + } - 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.) for compatibility with some network filesystems. +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; + } -+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. ++ /* re-open to point to the mounted filesystem root */ ++ rc = mnt_context_reopen_target_fd(cxt); + - === Bind mount operation + 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 */ - Remount part of the file hierarchy somewhere else. The call is: -@@ -717,6 +719,8 @@ Allow mounting sub-directory from a filesystem instead of the root directory. Fo - + - Note that this feature will not work in session with an unshared private mount namespace (after *unshare --mount*) on old kernels or with *mount*(8) without support for file-descriptors-based mount kernel API. In this case, you need *unshare --mount --propagation shared*. - + -+For unprivileged (non-root) users, this feature requires Linux >= 6.15 where the subdirectory is opened directly on the detached mount tree. -++ - This feature is EXPERIMENTAL. ++ 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); - *X-mount.owner*=_username_|_UID_, *X-mount.group*=_group_|_GID_:: --- -2.45.4 - diff --git a/SPECS/util-linux/CVE-2026-53614.patch b/SPECS/util-linux/CVE-2026-53614.patch index d9227e4ec17..f36c6f8794b 100644 --- a/SPECS/util-linux/CVE-2026-53614.patch +++ b/SPECS/util-linux/CVE-2026-53614.patch @@ -1,4 +1,4 @@ -From bab1fcac296bf7a9b50fe94dc116cdccb2a12865 Mon Sep 17 00:00:00 2001 +From cc81bbcec598cb91f0eb8456282f33eed820ed5f Mon Sep 17 00:00:00 2001 From: Karel Zak Date: Tue, 16 Jun 2026 10:58:32 +0200 Subject: [PATCH] libmount: fix SUID bypass via LIBMOUNT_FORCE_MOUNT2 and diff --git a/SPECS/util-linux/util-linux.spec b/SPECS/util-linux/util-linux.spec index ff0f4739a65..af6eaed0bc0 100644 --- a/SPECS/util-linux/util-linux.spec +++ b/SPECS/util-linux/util-linux.spec @@ -23,10 +23,10 @@ Patch3: CVE-2026-13595.patch Patch4: CVE-2026-76642.patch Patch5: CVE-2026-78408.patch Patch6: CVE-2026-78410.patch -Patch7: CVE-2026-53615.patch -Patch8: CVE-2026-53612.patch -Patch9: CVE-2026-53613.patch -Patch10: CVE-2026-53614.patch +Patch7: CVE-2026-53612.patch +Patch8: CVE-2026-53613.patch +Patch9: CVE-2026-53614.patch +Patch10: CVE-2026-53615.patch BuildRequires: audit-devel BuildRequires: libcap-ng-devel BuildRequires: libselinux-devel