xref: /linux/tools/testing/selftests/openat2/openat2_test.c (revision d7f39aee79f04eeaa42085728423501b33ac5be5)
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 "../kselftest.h"
19 #include "helpers.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 #define NUM_OPENAT2_STRUCT_TESTS 7
49 #define NUM_OPENAT2_STRUCT_VARIATIONS 13
50 
51 void test_openat2_struct(void)
52 {
53 	int misalignments[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 17, 87 };
54 
55 	struct struct_test tests[] = {
56 		/* Normal struct. */
57 		{ .name = "normal struct",
58 		  .arg.inner.flags = O_RDONLY,
59 		  .size = sizeof(struct open_how) },
60 		/* Bigger struct, with zeroed out end. */
61 		{ .name = "bigger struct (zeroed out)",
62 		  .arg.inner.flags = O_RDONLY,
63 		  .size = sizeof(struct open_how_ext) },
64 
65 		/* TODO: Once expanded, check zero-padding. */
66 
67 		/* Smaller than version-0 struct. */
68 		{ .name = "zero-sized 'struct'",
69 		  .arg.inner.flags = O_RDONLY, .size = 0, .err = -EINVAL },
70 		{ .name = "smaller-than-v0 struct",
71 		  .arg.inner.flags = O_RDONLY,
72 		  .size = OPEN_HOW_SIZE_VER0 - 1, .err = -EINVAL },
73 
74 		/* Bigger struct, with non-zero trailing bytes. */
75 		{ .name = "bigger struct (non-zero data in first 'future field')",
76 		  .arg.inner.flags = O_RDONLY, .arg.extra1 = 0xdeadbeef,
77 		  .size = sizeof(struct open_how_ext), .err = -E2BIG },
78 		{ .name = "bigger struct (non-zero data in middle of 'future fields')",
79 		  .arg.inner.flags = O_RDONLY, .arg.extra2 = 0xfeedcafe,
80 		  .size = sizeof(struct open_how_ext), .err = -E2BIG },
81 		{ .name = "bigger struct (non-zero data at end of 'future fields')",
82 		  .arg.inner.flags = O_RDONLY, .arg.extra3 = 0xabad1dea,
83 		  .size = sizeof(struct open_how_ext), .err = -E2BIG },
84 	};
85 
86 	BUILD_BUG_ON(ARRAY_LEN(misalignments) != NUM_OPENAT2_STRUCT_VARIATIONS);
87 	BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_STRUCT_TESTS);
88 
89 	for (int i = 0; i < ARRAY_LEN(tests); i++) {
90 		struct struct_test *test = &tests[i];
91 		struct open_how_ext how_ext = test->arg;
92 
93 		for (int j = 0; j < ARRAY_LEN(misalignments); j++) {
94 			int fd, misalign = misalignments[j];
95 			char *fdpath = NULL;
96 			bool failed;
97 			void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
98 
99 			void *copy = NULL, *how_copy = &how_ext;
100 
101 			if (!openat2_supported) {
102 				ksft_print_msg("openat2(2) unsupported\n");
103 				resultfn = ksft_test_result_skip;
104 				goto skip;
105 			}
106 
107 			if (misalign) {
108 				/*
109 				 * Explicitly misalign the structure copying it with the given
110 				 * (mis)alignment offset. The other data is set to be non-zero to
111 				 * make sure that non-zero bytes outside the struct aren't checked
112 				 *
113 				 * This is effectively to check that is_zeroed_user() works.
114 				 */
115 				copy = malloc(misalign + sizeof(how_ext));
116 				how_copy = copy + misalign;
117 				memset(copy, 0xff, misalign);
118 				memcpy(how_copy, &how_ext, sizeof(how_ext));
119 			}
120 
121 			fd = raw_openat2(AT_FDCWD, ".", how_copy, test->size);
122 			if (test->err >= 0)
123 				failed = (fd < 0);
124 			else
125 				failed = (fd != test->err);
126 			if (fd >= 0) {
127 				fdpath = fdreadlink(fd);
128 				close(fd);
129 			}
130 
131 			if (failed) {
132 				resultfn = ksft_test_result_fail;
133 
134 				ksft_print_msg("openat2 unexpectedly returned ");
135 				if (fdpath)
136 					ksft_print_msg("%d['%s']\n", fd, fdpath);
137 				else
138 					ksft_print_msg("%d (%s)\n", fd, strerror(-fd));
139 			}
140 
141 skip:
142 			if (test->err >= 0)
143 				resultfn("openat2 with %s argument [misalign=%d] succeeds\n",
144 					 test->name, misalign);
145 			else
146 				resultfn("openat2 with %s argument [misalign=%d] fails with %d (%s)\n",
147 					 test->name, misalign, test->err,
148 					 strerror(-test->err));
149 
150 			free(copy);
151 			free(fdpath);
152 			fflush(stdout);
153 		}
154 	}
155 }
156 
157 struct flag_test {
158 	const char *name;
159 	struct open_how how;
160 	int err;
161 };
162 
163 #define NUM_OPENAT2_FLAG_TESTS 25
164 
165 void test_openat2_flags(void)
166 {
167 	struct flag_test tests[] = {
168 		/* O_TMPFILE is incompatible with O_PATH and O_CREAT. */
169 		{ .name = "incompatible flags (O_TMPFILE | O_PATH)",
170 		  .how.flags = O_TMPFILE | O_PATH | O_RDWR, .err = -EINVAL },
171 		{ .name = "incompatible flags (O_TMPFILE | O_CREAT)",
172 		  .how.flags = O_TMPFILE | O_CREAT | O_RDWR, .err = -EINVAL },
173 
174 		/* O_PATH only permits certain other flags to be set ... */
175 		{ .name = "compatible flags (O_PATH | O_CLOEXEC)",
176 		  .how.flags = O_PATH | O_CLOEXEC },
177 		{ .name = "compatible flags (O_PATH | O_DIRECTORY)",
178 		  .how.flags = O_PATH | O_DIRECTORY },
179 		{ .name = "compatible flags (O_PATH | O_NOFOLLOW)",
180 		  .how.flags = O_PATH | O_NOFOLLOW },
181 		/* ... and others are absolutely not permitted. */
182 		{ .name = "incompatible flags (O_PATH | O_RDWR)",
183 		  .how.flags = O_PATH | O_RDWR, .err = -EINVAL },
184 		{ .name = "incompatible flags (O_PATH | O_CREAT)",
185 		  .how.flags = O_PATH | O_CREAT, .err = -EINVAL },
186 		{ .name = "incompatible flags (O_PATH | O_EXCL)",
187 		  .how.flags = O_PATH | O_EXCL, .err = -EINVAL },
188 		{ .name = "incompatible flags (O_PATH | O_NOCTTY)",
189 		  .how.flags = O_PATH | O_NOCTTY, .err = -EINVAL },
190 		{ .name = "incompatible flags (O_PATH | O_DIRECT)",
191 		  .how.flags = O_PATH | O_DIRECT, .err = -EINVAL },
192 		{ .name = "incompatible flags (O_PATH | O_LARGEFILE)",
193 		  .how.flags = O_PATH | O_LARGEFILE, .err = -EINVAL },
194 
195 		/* ->mode must only be set with O_{CREAT,TMPFILE}. */
196 		{ .name = "non-zero how.mode and O_RDONLY",
197 		  .how.flags = O_RDONLY, .how.mode = 0600, .err = -EINVAL },
198 		{ .name = "non-zero how.mode and O_PATH",
199 		  .how.flags = O_PATH,   .how.mode = 0600, .err = -EINVAL },
200 		{ .name = "valid how.mode and O_CREAT",
201 		  .how.flags = O_CREAT,  .how.mode = 0600 },
202 		{ .name = "valid how.mode and O_TMPFILE",
203 		  .how.flags = O_TMPFILE | O_RDWR, .how.mode = 0600 },
204 		/* ->mode must only contain 0777 bits. */
205 		{ .name = "invalid how.mode and O_CREAT",
206 		  .how.flags = O_CREAT,
207 		  .how.mode = 0xFFFF, .err = -EINVAL },
208 		{ .name = "invalid (very large) how.mode and O_CREAT",
209 		  .how.flags = O_CREAT,
210 		  .how.mode = 0xC000000000000000ULL, .err = -EINVAL },
211 		{ .name = "invalid how.mode and O_TMPFILE",
212 		  .how.flags = O_TMPFILE | O_RDWR,
213 		  .how.mode = 0x1337, .err = -EINVAL },
214 		{ .name = "invalid (very large) how.mode and O_TMPFILE",
215 		  .how.flags = O_TMPFILE | O_RDWR,
216 		  .how.mode = 0x0000A00000000000ULL, .err = -EINVAL },
217 
218 		/* ->resolve flags must not conflict. */
219 		{ .name = "incompatible resolve flags (BENEATH | IN_ROOT)",
220 		  .how.flags = O_RDONLY,
221 		  .how.resolve = RESOLVE_BENEATH | RESOLVE_IN_ROOT,
222 		  .err = -EINVAL },
223 
224 		/* ->resolve must only contain RESOLVE_* flags. */
225 		{ .name = "invalid how.resolve and O_RDONLY",
226 		  .how.flags = O_RDONLY,
227 		  .how.resolve = 0x1337, .err = -EINVAL },
228 		{ .name = "invalid how.resolve and O_CREAT",
229 		  .how.flags = O_CREAT,
230 		  .how.resolve = 0x1337, .err = -EINVAL },
231 		{ .name = "invalid how.resolve and O_TMPFILE",
232 		  .how.flags = O_TMPFILE | O_RDWR,
233 		  .how.resolve = 0x1337, .err = -EINVAL },
234 		{ .name = "invalid how.resolve and O_PATH",
235 		  .how.flags = O_PATH,
236 		  .how.resolve = 0x1337, .err = -EINVAL },
237 
238 		/* currently unknown upper 32 bit rejected. */
239 		{ .name = "currently unknown bit (1 << 63)",
240 		  .how.flags = O_RDONLY | (1ULL << 63),
241 		  .how.resolve = 0, .err = -EINVAL },
242 	};
243 
244 	BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_FLAG_TESTS);
245 
246 	for (int i = 0; i < ARRAY_LEN(tests); i++) {
247 		int fd, fdflags = -1;
248 		char *path, *fdpath = NULL;
249 		bool failed = false;
250 		struct flag_test *test = &tests[i];
251 		void (*resultfn)(const char *msg, ...) = ksft_test_result_pass;
252 
253 		if (!openat2_supported) {
254 			ksft_print_msg("openat2(2) unsupported\n");
255 			resultfn = ksft_test_result_skip;
256 			goto skip;
257 		}
258 
259 		path = (test->how.flags & O_CREAT) ? "/tmp/ksft.openat2_tmpfile" : ".";
260 		unlink(path);
261 
262 		fd = sys_openat2(AT_FDCWD, path, &test->how);
263 		if (fd < 0 && fd == -EOPNOTSUPP) {
264 			/*
265 			 * Skip the testcase if it failed because not supported
266 			 * by FS. (e.g. a valid O_TMPFILE combination on NFS)
267 			 */
268 			ksft_test_result_skip("openat2 with %s fails with %d (%s)\n",
269 					      test->name, fd, strerror(-fd));
270 			goto next;
271 		}
272 
273 		if (test->err >= 0)
274 			failed = (fd < 0);
275 		else
276 			failed = (fd != test->err);
277 		if (fd >= 0) {
278 			int otherflags;
279 
280 			fdpath = fdreadlink(fd);
281 			fdflags = fcntl(fd, F_GETFL);
282 			otherflags = fcntl(fd, F_GETFD);
283 			close(fd);
284 
285 			E_assert(fdflags >= 0, "fcntl F_GETFL of new fd");
286 			E_assert(otherflags >= 0, "fcntl F_GETFD of new fd");
287 
288 			/* O_CLOEXEC isn't shown in F_GETFL. */
289 			if (otherflags & FD_CLOEXEC)
290 				fdflags |= O_CLOEXEC;
291 			/* O_CREAT is hidden from F_GETFL. */
292 			if (test->how.flags & O_CREAT)
293 				fdflags |= O_CREAT;
294 			if (!(test->how.flags & O_LARGEFILE))
295 				fdflags &= ~O_LARGEFILE;
296 			failed |= (fdflags != test->how.flags);
297 		}
298 
299 		if (failed) {
300 			resultfn = ksft_test_result_fail;
301 
302 			ksft_print_msg("openat2 unexpectedly returned ");
303 			if (fdpath)
304 				ksft_print_msg("%d['%s'] with %X (!= %llX)\n",
305 					       fd, fdpath, fdflags,
306 					       test->how.flags);
307 			else
308 				ksft_print_msg("%d (%s)\n", fd, strerror(-fd));
309 		}
310 
311 skip:
312 		if (test->err >= 0)
313 			resultfn("openat2 with %s succeeds\n", test->name);
314 		else
315 			resultfn("openat2 with %s fails with %d (%s)\n",
316 				 test->name, test->err, strerror(-test->err));
317 next:
318 		free(fdpath);
319 		fflush(stdout);
320 	}
321 }
322 
323 #define NUM_TESTS (NUM_OPENAT2_STRUCT_VARIATIONS * NUM_OPENAT2_STRUCT_TESTS + \
324 		   NUM_OPENAT2_FLAG_TESTS)
325 
326 int main(int argc, char **argv)
327 {
328 	ksft_print_header();
329 	ksft_set_plan(NUM_TESTS);
330 
331 	test_openat2_struct();
332 	test_openat2_flags();
333 
334 	if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
335 		ksft_exit_fail();
336 	else
337 		ksft_exit_pass();
338 }
339