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