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#define O_LARGEFILE 0x8000
  26
  27struct 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
  36struct 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
  46void 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
 136skip:
 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
 152struct flag_test {
 153        const char *name;
 154        struct open_how how;
 155        int err;
 156};
 157
 158#define NUM_OPENAT2_FLAG_TESTS 23
 159
 160void 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
 285skip:
 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
 300int 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