mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-27 07:20:11 +00:00
297 lines
9.0 KiB
C
297 lines
9.0 KiB
C
/* Tests for lchmod and fchmodat with AT_SYMLINK_NOFOLLOW.
|
|
Copyright (C) 2020-2024 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 <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <support/check.h>
|
|
#include <support/descriptors.h>
|
|
#include <support/namespace.h>
|
|
#include <support/support.h>
|
|
#include <support/temp_file.h>
|
|
#include <support/xunistd.h>
|
|
#include <unistd.h>
|
|
|
|
#if __has_include (<sys/mount.h>)
|
|
# include <sys/mount.h>
|
|
#endif
|
|
|
|
/* Array of file descriptors. */
|
|
#define DYNARRAY_STRUCT fd_list
|
|
#define DYNARRAY_ELEMENT int
|
|
#define DYNARRAY_INITIAL_SIZE 0
|
|
#define DYNARRAY_PREFIX fd_list_
|
|
#include <malloc/dynarray-skeleton.c>
|
|
|
|
static int
|
|
fchmodat_with_lchmod (int fd, const char *path, mode_t mode, int flags)
|
|
{
|
|
TEST_COMPARE (fd, AT_FDCWD);
|
|
if (flags == 0)
|
|
return chmod (path, mode);
|
|
else
|
|
{
|
|
TEST_COMPARE (flags, AT_SYMLINK_NOFOLLOW);
|
|
return lchmod (path, mode);
|
|
}
|
|
}
|
|
|
|
/* Chose the appropriate path to pass as the path argument to the *at
|
|
functions. */
|
|
static const char *
|
|
select_path (bool do_relative_path, const char *full_path, const char *relative_path)
|
|
{
|
|
if (do_relative_path)
|
|
return relative_path;
|
|
else
|
|
return full_path;
|
|
}
|
|
|
|
static void
|
|
update_file_time_to_y2038 (const char *fname, int flags)
|
|
{
|
|
#ifdef CHECK_TIME64
|
|
/* Y2038 threshold plus 1 second. */
|
|
const struct timespec ts[] = { { 0x80000001LL, 0}, { 0x80000001LL } };
|
|
TEST_VERIFY_EXIT (utimensat (AT_FDCWD, fname, ts, flags) == 0);
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
test_1 (bool do_relative_path, int (*chmod_func) (int fd, const char *, mode_t, int))
|
|
{
|
|
char *tempdir = support_create_temp_directory ("tst-lchmod-");
|
|
#ifdef CHECK_TIME64
|
|
if (!support_path_support_time64 (tempdir))
|
|
{
|
|
puts ("info: test skipped, filesystem does not support 64 bit time_t");
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
char *path_dangling = xasprintf ("%s/dangling", tempdir);
|
|
char *path_file = xasprintf ("%s/file", tempdir);
|
|
char *path_loop = xasprintf ("%s/loop", tempdir);
|
|
char *path_missing = xasprintf ("%s/missing", tempdir);
|
|
char *path_to_file = xasprintf ("%s/to-file", tempdir);
|
|
|
|
int fd;
|
|
if (do_relative_path)
|
|
fd = xopen (tempdir, O_DIRECTORY | O_RDONLY, 0);
|
|
else
|
|
fd = AT_FDCWD;
|
|
|
|
add_temp_file (path_dangling);
|
|
add_temp_file (path_loop);
|
|
add_temp_file (path_file);
|
|
add_temp_file (path_to_file);
|
|
|
|
support_write_file_string (path_file, "");
|
|
xsymlink ("file", path_to_file);
|
|
xsymlink ("loop", path_loop);
|
|
xsymlink ("target-does-not-exist", path_dangling);
|
|
|
|
update_file_time_to_y2038 (path_file, 0);
|
|
update_file_time_to_y2038 (path_to_file, AT_SYMLINK_NOFOLLOW);
|
|
|
|
/* Check that the modes do not collide with what we will use in the
|
|
test. */
|
|
struct stat st;
|
|
xstat (path_file, &st);
|
|
TEST_VERIFY ((st.st_mode & 0777) != 1);
|
|
xlstat (path_to_file, &st);
|
|
TEST_VERIFY ((st.st_mode & 0777) != 2);
|
|
mode_t original_symlink_mode = st.st_mode;
|
|
|
|
/* We should be able to change the mode of a file, including through
|
|
the symbolic link to-file. */
|
|
const char *arg = select_path (do_relative_path, path_file, "file");
|
|
TEST_COMPARE (chmod_func (fd, arg, 1, 0), 0);
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 1);
|
|
arg = select_path (do_relative_path, path_to_file, "to-file");
|
|
TEST_COMPARE (chmod_func (fd, arg, 2, 0), 0);
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 2);
|
|
xlstat (path_to_file, &st);
|
|
TEST_COMPARE (original_symlink_mode, st.st_mode);
|
|
arg = select_path (do_relative_path, path_file, "file");
|
|
TEST_COMPARE (chmod_func (fd, arg, 1, 0), 0);
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 1);
|
|
xlstat (path_to_file, &st);
|
|
TEST_COMPARE (original_symlink_mode, st.st_mode);
|
|
|
|
/* Changing the mode of a symbolic link should fail. */
|
|
arg = select_path (do_relative_path, path_to_file, "to-file");
|
|
int ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
|
|
TEST_COMPARE (ret, -1);
|
|
TEST_COMPARE (errno, EOPNOTSUPP);
|
|
|
|
/* The modes should remain unchanged. */
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 1);
|
|
xlstat (path_to_file, &st);
|
|
TEST_COMPARE (original_symlink_mode, st.st_mode);
|
|
|
|
/* Likewise, changing dangling and looping symbolic links must
|
|
fail. */
|
|
const char *paths[] = { path_dangling, path_loop };
|
|
for (size_t i = 0; i < array_length (paths); ++i)
|
|
{
|
|
const char *path = paths[i];
|
|
const char *filename = strrchr (path, '/');
|
|
TEST_VERIFY_EXIT (filename != NULL);
|
|
++filename;
|
|
mode_t new_mode = 010 + i;
|
|
|
|
xlstat (path, &st);
|
|
TEST_VERIFY ((st.st_mode & 0777) != new_mode);
|
|
original_symlink_mode = st.st_mode;
|
|
arg = select_path (do_relative_path, path, filename);
|
|
ret = chmod_func (fd, arg, new_mode, AT_SYMLINK_NOFOLLOW);
|
|
TEST_COMPARE (ret, -1);
|
|
TEST_COMPARE (errno, EOPNOTSUPP);
|
|
xlstat (path, &st);
|
|
TEST_COMPARE (st.st_mode, original_symlink_mode);
|
|
}
|
|
|
|
/* A missing file should always result in ENOENT. The presence of
|
|
/proc does not matter. */
|
|
arg = select_path (do_relative_path, path_missing, "missing");
|
|
TEST_COMPARE (chmod_func (fd, arg, 020, 0), -1);
|
|
TEST_COMPARE (errno, ENOENT);
|
|
TEST_COMPARE (chmod_func (fd, arg, 020, AT_SYMLINK_NOFOLLOW), -1);
|
|
TEST_COMPARE (errno, ENOENT);
|
|
|
|
/* Test without available file descriptors. */
|
|
{
|
|
struct fd_list fd_list;
|
|
fd_list_init (&fd_list);
|
|
while (true)
|
|
{
|
|
int ret = dup (STDOUT_FILENO);
|
|
if (ret == -1)
|
|
{
|
|
if (errno == ENFILE || errno == EMFILE)
|
|
break;
|
|
FAIL_EXIT1 ("dup: %m");
|
|
}
|
|
fd_list_add (&fd_list, ret);
|
|
TEST_VERIFY_EXIT (!fd_list_has_failed (&fd_list));
|
|
}
|
|
/* Without AT_SYMLINK_NOFOLLOW, changing the permissions should
|
|
work as before. */
|
|
arg = select_path (do_relative_path, path_file, "file");
|
|
TEST_COMPARE (chmod_func (fd, arg, 3, 0), 0);
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 3);
|
|
/* But with AT_SYMLINK_NOFOLLOW, even if we originally had
|
|
support, we may have lost it. */
|
|
ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
|
|
if (ret == 0)
|
|
{
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 2);
|
|
}
|
|
else
|
|
{
|
|
TEST_COMPARE (ret, -1);
|
|
/* The error code from the openat fallback leaks out. */
|
|
if (errno != ENFILE && errno != EMFILE)
|
|
TEST_COMPARE (errno, EOPNOTSUPP);
|
|
xstat (path_file, &st);
|
|
TEST_COMPARE (st.st_mode & 0777, 3);
|
|
}
|
|
|
|
/* Close the descriptors. */
|
|
for (int *pfd = fd_list_begin (&fd_list); pfd < fd_list_end (&fd_list);
|
|
++pfd)
|
|
xclose (*pfd);
|
|
fd_list_free (&fd_list);
|
|
}
|
|
|
|
if (do_relative_path)
|
|
xclose (fd);
|
|
|
|
free (path_dangling);
|
|
free (path_file);
|
|
free (path_loop);
|
|
free (path_missing);
|
|
free (path_to_file);
|
|
|
|
free (tempdir);
|
|
}
|
|
|
|
static void
|
|
test_3 (void)
|
|
{
|
|
puts ("info: testing lchmod");
|
|
test_1 (false, fchmodat_with_lchmod);
|
|
puts ("info: testing fchmodat with AT_FDCWD");
|
|
test_1 (false, fchmodat);
|
|
puts ("info: testing fchmodat with relative path");
|
|
test_1 (true, fchmodat);
|
|
}
|
|
|
|
static int
|
|
do_test (void)
|
|
{
|
|
struct support_descriptors *descriptors = support_descriptors_list ();
|
|
|
|
/* Run the three tests in the default environment. */
|
|
test_3 ();
|
|
|
|
/* Try to set up a /proc-less environment and re-test. */
|
|
#if __has_include (<sys/mount.h>)
|
|
if (!support_become_root ())
|
|
puts ("warning: could not obtain root-like privileges");
|
|
if (!support_enter_mount_namespace ())
|
|
puts ("warning: could enter a mount namespace");
|
|
else
|
|
{
|
|
/* Attempt to mount an empty directory over /proc. */
|
|
char *tempdir = support_create_temp_directory ("tst-lchmod-");
|
|
bool proc_emptied
|
|
= mount (tempdir, "/proc", "none", MS_BIND, NULL) == 0;
|
|
if (!proc_emptied)
|
|
printf ("warning: bind-mounting /proc failed: %m");
|
|
free (tempdir);
|
|
|
|
puts ("info: re-running tests (after trying to empty /proc)");
|
|
test_3 ();
|
|
|
|
if (proc_emptied)
|
|
/* Reveal the original /proc, which is needed by the
|
|
descriptors check below. */
|
|
TEST_COMPARE (umount ("/proc"), 0);
|
|
}
|
|
#endif /* <sys/mount.h>. */
|
|
|
|
support_descriptors_check (descriptors);
|
|
support_descriptors_free (descriptors);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#include <support/test-driver.c>
|