]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: prefer linkat(AT_EMPTY_PATH) over /proc/self/fd/ shenanigans
authorMike Yuan <me@yhndnzj.com>
Tue, 27 May 2025 00:10:07 +0000 (02:10 +0200)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 27 May 2025 03:19:22 +0000 (12:19 +0900)
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...

src/basic/fs-util.c

index 5db9572ba558fe0aca28f49812685f9d9a19ab6f..a845a1e70b2bd4b4fbb0d071496e74d80836a5e1 100644 (file)
@@ -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) {