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