selftest: add tests for O_EMPTYPATH

Add tests for the new O_EMPTYPATH flag of openat(2)/openat2(2).

Also, the current openat2 tests include a helper header file that
defines the necessary structs and constants to use openat2(2), such as
struct open_how. This may result in conflicting definitions when the
system header openat2.h is present as well.

So add openat2.h generated by 'make headers' to the uapi header
files in ./tools/include and remove the helper file definitions of
the current openat2 selftests.

Signed-off-by: Jori Koolstra <jkoolstra@xs4all.nl>
Link: https://patch.msgid.link/20260424114611.1678641-3-jkoolstra@xs4all.nl
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Jori Koolstra
2026-04-24 13:46:03 +02:00
committed by Christian Brauner
parent 31cf44efa6
commit c0329020da
4 changed files with 101 additions and 36 deletions

View File

@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _LINUX_OPENAT2_H
#define _LINUX_OPENAT2_H
#include <linux/types.h>
/*
* Arguments for how openat2(2) should open the target path. If only @flags and
* @mode are non-zero, then openat2(2) operates very similarly to openat(2).
*
* However, unlike openat(2), unknown or invalid bits in @flags result in
* -EINVAL rather than being silently ignored. @mode must be zero unless one of
* {O_CREAT, O_TMPFILE} are set.
*
* @flags: O_* flags.
* @mode: O_CREAT/O_TMPFILE file mode.
* @resolve: RESOLVE_* flags.
*/
struct open_how {
__u64 flags;
__u64 mode;
__u64 resolve;
};
/* how->resolve flags for openat2(2). */
#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings
(includes bind-mounts). */
#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style
"magic-links". */
#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks
(implies OEXT_NO_MAGICLINKS) */
#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like
"..", symlinks, and absolute
paths which escape the dirfd. */
#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".."
be scoped inside the dirfd
(similar to chroot(2)). */
#define RESOLVE_CACHED 0x20 /* Only complete if resolution can be
completed through cached lookup. May
return -EAGAIN if that's not
possible. */
#endif /* _LINUX_OPENAT2_H */

View File

@@ -1,8 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-or-later
CFLAGS += $(KHDR_INCLUDES)
CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined
TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test
CFLAGS += -Wall -O2 -g -fsanitize=address -fsanitize=undefined $(TOOLS_INCLUDES)
TEST_GEN_PROGS := openat2_test resolve_test rename_attack_test emptypath_test
# gcc requires -static-libasan in order to ensure that Address Sanitizer's
# library is the first one loaded. However, clang already statically links the

View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#define __SANE_USERSPACE_TYPES__
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include "kselftest.h"
#ifndef O_EMPTYPATH
#define O_EMPTYPATH (1 << 26)
#endif
int main(void)
{
int opath_fd, reopen_fd;
const char *path = "/tmp/emptypath_test";
ksft_print_header();
ksft_set_plan(2);
opath_fd = open(path, O_CREAT | O_WRONLY, S_IRWXU);
if (opath_fd < 0)
ksft_exit_fail_msg("create %s: %m\n", path);
close(opath_fd);
opath_fd = open(path, O_PATH);
if (opath_fd < 0)
ksft_exit_fail_msg("open %s O_PATH: %m\n", path);
reopen_fd = openat(opath_fd, "", O_RDONLY);
if (reopen_fd < 0 && errno == ENOENT)
ksft_test_result_pass("empty path without O_EMPTYPATH returns ENOENT\n");
else if (reopen_fd >= 0) {
ksft_test_result_fail("empty path without O_EMPTYPATH unexpectedly succeeded\n");
close(reopen_fd);
} else {
ksft_test_result_fail("empty path without O_EMPTYPATH: expected ENOENT, got %m\n");
}
reopen_fd = openat(opath_fd, "", O_RDONLY | O_EMPTYPATH);
if (reopen_fd < 0 && errno == EINVAL)
ksft_exit_skip("O_EMPTYPATH not supported\n");
if (reopen_fd >= 0) {
ksft_test_result_pass("O_EMPTYPATH reopens O_PATH fd\n");
close(reopen_fd);
} else {
ksft_test_result_fail("O_EMPTYPATH failed: %m\n");
}
unlink(path);
ksft_finished();
}

View File

@@ -15,47 +15,14 @@
#include <limits.h>
#include <linux/types.h>
#include <linux/unistd.h>
#include <linux/openat2.h>
#include "kselftest_harness.h"
#define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); })))
/*
* Arguments for how openat2(2) should open the target path. If @resolve is
* zero, then openat2(2) operates very similarly to openat(2).
*
* However, unlike openat(2), unknown bits in @flags result in -EINVAL rather
* than being silently ignored. @mode must be zero unless one of {O_CREAT,
* O_TMPFILE} are set.
*
* @flags: O_* flags.
* @mode: O_CREAT/O_TMPFILE file mode.
* @resolve: RESOLVE_* flags.
*/
struct open_how {
__u64 flags;
__u64 mode;
__u64 resolve;
};
#define OPEN_HOW_SIZE_VER0 24 /* sizeof first published struct */
#define OPEN_HOW_SIZE_LATEST OPEN_HOW_SIZE_VER0
#ifndef RESOLVE_IN_ROOT
/* how->resolve flags for openat2(2). */
#define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings
(includes bind-mounts). */
#define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style
"magic-links". */
#define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks
(implies OEXT_NO_MAGICLINKS) */
#define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like
"..", symlinks, and absolute
paths which escape the dirfd. */
#define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".."
be scoped inside the dirfd
(similar to chroot(2)). */
#endif /* RESOLVE_IN_ROOT */
__maybe_unused
static bool needs_openat2(const struct open_how *how)
{