From 24f453dd39c25e00527d0ed0a1e9fefa6295999b Mon Sep 17 00:00:00 2001 From: Anoop C S Date: Tue, 27 Aug 2024 15:50:44 +0530 Subject: [PATCH] client: Resolve symlink from dirfd for empty pathname 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 --- src/client/Client.cc | 6 ++++++ src/test/libcephfs/test.cc | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/client/Client.cc b/src/client/Client.cc index 9c6785fe65e..f8373095b38 100644 --- a/src/client/Client.cc +++ b/src/client/Client.cc @@ -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); diff --git a/src/test/libcephfs/test.cc b/src/test/libcephfs/test.cc index b51689ab263..6f10d2bbd4e 100644 --- a/src/test/libcephfs/test.cc +++ b/src/test/libcephfs/test.cc @@ -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)); -- 2.39.5