From: Mike Yuan Date: Tue, 27 May 2025 00:10:07 +0000 (+0200) Subject: fs-util: prefer linkat(AT_EMPTY_PATH) over /proc/self/fd/ shenanigans X-Git-Tag: v258-rc1~482 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7f436569c1bae669d306d79d782ab82d04da4a4f;p=thirdparty%2Fsystemd.git fs-util: prefer linkat(AT_EMPTY_PATH) over /proc/self/fd/ shenanigans The permission check got relaxed in kernel v6.10, so let's switch the fallback order around. This also effectively reverts 94d94f0c0a7d28816c815dc9770cc659769fe980, as I just realized that link_fd() involves multiple paths and we can't tell which one tripped ENOENT... --- diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 5db9572ba55..a845a1e70b2 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1343,26 +1343,25 @@ int xopenat_lock_full( } int link_fd(int fd, int newdirfd, const char *newpath) { - int r, k; + int r; assert(fd >= 0); assert(newdirfd >= 0 || newdirfd == AT_FDCWD); assert(newpath); - /* Try linking via /proc/self/fd/ first. */ - r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); - if (r != -ENOENT) - return r; - - /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a - * more recent kernel, but does not require /proc/ mounted) */ - k = proc_mounted(); - if (k < 0) - return r; - if (k > 0) - return -EBADF; + /* Try to link via AT_EMPTY_PATH first. This fails with ENOENT if we don't have CAP_DAC_READ_SEARCH + * on kernels < 6.10, in which case we'd then resort to /proc/self/fd/ dance. + * + * See also: https://github.com/torvalds/linux/commit/42bd2af5950456d46fdaa91c3a8fb02e680f19f5 */ + r = RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); + if (r == -ENOENT) { + r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); + if (r == -ENOENT && proc_mounted() == 0) /* No proc_fd_enoent_errno() here because we don't + know if it's the target path that's missing. */ + return -ENOSYS; + } - return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); + return r; } int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {