@@ -262,6 +262,10 @@ class Function:
# In those cases, use a suffix ('_int') for the path variable names instead of
# overwriting them. This means the wrapper needs to handle paths itself.
self.preserve_paths = False
+ # Leave an empty path unchanged instead of resolving it to dirfd's
+ # path, for functions where only a flag (e.g. O_EMPTYPATH) makes
+ # an empty path name dirfd.
+ self.keep_empty_path = False
function, comments = line.split(';')
comment = re.search(r'/\* *(.*) *\*/', comments)
@@ -427,9 +431,12 @@ class Function:
if self.preserve_paths and self.noignore_path:
# path_int is only read by the ignore_paths() check
continue
- fix_paths.append(
- "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" %
- (pathvar, prefix, self.dirfd, path, self.flags))
+ munge = "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" % \
+ (pathvar, prefix, self.dirfd, path, self.flags)
+ if self.keep_empty_path:
+ # see above for the asm()
+ munge = "asm(\"\" : \"+r\"(%s));\n\t\tif (!%s || *%s)\n\t\t\t%s" % (path, path, path, munge)
+ fix_paths.append(munge)
return "\n\t\t".join(fix_paths)
def ignore_paths(self):
@@ -13,6 +13,17 @@
int existed = 1;
int save_errno;
sigset_t local_saved_sigmask;
+ const char *record_path = path;
+ int stat_flags = (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0;
+
+ /* An empty path is left for the kernel, which only accepts it with
+ * O_EMPTYPATH, to reopen dirfd. Look at and record dirfd instead.
+ */
+ if (path && !*path) {
+ stat_flags |= AT_EMPTY_PATH;
+ record_path = pseudo_root_path(__func__, __LINE__, dirfd,
+ dirfd == AT_FDCWD ? "." : "", 0);
+ }
/* mask out mode bits appropriately */
mode = mode & ~pseudo_umask;
@@ -47,7 +58,7 @@
/* note "else" in #ifdef O_TMPFILE above */
if (flags & O_CREAT) {
save_errno = errno;
- rc = base_fstatat(dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+ rc = base_fstatat(dirfd, path, &buf, stat_flags);
existed = (rc != -1);
if (!existed)
pseudo_debug(PDBGF_FILE, "openat_creat: %s -> 0%o\n", path, mode);
@@ -60,7 +71,7 @@
*/
if (!(flags & O_NONBLOCK) && ((flags & (O_WRONLY | O_RDONLY | O_RDWR)) != O_RDWR)) {
save_errno = errno;
- rc = base_fstatat(dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+ rc = base_fstatat(dirfd, path, &buf, stat_flags);
if (rc != -1 && S_ISFIFO(buf.st_mode)) {
overly_magic_nonblocking = 1;
}
@@ -105,7 +116,7 @@
return rc;
}
#endif
- stat_rc = base_fstatat(dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+ stat_rc = base_fstatat(dirfd, path, &buf, stat_flags);
pseudo_debug(PDBGF_FILE, "openat(path %s), flags %o, stat rc %d, stat mode %o\n",
path, flags, stat_rc, buf.st_mode);
@@ -122,11 +133,11 @@
}
pseudo_client_path_unlock(path_lock_fd);
}
- pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, &buf);
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, record_path, &buf);
} else {
pseudo_debug(PDBGF_FILE, "openat (fd %d, path %d/%s, flags %d) succeeded, but stat failed (%s).\n",
rc, dirfd, path, flags, strerror(errno));
- pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, 0);
+ pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, record_path, 0);
}
errno = save_errno;
}
@@ -8,8 +8,8 @@ int __fxstat(int ver, int fd, struct stat *buf);
int lchmod(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW, path_lock=shared */
int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW, path_lock=shared */
int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags); /* path_lock=shared */
-int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */
-int __openat_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */
+int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */
+int __openat_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */
int open_tree(int dirfd, const char *path, unsigned int flags); /* preserve_paths=1, noignore_path=1 */
int open_tree_attr(int dirfd, const char *path, unsigned int flags, struct mount_attr *attr, size_t size); /* preserve_paths=1, noignore_path=1*/
int name_to_handle_at(int dirfd, const char *name, struct file_handle *handle, int *mount_id, int flags);
@@ -24,8 +24,8 @@ int fcntl64(int fd, int cmd, ...{struct flock *lock}); /* noignore_path=1 */
char *canonicalize_file_name(const char *filename);
int eaccess(const char *path, int mode);
int open64(const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */
-int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */
-int __openat64_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */
+int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */
+int __openat64_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */
int creat64(const char *path, mode_t mode);
int stat(const char *path, struct stat *buf); /* real_func=pseudo_stat, path_lock=shared */
int lstat(const char *path, struct stat *buf); /* real_func=pseudo_lstat, flags=AT_SYMLINK_NOFOLLOW, path_lock=shared */
new file mode 100644
@@ -0,0 +1,79 @@
+/*
+ * Test opening an empty path, 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
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifndef O_EMPTYPATH
+#define O_EMPTYPATH (1 << 26)
+#endif
+
+static int failed;
+
+#define FAIL(...) do { printf(__VA_ARGS__); failed = 1; } while (0)
+
+/* fd should be dir, with pseudo knowing its path */
+static void
+check_reopened(const char *how, int fd) {
+ struct stat st;
+
+ if (fd == -1) {
+ if (errno == ENOENT || errno == EINVAL)
+ printf("%s: O_EMPTYPATH not supported by the kernel, skipping\n", how);
+ else
+ FAIL("%s: %s\n", how, strerror(errno));
+ return;
+ }
+ if (fstatat(fd, "file", &st, 0) == -1)
+ FAIL("%s: fstatat: %s\n", how, strerror(errno));
+ else if (st.st_uid != 123 || st.st_gid != 456)
+ FAIL("%s: file owned by %d:%d, expected 123:456\n",
+ how, (int) st.st_uid, (int) st.st_gid);
+ close(fd);
+}
+
+int
+main(int argc, char *argv[]) {
+ char path[4096];
+ int dfd, pfd, fd;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s <dir>\n", argv[0]);
+ return 2;
+ }
+ snprintf(path, sizeof(path), "%s/file", argv[1]);
+ if (chown(path, 123, 456) == -1) {
+ perror("chown");
+ return 1;
+ }
+ dfd = open(argv[1], O_RDONLY | O_DIRECTORY);
+ pfd = open(argv[1], O_PATH | O_DIRECTORY);
+ if (dfd == -1 || pfd == -1) {
+ perror("open");
+ return 1;
+ }
+
+ fd = openat(dfd, "", O_RDONLY);
+ if (fd != -1 || errno != ENOENT)
+ FAIL("openat 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));
+ if (fchdir(dfd) == -1) {
+ perror("fchdir");
+ return 1;
+ }
+ check_reopened("openat AT_FDCWD", openat(AT_FDCWD, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH));
+
+ return failed;
+}
new file mode 100755
@@ -0,0 +1,12 @@
+#!/bin/sh
+#
+# Test opening an empty path, with and without O_EMPTYPATH.
+# SPDX-License-Identifier: LGPL-2.1-only
+
+dir=$(pwd)/openat_emptypath_test
+trap 'rm -rf "$dir"' 0
+rm -rf "$dir"
+mkdir -p "$dir"
+touch "$dir/file"
+
+./test/test-openat-emptypath "$dir"
openat() wrappers resolved an empty path to dirfd's recorded path, so openat(dirfd, "", ...) opened dirfd's path instead of failing with ENOENT. Linux 7.2 adds O_EMPTYPATH, with which an empty path reopens dirfd itself, e.g. to turn an O_PATH descriptor into a usable one; resolving it to a path opens the wrong file if dirfd's file was renamed or removed. Add a makewrappers option, keep_empty_path, to leave an empty path unchanged, and use it for the openat() wrappers so the kernel decides what an empty path means. The guts look at dirfd with AT_EMPTY_PATH and record dirfd's path for the new descriptor. [YOCTO #16327] Reported-by: Paul Barker <paul@pbarker.dev> AI-Generated: Uses GitHub Copilot Signed-off-by: Mark Hatle <mark.hatle@amd.com> --- makewrappers | 13 ++++-- ports/linux/guts/openat.c | 21 +++++++--- ports/linux/wrapfuncs.in | 8 ++-- test/test-openat-emptypath.c | 79 +++++++++++++++++++++++++++++++++++ test/test-openat-emptypath.sh | 12 ++++++ 5 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 test/test-openat-emptypath.c create mode 100755 test/test-openat-emptypath.sh