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