From: Karel Zak Date: Tue, 16 Jun 2026 09:15:19 +0000 (+0200) Subject: libmount: use fd-based fchownat/chmod in hook_owner X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=24da33905c7115c4cbccd0afb2a469804e96467a;p=thirdparty%2Futil-linux.git 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 --- diff --git a/libmount/src/hook_owner.c b/libmount/src/hook_owner.c index abd21c0ac..e8e92c2f3 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_OBJ(CXT, cxt, ul_debug(" lchown(%s, %u, %u)", target, hd->owner, hd->group)); - if (lchown(target, hd->owner, hd->group) == -1) - return -MNT_ERR_CHOWN; + DBG_OBJ(CXT, cxt, ul_debug(" 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_OBJ(CXT, cxt, ul_debug(" 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_OBJ(CXT, cxt, ul_debug(" 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; }