(root)/
glibc-2.38/
io/
tst-lchmod.c
       1  /* Tests for lchmod and fchmodat with AT_SYMLINK_NOFOLLOW.
       2     Copyright (C) 2020-2023 Free Software Foundation, Inc.
       3     This file is part of the GNU C Library.
       4  
       5     The GNU C Library is free software; you can redistribute it and/or
       6     modify it under the terms of the GNU Lesser General Public
       7     License as published by the Free Software Foundation; either
       8     version 2.1 of the License, or (at your option) any later version.
       9  
      10     The GNU C Library is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      13     Lesser General Public License for more details.
      14  
      15     You should have received a copy of the GNU Lesser General Public
      16     License along with the GNU C Library; if not, see
      17     <https://www.gnu.org/licenses/>.  */
      18  
      19  #include <array_length.h>
      20  #include <errno.h>
      21  #include <fcntl.h>
      22  #include <stdbool.h>
      23  #include <stdio.h>
      24  #include <stdlib.h>
      25  #include <string.h>
      26  #include <support/check.h>
      27  #include <support/descriptors.h>
      28  #include <support/namespace.h>
      29  #include <support/support.h>
      30  #include <support/temp_file.h>
      31  #include <support/xunistd.h>
      32  #include <unistd.h>
      33  
      34  #if __has_include (<sys/mount.h>)
      35  # include <sys/mount.h>
      36  #endif
      37  
      38  /* Array of file descriptors.  */
      39  #define DYNARRAY_STRUCT fd_list
      40  #define DYNARRAY_ELEMENT int
      41  #define DYNARRAY_INITIAL_SIZE 0
      42  #define DYNARRAY_PREFIX fd_list_
      43  #include <malloc/dynarray-skeleton.c>
      44  
      45  static int
      46  fchmodat_with_lchmod (int fd, const char *path, mode_t mode, int flags)
      47  {
      48    TEST_COMPARE (fd, AT_FDCWD);
      49    if (flags == 0)
      50      return chmod (path, mode);
      51    else
      52      {
      53        TEST_COMPARE (flags, AT_SYMLINK_NOFOLLOW);
      54        return lchmod (path, mode);
      55      }
      56  }
      57  
      58  /* Chose the appropriate path to pass as the path argument to the *at
      59     functions.  */
      60  static const char *
      61  select_path (bool do_relative_path, const char *full_path, const char *relative_path)
      62  {
      63    if (do_relative_path)
      64      return relative_path;
      65    else
      66      return full_path;
      67  }
      68  
      69  static void
      70  update_file_time_to_y2038 (const char *fname, int flags)
      71  {
      72  #ifdef CHECK_TIME64
      73    /* Y2038 threshold plus 1 second.  */
      74    const struct timespec ts[] = { { 0x80000001LL, 0}, { 0x80000001LL } };
      75    TEST_VERIFY_EXIT (utimensat (AT_FDCWD, fname, ts, flags) == 0);
      76  #endif
      77  }
      78  
      79  static void
      80  test_1 (bool do_relative_path, int (*chmod_func) (int fd, const char *, mode_t, int))
      81  {
      82    char *tempdir = support_create_temp_directory ("tst-lchmod-");
      83  #ifdef CHECK_TIME64
      84    if (!support_path_support_time64 (tempdir))
      85      {
      86        puts ("info: test skipped, filesystem does not support 64 bit time_t");
      87        return;
      88      }
      89  #endif
      90  
      91    char *path_dangling = xasprintf ("%s/dangling", tempdir);
      92    char *path_file = xasprintf ("%s/file", tempdir);
      93    char *path_loop = xasprintf ("%s/loop", tempdir);
      94    char *path_missing = xasprintf ("%s/missing", tempdir);
      95    char *path_to_file = xasprintf ("%s/to-file", tempdir);
      96  
      97    int fd;
      98    if (do_relative_path)
      99      fd = xopen (tempdir, O_DIRECTORY | O_RDONLY, 0);
     100    else
     101      fd = AT_FDCWD;
     102  
     103    add_temp_file (path_dangling);
     104    add_temp_file (path_loop);
     105    add_temp_file (path_file);
     106    add_temp_file (path_to_file);
     107  
     108    support_write_file_string (path_file, "");
     109    xsymlink ("file", path_to_file);
     110    xsymlink ("loop", path_loop);
     111    xsymlink ("target-does-not-exist", path_dangling);
     112  
     113    update_file_time_to_y2038 (path_file, 0);
     114    update_file_time_to_y2038 (path_to_file, AT_SYMLINK_NOFOLLOW);
     115  
     116    /* Check that the modes do not collide with what we will use in the
     117       test.  */
     118    struct stat st;
     119    xstat (path_file, &st);
     120    TEST_VERIFY ((st.st_mode & 0777) != 1);
     121    xlstat (path_to_file, &st);
     122    TEST_VERIFY ((st.st_mode & 0777) != 2);
     123    mode_t original_symlink_mode = st.st_mode;
     124  
     125    /* We should be able to change the mode of a file, including through
     126       the symbolic link to-file.  */
     127    const char *arg = select_path (do_relative_path, path_file, "file");
     128    TEST_COMPARE (chmod_func (fd, arg, 1, 0), 0);
     129    xstat (path_file, &st);
     130    TEST_COMPARE (st.st_mode & 0777, 1);
     131    arg = select_path (do_relative_path, path_to_file, "to-file");
     132    TEST_COMPARE (chmod_func (fd, arg, 2, 0), 0);
     133    xstat (path_file, &st);
     134    TEST_COMPARE (st.st_mode & 0777, 2);
     135    xlstat (path_to_file, &st);
     136    TEST_COMPARE (original_symlink_mode, st.st_mode);
     137    arg = select_path (do_relative_path, path_file, "file");
     138    TEST_COMPARE (chmod_func (fd, arg, 1, 0), 0);
     139    xstat (path_file, &st);
     140    TEST_COMPARE (st.st_mode & 0777, 1);
     141    xlstat (path_to_file, &st);
     142    TEST_COMPARE (original_symlink_mode, st.st_mode);
     143  
     144    /* Changing the mode of a symbolic link should fail.  */
     145    arg = select_path (do_relative_path, path_to_file, "to-file");
     146    int ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
     147    TEST_COMPARE (ret, -1);
     148    TEST_COMPARE (errno, EOPNOTSUPP);
     149  
     150    /* The modes should remain unchanged.  */
     151    xstat (path_file, &st);
     152    TEST_COMPARE (st.st_mode & 0777, 1);
     153    xlstat (path_to_file, &st);
     154    TEST_COMPARE (original_symlink_mode, st.st_mode);
     155  
     156    /* Likewise, changing dangling and looping symbolic links must
     157       fail.  */
     158    const char *paths[] = { path_dangling, path_loop };
     159    for (size_t i = 0; i < array_length (paths); ++i)
     160      {
     161        const char *path = paths[i];
     162        const char *filename = strrchr (path, '/');
     163        TEST_VERIFY_EXIT (filename != NULL);
     164        ++filename;
     165        mode_t new_mode = 010 + i;
     166  
     167        xlstat (path, &st);
     168        TEST_VERIFY ((st.st_mode & 0777) != new_mode);
     169        original_symlink_mode = st.st_mode;
     170        arg = select_path (do_relative_path, path, filename);
     171        ret = chmod_func (fd, arg, new_mode, AT_SYMLINK_NOFOLLOW);
     172        TEST_COMPARE (ret, -1);
     173        TEST_COMPARE (errno, EOPNOTSUPP);
     174        xlstat (path, &st);
     175        TEST_COMPARE (st.st_mode, original_symlink_mode);
     176      }
     177  
     178     /* A missing file should always result in ENOENT.  The presence of
     179        /proc does not matter.  */
     180     arg = select_path (do_relative_path, path_missing, "missing");
     181     TEST_COMPARE (chmod_func (fd, arg, 020, 0), -1);
     182     TEST_COMPARE (errno, ENOENT);
     183     TEST_COMPARE (chmod_func (fd, arg, 020, AT_SYMLINK_NOFOLLOW), -1);
     184     TEST_COMPARE (errno, ENOENT);
     185  
     186     /* Test without available file descriptors.  */
     187     {
     188       struct fd_list fd_list;
     189       fd_list_init (&fd_list);
     190       while (true)
     191         {
     192           int ret = dup (STDOUT_FILENO);
     193           if (ret == -1)
     194             {
     195               if (errno == ENFILE || errno == EMFILE)
     196                 break;
     197               FAIL_EXIT1 ("dup: %m");
     198             }
     199           fd_list_add (&fd_list, ret);
     200           TEST_VERIFY_EXIT (!fd_list_has_failed (&fd_list));
     201         }
     202       /* Without AT_SYMLINK_NOFOLLOW, changing the permissions should
     203          work as before.  */
     204       arg = select_path (do_relative_path, path_file, "file");
     205       TEST_COMPARE (chmod_func (fd, arg, 3, 0), 0);
     206       xstat (path_file, &st);
     207       TEST_COMPARE (st.st_mode & 0777, 3);
     208       /* But with AT_SYMLINK_NOFOLLOW, even if we originally had
     209          support, we may have lost it.  */
     210       ret = chmod_func (fd, arg, 2, AT_SYMLINK_NOFOLLOW);
     211       if (ret == 0)
     212         {
     213           xstat (path_file, &st);
     214           TEST_COMPARE (st.st_mode & 0777, 2);
     215         }
     216       else
     217         {
     218           TEST_COMPARE (ret, -1);
     219           /* The error code from the openat fallback leaks out.  */
     220           if (errno != ENFILE && errno != EMFILE)
     221             TEST_COMPARE (errno, EOPNOTSUPP);
     222         }
     223       xstat (path_file, &st);
     224       TEST_COMPARE (st.st_mode & 0777, 3);
     225  
     226       /* Close the descriptors.  */
     227       for (int *pfd = fd_list_begin (&fd_list); pfd < fd_list_end (&fd_list);
     228            ++pfd)
     229         xclose (*pfd);
     230       fd_list_free (&fd_list);
     231     }
     232  
     233     if (do_relative_path)
     234      xclose (fd);
     235  
     236     free (path_dangling);
     237     free (path_file);
     238     free (path_loop);
     239     free (path_missing);
     240     free (path_to_file);
     241  
     242     free (tempdir);
     243  }
     244  
     245  static void
     246  test_3 (void)
     247  {
     248    puts ("info: testing lchmod");
     249    test_1 (false, fchmodat_with_lchmod);
     250    puts ("info: testing fchmodat with AT_FDCWD");
     251    test_1 (false, fchmodat);
     252    puts ("info: testing fchmodat with relative path");
     253    test_1 (true, fchmodat);
     254  }
     255  
     256  static int
     257  do_test (void)
     258  {
     259    struct support_descriptors *descriptors = support_descriptors_list ();
     260  
     261    /* Run the three tests in the default environment.  */
     262    test_3 ();
     263  
     264    /* Try to set up a /proc-less environment and re-test.  */
     265  #if __has_include (<sys/mount.h>)
     266    if (!support_become_root ())
     267      puts ("warning: could not obtain root-like privileges");
     268    if (!support_enter_mount_namespace ())
     269      puts ("warning: could enter a mount namespace");
     270    else
     271      {
     272        /* Attempt to mount an empty directory over /proc.  */
     273        char *tempdir = support_create_temp_directory ("tst-lchmod-");
     274        bool proc_emptied
     275          = mount (tempdir, "/proc", "none", MS_BIND, NULL) == 0;
     276        if (!proc_emptied)
     277          printf ("warning: bind-mounting /proc failed: %m");
     278        free (tempdir);
     279  
     280        puts ("info: re-running tests (after trying to empty /proc)");
     281        test_3 ();
     282  
     283        if (proc_emptied)
     284          /* Reveal the original /proc, which is needed by the
     285             descriptors check below.  */
     286          TEST_COMPARE (umount ("/proc"), 0);
     287      }
     288  #endif /* <sys/mount.h>.  */
     289  
     290    support_descriptors_check (descriptors);
     291    support_descriptors_free (descriptors);
     292  
     293    return 0;
     294  }
     295  
     296  #include <support/test-driver.c>