Merge tag 'linux_kselftest-next-6.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel...
[sfrench/cifs-2.6.git] / tools / testing / selftests / openat2 / openat2_test.c
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 (!= %llX)\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 }