@@ -15,6 +15,8 @@
sigset_t local_saved_sigmask;
struct open_how my_how;
char *pseudo_path;
+ const char *stat_path, *record_path;
+ int stat_flags;
/* Validate parameters */
if (!how || size < sizeof(struct open_how)) {
@@ -41,6 +43,18 @@
memcpy(&my_how, how, size);
+ stat_path = record_path = pseudo_path;
+ stat_flags = (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0;
+ /* An empty path is passed to the kernel, which only accepts it with
+ * O_EMPTYPATH, to reopen dirfd. Look at and record dirfd instead.
+ */
+ if (path && !*path) {
+ stat_path = "";
+ stat_flags |= AT_EMPTY_PATH;
+ record_path = pseudo_root_path(__func__, __LINE__, dirfd,
+ dirfd == AT_FDCWD ? "." : "", 0);
+ }
+
/* mask out mode bits appropriately */
my_how.mode = my_how.mode & ~pseudo_umask;
@@ -81,7 +95,7 @@
/* note "else" in #ifdef O_TMPFILE above */
if (my_how.flags & O_CREAT) {
save_errno = errno;
- rc = base_fstatat(dirfd, pseudo_path, &buf, (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+ rc = base_fstatat(dirfd, stat_path, &buf, stat_flags);
existed = (rc != -1);
if (!existed)
pseudo_debug(PDBGF_FILE, "openat2_creat: %s -> 0%lld\n", pseudo_path, my_how.mode);
@@ -94,7 +108,7 @@
*/
if (!(my_how.flags & O_NONBLOCK) && ((my_how.flags & (O_WRONLY | O_RDONLY | O_RDWR)) != O_RDWR)) {
save_errno = errno;
- rc = base_fstatat(dirfd, pseudo_path, &buf, (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+ rc = base_fstatat(dirfd, stat_path, &buf, stat_flags);
if (rc != -1 && S_ISFIFO(buf.st_mode)) {
overly_magic_nonblocking = 1;
}
@@ -151,7 +165,7 @@
return rc;
}
#endif
- stat_rc = base_fstatat(dirfd, pseudo_path, &buf, (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+ stat_rc = base_fstatat(dirfd, stat_path, &buf, stat_flags);
pseudo_debug(PDBGF_FILE, "openat2(path %s), flags %lld, stat rc %d, stat mode %o\n",
pseudo_path, my_how.flags, stat_rc, buf.st_mode);
@@ -168,11 +182,11 @@
}
pseudo_client_path_unlock(path_lock_fd);
}
- pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, pseudo_path, &buf);
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, record_path, &buf);
} else {
pseudo_debug(PDBGF_FILE, "openat2 (fd %d, path %d/%s, flags %lld) succeeded, but stat failed (%s).\n",
rc, dirfd, pseudo_path, my_how.flags, strerror(errno));
- pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, pseudo_path, 0);
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, record_path, 0);
}
errno = save_errno;
}
@@ -1,6 +1,6 @@
/*
- * Test opening an empty path, which only names dirfd with O_EMPTYPATH
- * (Linux 7.2), the way the kernel does.
+ * Test opening an empty path with openat() and openat2(), which only
+ * names dirfd with O_EMPTYPATH (Linux 7.2), the way the kernel does.
* SPDX-License-Identifier: LGPL-2.1-only
*/
#define _GNU_SOURCE
@@ -10,8 +10,19 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/syscall.h>
#include <unistd.h>
+#if __has_include (<linux/openat2.h>)
+# include <linux/openat2.h>
+#else
+struct open_how {
+ unsigned long long flags;
+ unsigned long long mode;
+ unsigned long long resolve;
+};
+#endif
+
#ifndef O_EMPTYPATH
#define O_EMPTYPATH (1 << 26)
#endif
@@ -20,6 +31,13 @@ static int failed;
#define FAIL(...) do { printf(__VA_ARGS__); failed = 1; } while (0)
+static int
+do_openat2(int dirfd, const char *path, unsigned long long flags) {
+ struct open_how how = { .flags = flags };
+
+ return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
+}
+
/* fd should be dir, with pseudo knowing its path */
static void
check_reopened(const char *how, int fd) {
@@ -67,13 +85,21 @@ main(int argc, char *argv[]) {
fd, strerror(errno));
if (fd != -1)
close(fd);
+ fd = do_openat2(dfd, "", O_RDONLY);
+ if (fd != -1 || errno != ENOENT)
+ FAIL("openat2 without O_EMPTYPATH: expected ENOENT, got %d (%s)\n",
+ fd, strerror(errno));
+ if (fd != -1)
+ close(fd);
check_reopened("openat", openat(pfd, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH));
+ check_reopened("openat2", do_openat2(pfd, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH));
if (fchdir(dfd) == -1) {
perror("fchdir");
return 1;
}
check_reopened("openat AT_FDCWD", openat(AT_FDCWD, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH));
+ check_reopened("openat2 AT_FDCWD", do_openat2(AT_FDCWD, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH));
return failed;
}
openat2() passes an empty path to the kernel, which with Linux 7.2's O_EMPTYPATH reopens dirfd, but pseudo then looked at and recorded dirfd's recorded path. With AT_FDCWD that path is empty, so *at() calls relative to the new descriptor failed, and for a descriptor on a symlink it described the target instead. Look at dirfd itself with AT_EMPTY_PATH, as openat() now does, and record dirfd's path, or the working directory's for AT_FDCWD. [YOCTO #16327] Reported-by: Paul Barker <paul@pbarker.dev> AI-Generated: Uses GitHub Copilot Signed-off-by: Mark Hatle <mark.hatle@amd.com> --- ports/linux/openat2/guts/openat2.c | 24 +++++++++++++++++++----- test/test-openat-emptypath.c | 30 ++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-)