]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
client: Resolve symlink from dirfd for empty pathname 59503/head
authorAnoop C S <anoopcs@cryptolab.net>
Tue, 27 Aug 2024 10:20:44 +0000 (15:50 +0530)
committerAnoop C S <anoopcs@cryptolab.net>
Tue, 15 Oct 2024 10:51:15 +0000 (16:21 +0530)
man readlinkat(2)[1] points at a special case for readlinkat() syscall
as follows:

. . .
Since Linux 2.6.39, pathname can be an empty string, in which case the
call operates on the symbolic link referred to by dirfd (which should
have been obtained using open(2) with the O_PATH and O_NOFOLLOW flags).
. . .

man open(2)[2] further explains the need for such a special case when
a symlink is opened with O_PATH and O_NOFOLLOW:

. . .
If  pathname is a symbolic link and the O_NOFOLLOW flag is also
specified, then the call returns a file descriptor referring to the
symbolic link.  This file descriptor can be used as the dirfd argument
in calls to fchownat(2), fstatat(2), linkat(2), and readlinkat(2) with
an empty pathname to have the calls operate on the symbolic link.
. . .

Accordingly have a check to resolve symlinks out of dirfd when empty
pathnames are encountered within readlinkat(). In addition to that
match the standard file system behavior to return ENOENT instead of
EINVAL when the inode pointed to by dirfd is not a symbolic link with
empty pathnames.

Fixes: https://tracker.ceph.com/issues/67833
[1] https://www.man7.org/linux/man-pages/man2/readlinkat.2.html
[2] https://www.man7.org/linux/man-pages/man2/open.2.html

Signed-off-by: Anoop C S <anoopcs@cryptolab.net>
src/client/Client.cc
src/test/libcephfs/test.cc

index 9c6785fe65e5afc92966d655d455c546e7174c57..f8373095b38c17a2775801b2b43c5a6623673f4c 100644 (file)
@@ -7957,6 +7957,12 @@ int Client::readlinkat(int dirfd, const char *relpath, char *buf, loff_t size, c
     return r;
   }
 
+  if (!strcmp(relpath, "")) {
+    if (!dirinode.get()->is_symlink())
+      return -CEPHFS_ENOENT;
+    return _readlink(dirinode.get(), buf, size);
+  }
+
   InodeRef in;
   filepath path(relpath);
   r = path_walk(path, &in, perms, false, 0, dirinode);
index b51689ab2637e67edf65617e8ef53774ceb13f5f..6f10d2bbd4e0680b981c00ed8a3be2837caf1dc8 100644 (file)
@@ -3019,6 +3019,18 @@ TEST(LibCephFS, Readlinkat) {
   ASSERT_EQ(0, memcmp(target, rel_file_path, target_len));
 
   ASSERT_EQ(0, ceph_close(cmount, fd));
+#if defined(__linux__) && defined(O_PATH)
+  // test readlinkat with empty pathname relative to O_PATH|O_NOFOLLOW fd
+  fd = ceph_open(cmount, link_path, O_PATH | O_NOFOLLOW, 0);
+  ASSERT_LE(0, fd);
+  size_t link_target_len = strlen(rel_file_path);
+  char link_target[link_target_len+1];
+  ASSERT_EQ(link_target_len, ceph_readlinkat(cmount, fd, "", link_target, link_target_len));
+  link_target[link_target_len] = '\0';
+  ASSERT_EQ(0, memcmp(link_target, rel_file_path, link_target_len));
+  ASSERT_EQ(0, ceph_close(cmount, fd));
+#endif /* __linux */
+
   ASSERT_EQ(0, ceph_unlink(cmount, link_path));
   ASSERT_EQ(0, ceph_unlink(cmount, file_path));
   ASSERT_EQ(0, ceph_rmdir(cmount, dir_path));