mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-10-31 22:10:34 +03: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-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 <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>
 |