(root)/
glibc-2.38/
sysdeps/
unix/
sysv/
linux/
tst-getcwd-smallbuff.c
       1  /* Verify that getcwd returns ERANGE for size 1 byte and does not underflow
       2     buffer when the CWD is too long and is also a mount target of /.  See bug
       3     #28769 or CVE-2021-3999 for more context.
       4     Copyright The GNU Toolchain Authors.
       5     This file is part of the GNU C Library.
       6  
       7     The GNU C Library is free software; you can redistribute it and/or
       8     modify it under the terms of the GNU Lesser General Public
       9     License as published by the Free Software Foundation; either
      10     version 2.1 of the License, or (at your option) any later version.
      11  
      12     The GNU C Library is distributed in the hope that it will be useful,
      13     but WITHOUT ANY WARRANTY; without even the implied warranty of
      14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      15     Lesser General Public License for more details.
      16  
      17     You should have received a copy of the GNU Lesser General Public
      18     License along with the GNU C Library; if not, see
      19     <https://www.gnu.org/licenses/>.  */
      20  
      21  #include <errno.h>
      22  #include <fcntl.h>
      23  #include <intprops.h>
      24  #include <limits.h>
      25  #include <stdio.h>
      26  #include <stdlib.h>
      27  #include <string.h>
      28  #include <sys/mount.h>
      29  #include <sys/stat.h>
      30  #include <sys/types.h>
      31  #include <sys/wait.h>
      32  
      33  #include <sys/socket.h>
      34  #include <sys/un.h>
      35  #include <support/check.h>
      36  #include <support/temp_file.h>
      37  #include <support/test-driver.h>
      38  #include <support/xsched.h>
      39  #include <support/xunistd.h>
      40  
      41  static char *base;
      42  #define BASENAME "tst-getcwd-smallbuff"
      43  #define MOUNT_NAME "mpoint"
      44  static int sockfd[2];
      45  
      46  static void
      47  do_cleanup (void)
      48  {
      49    support_chdir_toolong_temp_directory (base);
      50    TEST_VERIFY_EXIT (rmdir (MOUNT_NAME) == 0);
      51    free (base);
      52  }
      53  
      54  static void
      55  send_fd (const int sock, const int fd)
      56  {
      57    struct msghdr msg = {0};
      58    union
      59      {
      60        struct cmsghdr hdr;
      61        char buf[CMSG_SPACE (sizeof (int))];
      62      } cmsgbuf = {0};
      63    struct cmsghdr *cmsg;
      64    struct iovec vec;
      65    char ch = 'A';
      66    ssize_t n;
      67  
      68    msg.msg_control = &cmsgbuf.buf;
      69    msg.msg_controllen = sizeof (cmsgbuf.buf);
      70  
      71    cmsg = CMSG_FIRSTHDR (&msg);
      72    cmsg->cmsg_len = CMSG_LEN (sizeof (int));
      73    cmsg->cmsg_level = SOL_SOCKET;
      74    cmsg->cmsg_type = SCM_RIGHTS;
      75    memcpy (CMSG_DATA (cmsg), &fd, sizeof (fd));
      76  
      77    vec.iov_base = &ch;
      78    vec.iov_len = 1;
      79    msg.msg_iov = &vec;
      80    msg.msg_iovlen = 1;
      81  
      82    while ((n = sendmsg (sock, &msg, 0)) == -1 && errno == EINTR);
      83  
      84    TEST_VERIFY_EXIT (n == 1);
      85  }
      86  
      87  static int
      88  recv_fd (const int sock)
      89  {
      90    struct msghdr msg = {0};
      91    union
      92      {
      93        struct cmsghdr hdr;
      94        char buf[CMSG_SPACE(sizeof(int))];
      95      } cmsgbuf = {0};
      96    struct cmsghdr *cmsg;
      97    struct iovec vec;
      98    ssize_t n;
      99    char ch = '\0';
     100    int fd = -1;
     101  
     102    vec.iov_base = &ch;
     103    vec.iov_len = 1;
     104    msg.msg_iov = &vec;
     105    msg.msg_iovlen = 1;
     106  
     107    msg.msg_control = &cmsgbuf.buf;
     108    msg.msg_controllen = sizeof (cmsgbuf.buf);
     109  
     110    while ((n = recvmsg (sock, &msg, 0)) == -1 && errno == EINTR);
     111    if (n != 1 || ch != 'A')
     112      return -1;
     113  
     114    cmsg = CMSG_FIRSTHDR (&msg);
     115    if (cmsg == NULL)
     116      return -1;
     117    if (cmsg->cmsg_type != SCM_RIGHTS)
     118      return -1;
     119    memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd));
     120    if (fd < 0)
     121      return -1;
     122    return fd;
     123  }
     124  
     125  static int
     126  child_func (void * const arg)
     127  {
     128    xclose (sockfd[0]);
     129    const int sock = sockfd[1];
     130    char ch;
     131  
     132    TEST_VERIFY_EXIT (read (sock, &ch, 1) == 1);
     133    TEST_VERIFY_EXIT (ch == '1');
     134  
     135    if (mount ("/", MOUNT_NAME, NULL, MS_BIND | MS_REC, NULL))
     136      FAIL_EXIT1 ("mount failed: %m\n");
     137    const int fd = xopen ("mpoint",
     138  			O_RDONLY | O_PATH | O_DIRECTORY | O_NOFOLLOW, 0);
     139  
     140    send_fd (sock, fd);
     141    xclose (fd);
     142  
     143    TEST_VERIFY_EXIT (read (sock, &ch, 1) == 1);
     144    TEST_VERIFY_EXIT (ch == 'a');
     145  
     146    xclose (sock);
     147    return 0;
     148  }
     149  
     150  static void
     151  update_map (char * const mapping, const char * const map_file)
     152  {
     153    const size_t map_len = strlen (mapping);
     154  
     155    const int fd = xopen (map_file, O_WRONLY, 0);
     156    xwrite (fd, mapping, map_len);
     157    xclose (fd);
     158  }
     159  
     160  static void
     161  proc_setgroups_write (const long child_pid, const char * const str)
     162  {
     163    const size_t str_len = strlen(str);
     164  
     165    char setgroups_path[sizeof ("/proc//setgroups") + INT_STRLEN_BOUND (long)];
     166  
     167    snprintf (setgroups_path, sizeof (setgroups_path),
     168  	    "/proc/%ld/setgroups", child_pid);
     169  
     170    const int fd = open (setgroups_path, O_WRONLY);
     171  
     172    if (fd < 0)
     173      {
     174        TEST_VERIFY_EXIT (errno == ENOENT);
     175        FAIL_UNSUPPORTED ("/proc/%ld/setgroups not found\n", child_pid);
     176      }
     177  
     178    xwrite (fd, str, str_len);
     179    xclose(fd);
     180  }
     181  
     182  static char child_stack[1024 * 1024];
     183  
     184  int
     185  do_test (void)
     186  {
     187    base = support_create_and_chdir_toolong_temp_directory (BASENAME);
     188  
     189    xmkdir (MOUNT_NAME, S_IRWXU);
     190    atexit (do_cleanup);
     191  
     192    /* Check whether user namespaces are supported.  */
     193    {
     194      pid_t pid = xfork ();
     195      if (pid == 0)
     196        {
     197  	if (unshare (CLONE_NEWUSER | CLONE_NEWNS) != 0)
     198  	  _exit (EXIT_UNSUPPORTED);
     199  	else
     200  	  _exit (0);
     201        }
     202      int status;
     203      xwaitpid (pid, &status, 0);
     204      TEST_VERIFY_EXIT (WIFEXITED (status));
     205      if (WEXITSTATUS (status) != 0)
     206        return WEXITSTATUS (status);
     207    }
     208  
     209    TEST_VERIFY_EXIT (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfd) == 0);
     210    pid_t child_pid = xclone (child_func, NULL, child_stack,
     211  			    sizeof (child_stack),
     212  			    CLONE_NEWUSER | CLONE_NEWNS | SIGCHLD);
     213  
     214    xclose (sockfd[1]);
     215    const int sock = sockfd[0];
     216  
     217    char map_path[sizeof ("/proc//uid_map") + INT_STRLEN_BOUND (long)];
     218    char map_buf[sizeof ("0  1") + INT_STRLEN_BOUND (long)];
     219  
     220    snprintf (map_path, sizeof (map_path), "/proc/%ld/uid_map",
     221  	    (long) child_pid);
     222    snprintf (map_buf, sizeof (map_buf), "0 %ld 1", (long) getuid());
     223    update_map (map_buf, map_path);
     224  
     225    proc_setgroups_write ((long) child_pid, "deny");
     226    snprintf (map_path, sizeof (map_path), "/proc/%ld/gid_map",
     227  	    (long) child_pid);
     228    snprintf (map_buf, sizeof (map_buf), "0 %ld 1", (long) getgid());
     229    update_map (map_buf, map_path);
     230  
     231    TEST_VERIFY_EXIT (send (sock, "1", 1, MSG_NOSIGNAL) == 1);
     232    const int fd = recv_fd (sock);
     233    TEST_VERIFY_EXIT (fd >= 0);
     234    TEST_VERIFY_EXIT (fchdir (fd) == 0);
     235  
     236    static char buf[2 * 10 + 1];
     237    memset (buf, 'A', sizeof (buf));
     238  
     239    /* Finally, call getcwd and check if it resulted in a buffer underflow.  */
     240    char * cwd = getcwd (buf + sizeof (buf) / 2, 1);
     241    TEST_VERIFY (cwd == NULL);
     242    TEST_VERIFY (errno == ERANGE);
     243  
     244    for (int i = 0; i < sizeof (buf); i++)
     245      if (buf[i] != 'A')
     246        {
     247  	printf ("buf[%d] = %02x\n", i, (unsigned int) buf[i]);
     248  	support_record_failure ();
     249        }
     250  
     251    TEST_VERIFY_EXIT (send (sock, "a", 1, MSG_NOSIGNAL) == 1);
     252    xclose (sock);
     253    TEST_VERIFY_EXIT (xwaitpid (child_pid, NULL, 0) == child_pid);
     254  
     255    return 0;
     256  }
     257  
     258  #define CLEANUP_HANDLER do_cleanup
     259  #include <support/test-driver.c>