xref: /linux/tools/testing/selftests/filesystems/openat2/helpers.h (revision d2fcf57ffc3b85b816550b3ee404ffcc83ace16c)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Author: Aleksa Sarai <cyphar@cyphar.com>
4  * Copyright (C) 2018-2019 SUSE LLC.
5  * Copyright (C) 2026 Amutable GmbH
6  */
7 
8 #ifndef __RESOLVEAT_H__
9 #define __RESOLVEAT_H__
10 
11 #define _GNU_SOURCE
12 #include <stdint.h>
13 #include <stdbool.h>
14 #include <errno.h>
15 #include <limits.h>
16 #include <linux/types.h>
17 #include <linux/unistd.h>
18 #include "kselftest.h"
19 
20 #define ARRAY_LEN(X) (sizeof (X) / sizeof (*(X)))
21 #define BUILD_BUG_ON(e) ((void)(sizeof(struct { int:(-!!(e)); })))
22 
23 /*
24  * Arguments for how openat2(2) should open the target path. If @resolve is
25  * zero, then openat2(2) operates very similarly to openat(2).
26  *
27  * However, unlike openat(2), unknown bits in @flags result in -EINVAL rather
28  * than being silently ignored. @mode must be zero unless one of {O_CREAT,
29  * O_TMPFILE} are set.
30  *
31  * @flags: O_* flags.
32  * @mode: O_CREAT/O_TMPFILE file mode.
33  * @resolve: RESOLVE_* flags.
34  */
35 struct open_how {
36 	__u64 flags;
37 	__u64 mode;
38 	__u64 resolve;
39 };
40 
41 #define OPEN_HOW_SIZE_VER0	24 /* sizeof first published struct */
42 #define OPEN_HOW_SIZE_LATEST	OPEN_HOW_SIZE_VER0
43 
44 #ifndef RESOLVE_IN_ROOT
45 /* how->resolve flags for openat2(2). */
46 #define RESOLVE_NO_XDEV		0x01 /* Block mount-point crossings
47 					(includes bind-mounts). */
48 #define RESOLVE_NO_MAGICLINKS	0x02 /* Block traversal through procfs-style
49 					"magic-links". */
50 #define RESOLVE_NO_SYMLINKS	0x04 /* Block traversal through all symlinks
51 					(implies OEXT_NO_MAGICLINKS) */
52 #define RESOLVE_BENEATH		0x08 /* Block "lexical" trickery like
53 					"..", symlinks, and absolute
54 					paths which escape the dirfd. */
55 #define RESOLVE_IN_ROOT		0x10 /* Make all jumps to "/" and ".."
56 					be scoped inside the dirfd
57 					(similar to chroot(2)). */
58 #endif /* RESOLVE_IN_ROOT */
59 
60 #define E_func(func, ...)						      \
61 	do {								      \
62 		errno = 0;						      \
63 		if (func(__VA_ARGS__) < 0)				      \
64 			ksft_exit_fail_msg("%s:%d %s failed - errno:%d\n",    \
65 					   __FILE__, __LINE__, #func, errno); \
66 	} while (0)
67 
68 #define E_asprintf(...)		E_func(asprintf,	__VA_ARGS__)
69 #define E_chmod(...)		E_func(chmod,		__VA_ARGS__)
70 #define E_dup2(...)		E_func(dup2,		__VA_ARGS__)
71 #define E_fchdir(...)		E_func(fchdir,		__VA_ARGS__)
72 #define E_fstatat(...)		E_func(fstatat,		__VA_ARGS__)
73 #define E_kill(...)		E_func(kill,		__VA_ARGS__)
74 #define E_mkdirat(...)		E_func(mkdirat,		__VA_ARGS__)
75 #define E_mount(...)		E_func(mount,		__VA_ARGS__)
76 #define E_prctl(...)		E_func(prctl,		__VA_ARGS__)
77 #define E_readlink(...)		E_func(readlink,	__VA_ARGS__)
78 #define E_setresuid(...)	E_func(setresuid,	__VA_ARGS__)
79 #define E_symlinkat(...)	E_func(symlinkat,	__VA_ARGS__)
80 #define E_touchat(...)		E_func(touchat,		__VA_ARGS__)
81 #define E_unshare(...)		E_func(unshare,		__VA_ARGS__)
82 
83 #define E_assert(expr, msg, ...)					\
84 	do {								\
85 		if (!(expr))						\
86 			ksft_exit_fail_msg("ASSERT(%s:%d) failed (%s): " msg "\n", \
87 					   __FILE__, __LINE__, #expr, ##__VA_ARGS__); \
88 	} while (0)
89 
90 __maybe_unused
91 static bool needs_openat2(const struct open_how *how)
92 {
93 	return how->resolve != 0;
94 }
95 
96 __maybe_unused
97 static int raw_openat2(int dfd, const char *path, void *how, size_t size)
98 {
99 	int ret = syscall(__NR_openat2, dfd, path, how, size);
100 
101 	return ret >= 0 ? ret : -errno;
102 }
103 
104 __maybe_unused
105 static int sys_openat2(int dfd, const char *path, struct open_how *how)
106 {
107 	return raw_openat2(dfd, path, how, sizeof(*how));
108 }
109 
110 __maybe_unused
111 static int sys_openat(int dfd, const char *path, struct open_how *how)
112 {
113 	int ret = openat(dfd, path, how->flags, how->mode);
114 
115 	return ret >= 0 ? ret : -errno;
116 }
117 
118 __maybe_unused
119 static int sys_renameat2(int olddirfd, const char *oldpath,
120 			 int newdirfd, const char *newpath, unsigned int flags)
121 {
122 	int ret = syscall(__NR_renameat2, olddirfd, oldpath,
123 					  newdirfd, newpath, flags);
124 
125 	return ret >= 0 ? ret : -errno;
126 }
127 
128 __maybe_unused
129 static int touchat(int dfd, const char *path)
130 {
131 	int fd = openat(dfd, path, O_CREAT, 0700);
132 
133 	if (fd >= 0)
134 		close(fd);
135 	return fd;
136 }
137 
138 __maybe_unused
139 static char *fdreadlink(int fd)
140 {
141 	char *target, *tmp;
142 
143 	E_asprintf(&tmp, "/proc/self/fd/%d", fd);
144 
145 	target = malloc(PATH_MAX);
146 	if (!target)
147 		ksft_exit_fail_msg("fdreadlink: malloc failed\n");
148 	memset(target, 0, PATH_MAX);
149 
150 	E_readlink(tmp, target, PATH_MAX);
151 	free(tmp);
152 	return target;
153 }
154 
155 __maybe_unused
156 static bool fdequal(int fd, int dfd, const char *path)
157 {
158 	char *fdpath, *dfdpath, *other;
159 	bool cmp;
160 
161 	fdpath = fdreadlink(fd);
162 	dfdpath = fdreadlink(dfd);
163 
164 	if (!path)
165 		E_asprintf(&other, "%s", dfdpath);
166 	else if (*path == '/')
167 		E_asprintf(&other, "%s", path);
168 	else
169 		E_asprintf(&other, "%s/%s", dfdpath, path);
170 
171 	cmp = !strcmp(fdpath, other);
172 
173 	free(fdpath);
174 	free(dfdpath);
175 	free(other);
176 	return cmp;
177 }
178 
179 static bool openat2_supported = false;
180 
181 __attribute__((constructor))
182 static void __detect_openat2_supported(void)
183 {
184 	struct open_how how = {};
185 	int fd;
186 
187 	BUILD_BUG_ON(sizeof(struct open_how) != OPEN_HOW_SIZE_VER0);
188 
189 	/* Check openat2(2) support. */
190 	fd = sys_openat2(AT_FDCWD, ".", &how);
191 	openat2_supported = (fd >= 0);
192 
193 	if (fd >= 0)
194 		close(fd);
195 }
196 
197 #endif /* __RESOLVEAT_H__ */
198