1
0
mirror of https://sourceware.org/git/glibc.git synced 2025-12-18 17:24:29 +03:00
Files
glibc/sysdeps/unix/sysv/linux/tst-openat2.c
Adhemerval Zanella 0f0a5cd338 linux: Add openat2 (BZ 31664)
The openat2 syscall was added on Linux 5.6, as an extension of openat.
Unlike other open-like functions, the kernel only provides the LFS
variant (so files larger than 4GB always succeed, unlike other
functions with an offset larger than off_t).  Also, similar to other
open functions, the new symbol is a cancellable entrypoint.

The test case added only stress tests for some of the syscalls' provided
functionality, and it is based on an existing kernel self-test.

A fortify wrapper is added to verify the argument size if not larger
than the current support open_how struct.

Gnulib added an openat2 module, which uses read-only for the open_how
argument [1].  There is no clear indication whether the kernel will
indeed use the argument as in-out, how it would do so, or for which
kind of functionality [2]. Also, adding a potentially different prototype
than gnulib only would add extra unnecessary friction and extra
wrappers to handle it.

Checked on x86_64-linux-gnu and aarch64-linux-gnu.

[1] https://gitweb.git.savannah.gnu.org/gitweb/?p=gnulib.git;a=commit;h=0b97ffdf32bdab909d02449043447237273df75e
[2] https://sourceware.org/pipermail/libc-alpha/2025-September/169740.html

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
2025-12-09 11:14:16 -03:00

483 lines
11 KiB
C

