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