xref: /linux/tools/testing/selftests/filesystems/openat2/openat2_test.c (revision 6045a75399b45f6805f07a03020abf384b9f53c3)
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  */
6 
7 #define _GNU_SOURCE
8 #define __SANE_USERSPACE_TYPES__ // Use ll64
9 #include <fcntl.h>
10 #include <sched.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <sys/mount.h>
14 #include <stdlib.h>
15 #include <stdbool.h>
16 #include <string.h>
17 
18 #include "helpers.h"
19 #include "kselftest_harness.h"
20 
21 /*
22  * O_LARGEFILE is set to 0 by glibc.
23  * XXX: This is wrong on {mips, parisc, powerpc, sparc}.
24  */
25 #undef	O_LARGEFILE
26 #ifdef __aarch64__
27 #define	O_LARGEFILE 0x20000
28 #else
29 #define	O_LARGEFILE 0x8000
30 #endif
31 
32 struct open_how_ext {
33 	struct open_how inner;
34 	uint32_t extra1;
35 	char pad1[128];
36 	uint32_t extra2;
37 	char pad2[128];
38 	uint32_t extra3;
39 };
40 
41 struct struct_test {
42 	const char *name;
43 	struct open_how_ext arg;
44 	size_t size;
45 	int err;
46 };
47 
48 struct flag_test {
49 	const char *name;
50 	struct open_how how;
51 	int err;
52 };
53 
54 FIXTURE(openat2) {};
55 
56 FIXTURE_SETUP(openat2)
57 {
58 	if (!openat2_supported)
59 		SKIP(return, "openat2(2) not supported");
60 }
61 
62 FIXTURE_TEARDOWN(openat2) {}
63 
64 /*
65  * Verify that the struct size and misalignment handling for openat2(2) is
66  * correct, including that is_zeroed_user() works.
67  */
68 TEST_F(openat2, struct_argument_sizes)
69 {
70 	int misalignments[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 17, 87 };
71 	struct struct_test tests[] = {
72 		/* Normal struct. */
73 		{ .name = "normal struct",
74 		  .arg.inner.flags = O_RDONLY,
75 		  .size = sizeof(struct open_how) },
76 		/* Bigger struct, with zeroed out end. */
77 		{ .name = "bigger struct (zeroed out)",
78 		  .arg.inner.flags = O_RDONLY,
79 		  .size = sizeof(struct open_how_ext) },
80 
81 		/* TODO: Once expanded, check zero-padding. */
82 
83 		/* Smaller than version-0 struct. */
84 		{ .name = "zero-sized 'struct'",
85 		  .arg.inner.flags = O_RDONLY, .size = 0, .err = -EINVAL },
86 		{ .name = "smaller-than-v0 struct",
87 		  .arg.inner.flags = O_RDONLY,
88 		  .size = OPEN_HOW_SIZE_VER0 - 1, .err = -EINVAL },
89 
90 		/* Bigger struct, with non-zero trailing bytes. */
91 		{ .name = "bigger struct (non-zero data in first 'future field')",
92 		  .arg.inner.flags = O_RDONLY, .arg.extra1 = 0xdeadbeef,
93 		  .size = sizeof(struct open_how_ext), .err = -E2BIG },
94 		{ .name = "bigger struct (non-zero data in middle of 'future fields')",
95 		  .arg.inner.flags = O_RDONLY, .arg.extra2 = 0xfeedcafe,
96 		  .size = sizeof(struct open_how_ext), .err = -E2BIG },
97 		{ .name = "bigger struct (non-zero data at end of 'future fields')",
98 		  .arg.inner.flags = O_RDONLY, .arg.extra3 = 0xabad1dea,
99 		  .size = sizeof(struct open_how_ext), .err = -E2BIG },
100 	};
101 
102 	for (int i = 0; i < ARRAY_SIZE(tests); i++) {
103 		struct struct_test *test = &tests[i];
104 		struct open_how_ext how_ext = test->arg;
105 
106 		for (int j = 0; j < ARRAY_SIZE(misalignments); j++) {
107 			int fd, misalign = misalignments[j];
108 			void *copy = NULL, *how_copy = &how_ext;
109 			char *fdpath = NULL;
110 
111 			if (misalign) {
112 				/*
113 				 * Explicitly misalign the structure copying it with the given
114 				 * (mis)alignment offset. The other data is set to be non-zero to
115 				 * make sure that non-zero bytes outside the struct aren't checked
116 				 *
117 				 * This is effectively to check that is_zeroed_user() works.
118 				 */
119 				copy = malloc(misalign + sizeof(how_ext));
120 				how_copy = copy + misalign;
121 				memset(copy, 0xff, misalign);
122 				memcpy(how_copy, &how_ext, sizeof(how_ext));
123 			}
124 
125 			fd = raw_openat2(AT_FDCWD, ".", how_copy, test->size);
126 			if (fd >= 0) {
127 				fdpath = fdreadlink(_metadata, fd);
128 				close(fd);
129 			}
130 
131 			if (test->err >= 0) {
132 				EXPECT_GE(fd, 0) {
133 					TH_LOG("openat2 with %s [misalign=%d] should succeed, got %d (%s)",
134 					       test->name, misalign,
135 					       fd, strerror(-fd));
136 				}
137 			} else {
138 				EXPECT_EQ(test->err, fd) {
139 					if (fdpath)
140 						TH_LOG("openat2 with %s [misalign=%d] should fail with %d (%s), got %d['%s']",
141 						       test->name, misalign,
142 						       test->err,
143 						       strerror(-test->err),
144 						       fd, fdpath);
145 					else
146 						TH_LOG("openat2 with %s [misalign=%d] should fail with %d (%s), got %d (%s)",
147 						       test->name, misalign,
148 						       test->err,
149 						       strerror(-test->err),
150 						       fd, strerror(-fd));
151 				}
152 			}
153 
154 			free(copy);
155 			free(fdpath);
156 		}
157 	}
158 }
159 
160 /* Verify openat2(2) flag and mode validation. */
161 TEST_F(openat2, flag_validation)
162 {
163 	struct flag_test tests[] = {
164 		/* O_TMPFILE is incompatible with O_PATH and O_CREAT. */
165 		{ .name = "incompatible flags (O_TMPFILE | O_PATH)",
166 		  .how.flags = O_TMPFILE | O_PATH | O_RDWR, .err = -EINVAL },
167 		{ .name = "incompatible flags (O_TMPFILE | O_CREAT)",
168 		  .how.flags = O_TMPFILE | O_CREAT | O_RDWR, .err = -EINVAL },
169 
170 		/* O_PATH only permits certain other flags to be set ... */
171 		{ .name = "compatible flags (O_PATH | O_CLOEXEC)",
172 		  .how.flags = O_PATH | O_CLOEXEC },
173 		{ .name = "compatible flags (O_PATH | O_DIRECTORY)",
174 		  .how.flags = O_PATH | O_DIRECTORY },
175 		{ .name = "compatible flags (O_PATH | O_NOFOLLOW)",
176 		  .how.flags = O_PATH | O_NOFOLLOW },
177 		/* ... and others are absolutely not permitted. */
178 		{ .name = "incompatible flags (O_PATH | O_RDWR)",
179 		  .how.flags = O_PATH | O_RDWR, .err = -EINVAL },
180 		{ .name = "incompatible flags (O_PATH | O_CREAT)",
181 		  .how.flags = O_PATH | O_CREAT, .err = -EINVAL },
182 		{ .name = "incompatible flags (O_PATH | O_EXCL)",
183 		  .how.flags = O_PATH | O_EXCL, .err = -EINVAL },
184 		{ .name = "incompatible flags (O_PATH | O_NOCTTY)",
185 		  .how.flags = O_PATH | O_NOCTTY, .err = -EINVAL },
186 		{ .name = "incompatible flags (O_PATH | O_DIRECT)",
187 		  .how.flags = O_PATH | O_DIRECT, .err = -EINVAL },
188 		{ .name = "incompatible flags (O_PATH | O_LARGEFILE)",
189 		  .how.flags = O_PATH | O_LARGEFILE, .err = -EINVAL },
190 
191 		/* ->mode must only be set with O_{CREAT,TMPFILE}. */
192 		{ .name = "non-zero how.mode and O_RDONLY",
193 		  .how.flags = O_RDONLY, .how.mode = 0600, .err = -EINVAL },
194 		{ .name = "non-zero how.mode and O_PATH",
195 		  .how.flags = O_PATH,   .how.mode = 0600, .err = -EINVAL },
196 		{ .name = "valid how.mode and O_CREAT",
197 		  .how.flags = O_CREAT,  .how.mode = 0600 },
198 		{ .name = "valid how.mode and O_TMPFILE",
199 		  .how.flags = O_TMPFILE | O_RDWR, .how.mode = 0600 },
200 		/* ->mode must only contain 0777 bits. */
201 		{ .name = "invalid how.mode and O_CREAT",
202 		  .how.flags = O_CREAT,
203 		  .how.mode = 0xFFFF, .err = -EINVAL },
204 		{ .name = "invalid (very large) how.mode and O_CREAT",
205 		  .how.flags = O_CREAT,
206 		  .how.mode = 0xC000000000000000ULL, .err = -EINVAL },
207 		{ .name = "invalid how.mode and O_TMPFILE",
208 		  .how.flags = O_TMPFILE | O_RDWR,
209 		  .how.mode = 0x1337, .err = -EINVAL },
210 		{ .name = "invalid (very large) how.mode and O_TMPFILE",
211 		  .how.flags = O_TMPFILE | O_RDWR,
212 		  .how.mode = 0x0000A00000000000ULL, .err = -EINVAL },
213 
214 		/* ->resolve flags must not conflict. */
215 		{ .name = "incompatible resolve flags (BENEATH | IN_ROOT)",
216 		  .how.flags = O_RDONLY,
217 		  .how.resolve = RESOLVE_BENEATH | RESOLVE_IN_ROOT,
218 		  .err = -EINVAL },
219 
220 		/* ->resolve must only contain RESOLVE_* flags. */
221 		{ .name = "invalid how.resolve and O_RDONLY",
222 		  .how.flags = O_RDONLY,
223 		  .how.resolve = 0x1337, .err = -EINVAL },
224 		{ .name = "invalid how.resolve and O_CREAT",
225 		  .how.flags = O_CREAT,
226 		  .how.resolve = 0x1337, .err = -EINVAL },
227 		{ .name = "invalid how.resolve and O_TMPFILE",
228 		  .how.flags = O_TMPFILE | O_RDWR,
229 		  .how.resolve = 0x1337, .err = -EINVAL },
230 		{ .name = "invalid how.resolve and O_PATH",
231 		  .how.flags = O_PATH,
232 		  .how.resolve = 0x1337, .err = -EINVAL },
233 
234 		/* currently unknown upper 32 bit rejected. */
235 		{ .name = "currently unknown bit (1 << 63)",
236 		  .how.flags = O_RDONLY | (1ULL << 63),
237 		  .how.resolve = 0, .err = -EINVAL },
238 	};
239 
240 	for (int i = 0; i < ARRAY_SIZE(tests); i++) {
241 		int fd, fdflags = -1;
242 		char *path, *fdpath = NULL;
243 		struct flag_test *test = &tests[i];
244 
245 		path = (test->how.flags & O_CREAT) ? "/tmp/ksft.openat2_tmpfile" : ".";
246 		unlink(path);
247 
248 		fd = sys_openat2(AT_FDCWD, path, &test->how);
249 		if (fd < 0 && fd == -EOPNOTSUPP) {
250 			/*
251 			 * Skip the testcase if it failed because not supported
252 			 * by FS. (e.g. a valid O_TMPFILE combination on NFS)
253 			 */
254 			TH_LOG("openat2 with %s not supported by FS -- skipping",
255 			       test->name);
256 			continue;
257 		}
258 
259 		if (test->err >= 0) {
260 			EXPECT_GE(fd, 0) {
261 				TH_LOG("openat2 with %s should succeed, got %d (%s)",
262 				       test->name, fd, strerror(-fd));
263 			}
264 			if (fd >= 0) {
265 				int otherflags;
266 
267 				fdpath = fdreadlink(_metadata, fd);
268 				fdflags = fcntl(fd, F_GETFL);
269 				otherflags = fcntl(fd, F_GETFD);
270 				close(fd);
271 
272 				ASSERT_GE(fdflags, 0);
273 				ASSERT_GE(otherflags, 0);
274 
275 				/* O_CLOEXEC isn't shown in F_GETFL. */
276 				if (otherflags & FD_CLOEXEC)
277 					fdflags |= O_CLOEXEC;
278 				/* O_CREAT is hidden from F_GETFL. */
279 				if (test->how.flags & O_CREAT)
280 					fdflags |= O_CREAT;
281 				if (!(test->how.flags & O_LARGEFILE))
282 					fdflags &= ~O_LARGEFILE;
283 
284 				EXPECT_EQ(fdflags, (int)test->how.flags) {
285 					TH_LOG("openat2 with %s: flags mismatch %X != %llX",
286 					       test->name, fdflags,
287 					       (unsigned long long)test->how.flags);
288 				}
289 			}
290 		} else {
291 			EXPECT_EQ(test->err, fd) {
292 				if (fd >= 0) {
293 					fdpath = fdreadlink(_metadata, fd);
294 					TH_LOG("openat2 with %s should fail with %d (%s), got %d['%s']",
295 					       test->name, test->err,
296 					       strerror(-test->err),
297 					       fd, fdpath);
298 				} else {
299 					TH_LOG("openat2 with %s should fail with %d (%s), got %d (%s)",
300 					       test->name, test->err,
301 					       strerror(-test->err),
302 					       fd, strerror(-fd));
303 				}
304 			}
305 			if (fd >= 0)
306 				close(fd);
307 		}
308 
309 		free(fdpath);
310 	}
311 }
312 
313 #ifndef OPENAT2_REGULAR
314 #define OPENAT2_REGULAR ((__u64)1 << 32)
315 #endif
316 
317 #ifndef EFTYPE
318 #define EFTYPE 134
319 #endif
320 
321 /* Kernel-internal carrier for OPENAT2_REGULAR (see __O_REGULAR in fcntl.h). */
322 #ifndef __O_REGULAR
323 #define __O_REGULAR (1 << 30)
324 #endif
325 
326 /* Verify that OPENAT2_REGULAR rejects non-regular files with EFTYPE. */
327 TEST_F(openat2, regular_flag)
328 {
329 	struct open_how how = {
330 		.flags = OPENAT2_REGULAR | O_RDONLY,
331 	};
332 	int fd;
333 
334 	fd = sys_openat2(AT_FDCWD, "/dev/null", &how);
335 	if (fd == -ENOENT)
336 		SKIP(return, "/dev/null does not exist");
337 
338 	EXPECT_EQ(-EFTYPE, fd) {
339 		TH_LOG("openat2 with OPENAT2_REGULAR should fail with %d (%s), got %d (%s)",
340 		       -EFTYPE, strerror(EFTYPE), fd, strerror(-fd));
341 	}
342 	if (fd >= 0)
343 		close(fd);
344 }
345 
346 /* open()/openat() must keep ignoring the internal __O_REGULAR bit. */
347 TEST(legacy_openat_ignores_o_regular)
348 {
349 	int fd;
350 
351 	fd = openat(AT_FDCWD, "/dev/null", O_RDONLY | __O_REGULAR);
352 	if (fd < 0 && errno == ENOENT)
353 		SKIP(return, "/dev/null does not exist");
354 
355 	ASSERT_GE(fd, 0) {
356 		TH_LOG("legacy openat() must ignore the __O_REGULAR carrier bit, got errno %d (%s)",
357 		       errno, strerror(errno));
358 	}
359 	close(fd);
360 }
361 
362 TEST_HARNESS_MAIN
363