/* Linux openat2 tests.
Copyright (C) 2025 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <array_length.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <stdint.h>
#include <support/check.h>
#include <support/support.h>
#include <support/temp_file.h>
#include <support/test-driver.h>
#include <support/xunistd.h>
static int dir_fd;
static char *temp_dir;
static char *temp_subdir;
static char *temp_some_file;
#define TEST_DIR_LINK "test_dir_link"
#define TEST_DIR_LINK_2 "test_dir_link_2"
#define TEMP_DIR_LINK "temp_dir_link"
#define INVALID_LINK "invalid-link"
#define VALID_LINK "valid-link"
static void
create_symlink (const char *target, const char *linkpath)
{
TEST_VERIFY_EXIT (symlinkat (target, dir_fd, linkpath) == 0);
add_temp_file (xasprintf ("%s/%s", temp_dir, linkpath));
}
static void
do_prepare (int argc, char *argv[])
{
/*
Construct a test directory with the following structure:
temp_dir/
|- tst-openat2.xxxxxxx
|- test_dir_link -> test_dir (/tmp)
|- test_dir_link_2 -> test_dir_link
|- temp_dir_link -> temp_dir/tst-openat2.xxxxxxx
|- some-file.xxxxxxx
|- invalid_link -> temp_dir/tst-openat2.xxxxxxx/some-file.xxxxxxx
|- valid_link -> some-file.xxxxxxx
|- subdir.xxxxxxx
|- some-file.xxxxxxx
*/
temp_dir = support_create_temp_directory ("tst-openat2.");
dir_fd = xopen (temp_dir, O_RDONLY | O_DIRECTORY, 0);
create_symlink (test_dir, TEST_DIR_LINK);
create_symlink (TEST_DIR_LINK, TEST_DIR_LINK_2);
create_symlink (temp_dir, TEMP_DIR_LINK);
{
char *filename;
int fd = create_temp_file_in_dir ("some-file.", temp_dir, &filename);
TEST_VERIFY_EXIT (fd != -1);
create_symlink (filename, INVALID_LINK);
create_symlink (basename (filename), VALID_LINK);
}
temp_subdir = support_create_temp_directory (xasprintf ("%s/subdir.",
basename (temp_dir)));
{
int fd = create_temp_file_in_dir ("some-file.", temp_subdir,
&temp_some_file);
TEST_VERIFY_EXIT (fd != -1);
}
}
#define PREPARE do_prepare
static int
do_test_struct (void)
{
static struct struct_test
{
struct open_how_ext
{
struct open_how inner;
int extra1;
int extra2;
int extra3;
} arg;
size_t size;
int err;
} tests[] =
{
{
/* Zero size. */
.arg.inner.flags = O_RDONLY,
.size = 0,
.err = EINVAL,
},
{
/* Normal struct. */
.arg.inner.flags = O_RDONLY,
.size = sizeof (struct open_how),
},
{
/* Larger struct, zeroed out the unused values. */
.arg.inner.flags = O_RDONLY,
.size = sizeof (struct open_how_ext),
},
{
/* Larger struct, non-zeroed out the unused values. */
.arg.inner.flags = O_RDONLY,
.arg.extra1 = 0xdeadbeef,
.size = sizeof (struct open_how_ext),
.err = E2BIG,
},
{
/* Larger struct, non-zeroed out the unused values. */
.arg.inner.flags = O_RDONLY,
.arg.extra2 = 0xdeadbeef,
.size = sizeof (struct open_how_ext),
.err = E2BIG,
},
};
for (struct struct_test *t = tests; t != array_end (tests); t++)
{
int fd = openat2 (AT_FDCWD, ".", (struct open_how *) &t->arg, t->size);
if (t->err != 0)
{
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, t->err);
}
else
TEST_VERIFY (fd >= 0);
}
return 0;
}
static int
do_test_flags (void)
{
static struct flag_test
{
const char *path;
struct open_how how;
int err;
} tests[] =
{
/* O_TMPFILE is incompatible with O_PATH and O_CREAT. */
{
.how.flags = O_TMPFILE | O_PATH | O_RDWR,
.err = EINVAL },
{
.how.flags = O_TMPFILE | O_CREAT | O_RDWR,
.err = EINVAL },
/* O_PATH only permits certain other flags to be set ... */
{
.how.flags = O_PATH | O_CLOEXEC
},
{
.how.flags = O_PATH | O_DIRECTORY
},
{
.how.flags = O_PATH | O_NOFOLLOW
},
/* ... and others are absolutely not permitted. */
{
.how.flags = O_PATH | O_RDWR,
.err = EINVAL },
{
.how.flags = O_PATH | O_CREAT,
.err = EINVAL },
{
.how.flags = O_PATH | O_EXCL,
.err = EINVAL },
{
.how.flags = O_PATH | O_NOCTTY,
.err = EINVAL },
{
.how.flags = O_PATH | O_DIRECT,
.err = EINVAL },
/* ->mode must only be set with O_{CREAT,TMPFILE}. */
{
.how.flags = O_RDONLY,
.how.mode = 0600,
.err = EINVAL },
{
.how.flags = O_PATH,
.how.mode = 0600,
.err = EINVAL },
{
.how.flags = O_CREAT,
.how.mode = 0600 },
{
.how.flags = O_TMPFILE | O_RDWR,
.how.mode = 0600 },
/* ->mode must only contain 0777 bits. */
{
.how.flags = O_CREAT, .how.mode = 0xFFFF, .err = EINVAL },
{
.how.flags = O_CREAT, .how.mode = 0xC000000000000000ULL,
.err = EINVAL },
{
.how.flags = O_TMPFILE | O_RDWR, .how.mode = 0x1337,
.err = EINVAL },
{
.how.flags = O_TMPFILE | O_RDWR,
.how.mode = 0x0000A00000000000ULL,
.err = EINVAL
},
/* ->resolve flags must not conflict. */
{
.how.flags = O_RDONLY,
.how.resolve = RESOLVE_BENEATH | RESOLVE_IN_ROOT,
.err = EINVAL
},
/* ->resolve must only contain RESOLVE_* flags. */
{
.how.flags = O_RDONLY,
.how.resolve = 0x1337,
.err = EINVAL
},
{
.how.flags = O_CREAT,
.how.resolve = 0x1337,
.err = EINVAL
},
{
.how.flags = O_TMPFILE | O_RDWR,
.how.resolve = 0x1337,
.err = EINVAL
},
{
.how.flags = O_PATH,
.how.resolve = 0x1337,
.err = EINVAL
},
/* currently unknown upper 32 bit rejected. */
{
.how.flags = O_RDONLY | (1ULL << 63),
.how.resolve = 0,
.err = EINVAL
},
};
for (struct flag_test *t = tests; t != array_end (tests); t++)
{
const char *path;
if (t->how.flags & O_CREAT)
{
char *newfile;
int temp_fd = create_temp_file ("tst-openat2.", &newfile);
TEST_VERIFY_EXIT (temp_fd != -1);
xunlink (newfile);
path = newfile;
}
else
path = ".";
int fd = openat2 (AT_FDCWD, path, &t->how, sizeof (struct open_how));
if (fd != 0 && errno == EOPNOTSUPP)
{
/* Skip the testcase if FS does not support the operation (e.g.
valid O_TMPFILE on NFS). */
continue;
}
if (t->err != 0)
{
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, t->err);
}
else
TEST_VERIFY (fd >= 0);
}
return 0;
}
static void
do_test_resolve (void)
{
int fd;
/* TEMP_DIR_LINK links to the absolute temp_dir, which escapes the temporary
test directory. */
fd = openat2 (dir_fd,
TEST_DIR_LINK,
&(struct open_how)
{
.resolve = RESOLVE_BENEATH,
},
sizeof (struct open_how));
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, EXDEV);
/* Same as before, TEMP_DIR_LINK_2 links to TEMP_DIR_LINK. */
fd = openat2 (dir_fd,
TEST_DIR_LINK_2,
&(struct open_how)
{
.resolve = RESOLVE_BENEATH,
},
sizeof (struct open_how));
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, EXDEV);
/* TEMP_DIR_LINK links to the temporary directory itself (dir_fd). */
fd = openat2 (dir_fd,
TEMP_DIR_LINK,
&(struct open_how)
{
.resolve = RESOLVE_BENEATH,
},
sizeof (struct open_how));
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, EXDEV);
/* Although it points to a valid file in same path, the link refers to
an absolute path. */
fd = openat2 (dir_fd,
INVALID_LINK,
&(struct open_how)
{
.resolve = RESOLVE_BENEATH,
},
sizeof (struct open_how));
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, EXDEV);
fd = openat2 (dir_fd,
VALID_LINK,
&(struct open_how)
{
.resolve = RESOLVE_BENEATH,
},
sizeof (struct open_how));
TEST_VERIFY (fd != -1);
xclose (fd);
/* There is no such file in temp_dir/tst-openat2.xxxxxxx. */
fd = openat2 (dir_fd,
"should-not-work",
&(struct open_how)
{
.resolve = RESOLVE_IN_ROOT,
},
sizeof (struct open_how));
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, ENOENT);
{
int subdir_fd = openat2 (dir_fd,
basename (temp_subdir),
&(struct open_how)
{
.flags = O_RDONLY | O_DIRECTORY,
.resolve = RESOLVE_IN_ROOT,
},
sizeof (struct open_how));
TEST_VERIFY (subdir_fd != -1);
/* Open the file within the subdir.xxxxxx with both tst-openat2.xxxxxxx
and tst-openat2.xxxxxxx/subdir.xxxxxxx file descriptors. */
fd = openat2 (subdir_fd,
basename (temp_some_file),
&(struct open_how)
{
.resolve = RESOLVE_IN_ROOT,
},
sizeof (struct open_how));
TEST_VERIFY (fd != -1);
xclose (fd);
fd = openat2 (dir_fd,
xasprintf ("%s/%s",
basename (temp_subdir),
basename (temp_some_file)),
&(struct open_how)
{
.resolve = RESOLVE_IN_ROOT,
},
sizeof (struct open_how));
TEST_VERIFY (fd != -1);
xclose (fd);
}
}
static int
do_test_basic (void)
{
int fd;
fd = openat2 (dir_fd,
"some-file",
&(struct open_how)
{
.flags = O_CREAT|O_RDWR|O_EXCL,
.mode = 0666,
},
sizeof (struct open_how));
TEST_VERIFY (fd != -1);
xwrite (fd, "hello", 5);
/* Before closing the file, try using this file descriptor to open
another file. This must fail. */
{
int fd2 = openat2 (fd,
"should-not-work",
&(struct open_how)
{
.flags = O_CREAT|O_RDWR|O_EXCL,
.mode = 0666,
},
sizeof (struct open_how));
TEST_COMPARE (fd2, -1);
TEST_COMPARE (errno, ENOTDIR);
}
/* Remove the created file. */
int cwdfd = xopen (".", O_RDONLY | O_DIRECTORY, 0);
TEST_COMPARE (fchdir (dir_fd), 0);
xunlink ("some-file");
TEST_COMPARE (fchdir (cwdfd), 0);
xclose (dir_fd);
xclose (cwdfd);
fd = openat2 (dir_fd,
"some-file",
&(struct open_how)
{
.flags = O_CREAT|O_RDWR|O_EXCL,
.mode = 0666,
},
sizeof (struct open_how));
TEST_COMPARE (fd, -1);
TEST_COMPARE (errno, EBADF);
return 0;
}
static int
do_test (void)
{
if (openat2 (AT_FDCWD, ".", &(struct open_how) {}, sizeof (struct open_how))
== -1 && errno == ENOSYS)
FAIL_UNSUPPORTED ("openat2 is not supported by the kernel");
do_test_struct ();
do_test_flags ();
do_test_resolve ();
do_test_basic ();
return 0;
}
#include <support/test-driver.c>