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