From: Mark Hatle <mark.hatle@amd.com>
To: <yocto-patches@lists.yoctoproject.org>
Subject: [pseudo][PATCH RFC 2/2] openat2: Look at dirfd for an empty path
Date: Thu, 12 Nov 2026 11:47:10 -0700 [thread overview]
Message-ID: <20261112184710.549482-3-mark.hatle@amd.com> (raw)
In-Reply-To: <20261112184710.549482-1-mark.hatle@amd.com>
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(-)
diff --git a/ports/linux/openat2/guts/openat2.c b/ports/linux/openat2/guts/openat2.c
index bb4a125..30a51ab 100644
--- a/ports/linux/openat2/guts/openat2.c
+++ b/ports/linux/openat2/guts/openat2.c
@@ -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;
}
diff --git a/test/test-openat-emptypath.c b/test/test-openat-emptypath.c
index 076f1fd..7ebec12 100644
--- a/test/test-openat-emptypath.c
+++ b/test/test-openat-emptypath.c
@@ -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;
}
--
2.34.1
prev parent reply other threads:[~2026-11-12 18:48 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-12 18:47 [pseudo][PATCH RFC 0/2] Implement new openat2 flags Mark Hatle
2026-11-12 18:47 ` [pseudo][PATCH RFC 1/2] ports/linux: Pass an empty openat() path to the kernel Mark Hatle
2026-11-12 18:47 ` Mark Hatle [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261112184710.549482-3-mark.hatle@amd.com \
--to=mark.hatle@amd.com \
--cc=yocto-patches@lists.yoctoproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.