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 24 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 234 BUILD_BUG_ON(ARRAY_LEN(tests) != NUM_OPENAT2_FLAG_TESTS); 235 236 for (int i = 0; i < ARRAY_LEN(tests); i++) { 237 int fd, fdflags = -1; 238 char *path, *fdpath = NULL; 239 bool failed = false; 240 struct flag_test *test = &tests[i]; 241 void (*resultfn)(const char *msg, ...) = ksft_test_result_pass; 242 243 if (!openat2_supported) { 244 ksft_print_msg("openat2(2) unsupported\n"); 245 resultfn = ksft_test_result_skip; 246 goto skip; 247 } 248 249 path = (test->how.flags & O_CREAT) ? "/tmp/ksft.openat2_tmpfile" : "."; 250 unlink(path); 251 252 fd = sys_openat2(AT_FDCWD, path, &test->how); 253 if (test->err >= 0) 254 failed = (fd < 0); 255 else 256 failed = (fd != test->err); 257 if (fd >= 0) { 258 int otherflags; 259 260 fdpath = fdreadlink(fd); 261 fdflags = fcntl(fd, F_GETFL); 262 otherflags = fcntl(fd, F_GETFD); 263 close(fd); 264 265 E_assert(fdflags >= 0, "fcntl F_GETFL of new fd"); 266 E_assert(otherflags >= 0, "fcntl F_GETFD of new fd"); 267 268 /* O_CLOEXEC isn't shown in F_GETFL. */ 269 if (otherflags & FD_CLOEXEC) 270 fdflags |= O_CLOEXEC; 271 /* O_CREAT is hidden from F_GETFL. */ 272 if (test->how.flags & O_CREAT) 273 fdflags |= O_CREAT; 274 if (!(test->how.flags & O_LARGEFILE)) 275 fdflags &= ~O_LARGEFILE; 276 failed |= (fdflags != test->how.flags); 277 } 278 279 if (failed) { 280 resultfn = ksft_test_result_fail; 281 282 ksft_print_msg("openat2 unexpectedly returned "); 283 if (fdpath) 284 ksft_print_msg("%d['%s'] with %X (!= %X)\n", 285 fd, fdpath, fdflags, 286 test->how.flags); 287 else 288 ksft_print_msg("%d (%s)\n", fd, strerror(-fd)); 289 } 290 291 skip: 292 if (test->err >= 0) 293 resultfn("openat2 with %s succeeds\n", test->name); 294 else 295 resultfn("openat2 with %s fails with %d (%s)\n", 296 test->name, test->err, strerror(-test->err)); 297 298 free(fdpath); 299 fflush(stdout); 300 } 301 } 302 303 #define NUM_TESTS (NUM_OPENAT2_STRUCT_VARIATIONS * NUM_OPENAT2_STRUCT_TESTS + \ 304 NUM_OPENAT2_FLAG_TESTS) 305 306 int main(int argc, char **argv) 307 { 308 ksft_print_header(); 309 ksft_set_plan(NUM_TESTS); 310 311 test_openat2_struct(); 312 test_openat2_flags(); 313 314 if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0) 315 ksft_exit_fail(); 316 else 317 ksft_exit_pass(); 318 } 319