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
31struct 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
40struct 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
50void 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
140skip:
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
156struct flag_test {
157 const char *name;
158 struct open_how how;
159 int err;
160};
161
162#define NUM_OPENAT2_FLAG_TESTS 25
163
164void 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 (!= %llX)\n",
304 fd, fdpath, fdflags,
305 test->how.flags);
306 else
307 ksft_print_msg("%d (%s)\n", fd, strerror(-fd));
308 }
309
310skip:
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));
316next:
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
325int 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

source code of linux/tools/testing/selftests/openat2/openat2_test.c