linux/tools/testing/selftests/openat2/openat2_test.c
<<
>>
Prefs
   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
  31struct 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
  40struct 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
  50void 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
 140skip:
 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
 156struct flag_test {
 157        const char *name;
 158        struct open_how how;
 159        int err;
 160};
 161
 162#define NUM_OPENAT2_FLAG_TESTS 25
 163
 164void 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
 300skip:
 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
 315int 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