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