339 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Author: Aleksa Sarai <cyphar@cyphar.com>4 * Copyright (C) 2018-2019 SUSE LLC.5 */6 7#define _GNU_SOURCE8#define __SANE_USERSPACE_TYPES__ // Use ll649#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_LARGEFILE26#ifdef __aarch64__27#define O_LARGEFILE 0x2000028#else29#define O_LARGEFILE 0x800030#endif31 32struct 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 41struct 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 749#define NUM_OPENAT2_STRUCT_VARIATIONS 1350 51void 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 given110 * (mis)alignment offset. The other data is set to be non-zero to111 * make sure that non-zero bytes outside the struct aren't checked112 *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 else125 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 else138 ksft_print_msg("%d (%s)\n", fd, strerror(-fd));139 }140 141skip:142 if (test->err >= 0)143 resultfn("openat2 with %s argument [misalign=%d] succeeds\n",144 test->name, misalign);145 else146 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 157struct flag_test {158 const char *name;159 struct open_how how;160 int err;161};162 163#define NUM_OPENAT2_FLAG_TESTS 25164 165void 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 supported266 * 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 else276 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 else308 ksft_print_msg("%d (%s)\n", fd, strerror(-fd));309 }310 311skip:312 if (test->err >= 0)313 resultfn("openat2 with %s succeeds\n", test->name);314 else315 resultfn("openat2 with %s fails with %d (%s)\n",316 test->name, test->err, strerror(-test->err));317next: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 326int 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 else337 ksft_exit_pass();338}339