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 (test->err >= 0) 263 failed = (fd < 0); 264 else 265 failed = (fd != test->err); 266 if (fd >= 0) { 267 int otherflags; 268 269 fdpath = fdreadlink(fd); 270 fdflags = fcntl(fd, F_GETFL); 271 otherflags = fcntl(fd, F_GETFD); 272 close(fd); 273 274 E_assert(fdflags >= 0, "fcntl F_GETFL of new fd"); 275 E_assert(otherflags >= 0, "fcntl F_GETFD of new fd"); 276 277 /* O_CLOEXEC isn't shown in F_GETFL. */ 278 if (otherflags & FD_CLOEXEC) 279 fdflags |= O_CLOEXEC; 280 /* O_CREAT is hidden from F_GETFL. */ 281 if (test->how.flags & O_CREAT) 282 fdflags |= O_CREAT; 283 if (!(test->how.flags & O_LARGEFILE)) 284 fdflags &= ~O_LARGEFILE; 285 failed |= (fdflags != test->how.flags); 286 } 287 288 if (failed) { 289 resultfn = ksft_test_result_fail; 290 291 ksft_print_msg("openat2 unexpectedly returned "); 292 if (fdpath) 293 ksft_print_msg("%d['%s'] with %X (!= %X)\n", 294 fd, fdpath, fdflags, 295 test->how.flags); 296 else 297 ksft_print_msg("%d (%s)\n", fd, strerror(-fd)); 298 } 299 300 skip: 301 if (test->err >= 0) 302 resultfn("openat2 with %s succeeds\n", test->name); 303 else 304 resultfn("openat2 with %s fails with %d (%s)\n", 305 test->name, test->err, strerror(-test->err)); 306 307 free(fdpath); 308 fflush(stdout); 309 } 310 } 311 312 #define NUM_TESTS (NUM_OPENAT2_STRUCT_VARIATIONS * NUM_OPENAT2_STRUCT_TESTS + \ 313 NUM_OPENAT2_FLAG_TESTS) 314 315 int main(int argc, char **argv) 316 { 317 ksft_print_header(); 318 ksft_set_plan(NUM_TESTS); 319 320 test_openat2_struct(); 321 test_openat2_flags(); 322 323 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) 324 ksft_exit_fail(); 325 else 326 ksft_exit_pass(); 327 } 328