public inbox for libc-stable@sourceware.org
 help / color / mirror / Atom feed
* [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX
@ 2022-01-24 22:48 Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 2/7] realpath: Set errno to ENAMETOOLONG for result larger than PATH_MAX [BZ #28770] Aurelien Jarno
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable; +Cc: Siddhesh Poyarekar, Adhemerval Zanella

From: Siddhesh Poyarekar <siddhesh@sourceware.org>

Add new helpers support_create_and_chdir_toolong_temp_directory and
support_chdir_toolong_temp_directory to create and descend into
directory trees longer than PATH_MAX.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
(cherry picked from commit fb7bff12e81c677a6622f724edd4d4987dd9d971)
---
 support/temp_file.c | 161 +++++++++++++++++++++++++++++++++++++++++---
 support/temp_file.h |   9 +++
 2 files changed, 160 insertions(+), 10 deletions(-)

diff --git a/support/temp_file.c b/support/temp_file.c
index c6df641876..e41128c2d4 100644
--- a/support/temp_file.c
+++ b/support/temp_file.c
@@ -1,5 +1,6 @@
 /* Temporary file handling for tests.
-   Copyright (C) 1998-2021 Free Software Foundation, Inc.
+   Copyright (C) 1998-2022 Free Software Foundation, Inc.
+   Copyright The GNU Tools Authors.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -20,15 +21,17 @@
    some 32-bit platforms. */
 #define _FILE_OFFSET_BITS 64
 
+#include <support/check.h>
 #include <support/temp_file.h>
 #include <support/temp_file-internal.h>
 #include <support/support.h>
 
+#include <errno.h>
 #include <paths.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
+#include <xunistd.h>
 
 /* List of temporary files.  */
 static struct temp_name_list
@@ -36,14 +39,20 @@ static struct temp_name_list
   struct temp_name_list *next;
   char *name;
   pid_t owner;
+  bool toolong;
 } *temp_name_list;
 
 /* Location of the temporary files.  Set by the test skeleton via
    support_set_test_dir.  The string is not be freed.  */
 static const char *test_dir = _PATH_TMP;
 
-void
-add_temp_file (const char *name)
+/* Name of subdirectories in a too long temporary directory tree.  */
+static char toolong_subdir[NAME_MAX + 1];
+static bool toolong_initialized;
+static size_t toolong_path_max;
+
+static void
+add_temp_file_internal (const char *name, bool toolong)
 {
   struct temp_name_list *newp
     = (struct temp_name_list *) xcalloc (sizeof (*newp), 1);
@@ -53,12 +62,19 @@ add_temp_file (const char *name)
       newp->name = newname;
       newp->next = temp_name_list;
       newp->owner = getpid ();
+      newp->toolong = toolong;
       temp_name_list = newp;
     }
   else
     free (newp);
 }
 
+void
+add_temp_file (const char *name)
+{
+  add_temp_file_internal (name, false);
+}
+
 int
 create_temp_file_in_dir (const char *base, const char *dir, char **filename)
 {
@@ -90,8 +106,8 @@ create_temp_file (const char *base, char **filename)
   return create_temp_file_in_dir (base, test_dir, filename);
 }
 
-char *
-support_create_temp_directory (const char *base)
+static char *
+create_temp_directory_internal (const char *base, bool toolong)
 {
   char *path = xasprintf ("%s/%sXXXXXX", test_dir, base);
   if (mkdtemp (path) == NULL)
@@ -99,16 +115,132 @@ support_create_temp_directory (const char *base)
       printf ("error: mkdtemp (\"%s\"): %m", path);
       exit (1);
     }
-  add_temp_file (path);
+  add_temp_file_internal (path, toolong);
   return path;
 }
 
-/* Helper functions called by the test skeleton follow.  */
+char *
+support_create_temp_directory (const char *base)
+{
+  return create_temp_directory_internal (base, false);
+}
+
+static void
+ensure_toolong_initialized (void)
+{
+  if (!toolong_initialized)
+    FAIL_EXIT1 ("uninitialized toolong directory tree\n");
+}
+
+static void
+initialize_toolong (const char *base)
+{
+  long name_max = pathconf (base, _PC_NAME_MAX);
+  name_max = (name_max < 0 ? 64
+	      : (name_max < sizeof (toolong_subdir) ? name_max
+		 : sizeof (toolong_subdir) - 1));
+
+  long path_max = pathconf (base, _PC_PATH_MAX);
+  path_max = (path_max < 0 ? 1024
+	      : path_max <= PTRDIFF_MAX ? path_max : PTRDIFF_MAX);
+
+  /* Sanity check to ensure that the test does not create temporary directories
+     in different filesystems because this API doesn't support it.  */
+  if (toolong_initialized)
+    {
+      if (name_max != strlen (toolong_subdir))
+	FAIL_UNSUPPORTED ("name_max: Temporary directories in different"
+			  " filesystems not supported yet\n");
+      if (path_max != toolong_path_max)
+	FAIL_UNSUPPORTED ("path_max: Temporary directories in different"
+			  " filesystems not supported yet\n");
+      return;
+    }
+
+  toolong_path_max = path_max;
+
+  size_t len = name_max;
+  memset (toolong_subdir, 'X', len);
+  toolong_initialized = true;
+}
+
+char *
+support_create_and_chdir_toolong_temp_directory (const char *basename)
+{
+  char *base = create_temp_directory_internal (basename, true);
+  xchdir (base);
+
+  initialize_toolong (base);
+
+  size_t sz = strlen (toolong_subdir);
+
+  /* Create directories and descend into them so that the final path is larger
+     than PATH_MAX.  */
+  for (size_t i = 0; i <= toolong_path_max / sz; i++)
+    {
+      int ret = mkdir (toolong_subdir, S_IRWXU);
+      if (ret != 0 && errno == ENAMETOOLONG)
+	FAIL_UNSUPPORTED ("Filesystem does not support creating too long "
+			  "directory trees\n");
+      else if (ret != 0)
+	FAIL_EXIT1 ("Failed to create directory tree: %m\n");
+      xchdir (toolong_subdir);
+    }
+  return base;
+}
 
 void
-support_set_test_dir (const char *path)
+support_chdir_toolong_temp_directory (const char *base)
 {
-  test_dir = path;
+  ensure_toolong_initialized ();
+
+  xchdir (base);
+
+  size_t sz = strlen (toolong_subdir);
+  for (size_t i = 0; i <= toolong_path_max / sz; i++)
+    xchdir (toolong_subdir);
+}
+
+/* Helper functions called by the test skeleton follow.  */
+
+static void
+remove_toolong_subdirs (const char *base)
+{
+  ensure_toolong_initialized ();
+
+  if (chdir (base) != 0)
+    {
+      printf ("warning: toolong cleanup base failed: chdir (\"%s\"): %m\n",
+	      base);
+      return;
+    }
+
+  /* Descend.  */
+  int levels = 0;
+  size_t sz = strlen (toolong_subdir);
+  for (levels = 0; levels <= toolong_path_max / sz; levels++)
+    if (chdir (toolong_subdir) != 0)
+      {
+	printf ("warning: toolong cleanup failed: chdir (\"%s\"): %m\n",
+		toolong_subdir);
+	break;
+      }
+
+  /* Ascend and remove.  */
+  while (--levels >= 0)
+    {
+      if (chdir ("..") != 0)
+	{
+	  printf ("warning: toolong cleanup failed: chdir (\"..\"): %m\n");
+	  return;
+	}
+      if (remove (toolong_subdir) != 0)
+	{
+	  printf ("warning: could not remove subdirectory: %s: %m\n",
+		  toolong_subdir);
+	  return;
+	}
+    }
 }
 
 void
@@ -123,6 +255,9 @@ support_delete_temp_files (void)
 	 around, to prevent PID reuse.)  */
       if (temp_name_list->owner == pid)
 	{
+	  if (temp_name_list->toolong)
+	    remove_toolong_subdirs (temp_name_list->name);
+
 	  if (remove (temp_name_list->name) != 0)
 	    printf ("warning: could not remove temporary file: %s: %m\n",
 		    temp_name_list->name);
@@ -147,3 +282,9 @@ support_print_temp_files (FILE *f)
       fprintf (f, ")\n");
     }
 }
+
+void
+support_set_test_dir (const char *path)
+{
+  test_dir = path;
+}
diff --git a/support/temp_file.h b/support/temp_file.h
index f3a7fb6f9c..a22964c6fa 100644
--- a/support/temp_file.h
+++ b/support/temp_file.h
@@ -44,6 +44,15 @@ int create_temp_file_in_dir (const char *base, const char *dir,
    returns.  The caller should free this string.  */
 char *support_create_temp_directory (const char *base);
 
+/* Create a temporary directory tree that is longer than PATH_MAX and schedule
+   it for deletion.  BASENAME is used as a prefix for the unique directory
+   name, which the function returns.  The caller should free this string.  */
+char *support_create_and_chdir_toolong_temp_directory (const char *basename);
+
+/* Change into the innermost directory of the directory tree BASE, which was
+   created using support_create_and_chdir_toolong_temp_directory.  */
+void support_chdir_toolong_temp_directory (const char *base);
+
 __END_DECLS
 
 #endif /* SUPPORT_TEMP_FILE_H */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [COMMITTED 2.33 2/7] realpath: Set errno to ENAMETOOLONG for result larger than PATH_MAX [BZ #28770]
  2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
@ 2022-01-24 22:48 ` Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 3/7] tst-realpath-toolong: Fix hurd build Aurelien Jarno
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable; +Cc: Siddhesh Poyarekar, Adhemerval Zanella

From: Siddhesh Poyarekar <siddhesh@sourceware.org>

realpath returns an allocated string when the result exceeds PATH_MAX,
which is unexpected when its second argument is not NULL.  This results
in the second argument (resolved) being uninitialized and also results
in a memory leak since the caller expects resolved to be the same as the
returned value.

Return NULL and set errno to ENAMETOOLONG if the result exceeds
PATH_MAX.  This fixes [BZ #28770], which is CVE-2021-3998.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
(cherry picked from commit ee8d5e33adb284601c00c94687bc907e10aec9bb)
---
 NEWS                          |  5 ++++
 stdlib/Makefile               |  3 ++-
 stdlib/canonicalize.c         | 12 +++++++--
 stdlib/tst-realpath-toolong.c | 49 +++++++++++++++++++++++++++++++++++
 4 files changed, 66 insertions(+), 3 deletions(-)
 create mode 100644 stdlib/tst-realpath-toolong.c

diff --git a/NEWS b/NEWS
index eba875d1f6..5352599d7f 100644
--- a/NEWS
+++ b/NEWS
@@ -26,6 +26,10 @@ Security related changes:
   CVE-2022-23218: Passing an overlong file name to the svcunix_create
   legacy function could result in a stack-based buffer overflow.
 
+  CVE-2021-3998: Passing a path longer than PATH_MAX to the realpath
+  function could result in a memory leak and potential access of
+  uninitialized memory.  Reported by Qualys.
+
 The following bugs are resolved with this release:
 
   [15271] dlfcn function failure after dlmopen terminates process
@@ -46,6 +50,7 @@ The following bugs are resolved with this release:
   [28524] Conversion from ISO-2022-JP-3 with iconv may emit spurious NULs
   [28532] powerpc64[le]: CFI for assembly templated syscalls is incorrect
   [28768] CVE-2022-23218: Buffer overflow in sunrpc svcunix_create
+  [28770] CVE-2021-3998: Unexpected return value from realpath() for too long results
 \f
 Version 2.33
 
diff --git a/stdlib/Makefile b/stdlib/Makefile
index b3b30ab73e..b1eebd568f 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -86,7 +86,8 @@ tests		:= tst-strtol tst-strtod testmb testrand testsort testdiv   \
 		   tst-makecontext-align test-bz22786 tst-strtod-nan-sign \
 		   tst-swapcontext1 tst-setcontext4 tst-setcontext5 \
 		   tst-setcontext6 tst-setcontext7 tst-setcontext8 \
-		   tst-setcontext9 tst-bz20544 tst-canon-bz26341
+		   tst-setcontext9 tst-bz20544 tst-canon-bz26341 \
+		   tst-realpath-toolong
 
 tests-internal	:= tst-strtod1i tst-strtod3 tst-strtod4 tst-strtod5i \
 		   tst-tls-atexit tst-tls-atexit-nodelete
diff --git a/stdlib/canonicalize.c b/stdlib/canonicalize.c
index 698f9ede25..7a23a51b3a 100644
--- a/stdlib/canonicalize.c
+++ b/stdlib/canonicalize.c
@@ -400,8 +400,16 @@ realpath_stk (const char *name, char *resolved,
 
 error:
   *dest++ = '\0';
-  if (resolved != NULL && dest - rname <= get_path_max ())
-    rname = strcpy (resolved, rname);
+  if (resolved != NULL)
+    {
+      if (dest - rname <= get_path_max ())
+	rname = strcpy (resolved, rname);
+      else
+	{
+	  failed = true;
+	  __set_errno (ENAMETOOLONG);
+	}
+    }
 
 error_nomem:
   scratch_buffer_free (&extra_buffer);
diff --git a/stdlib/tst-realpath-toolong.c b/stdlib/tst-realpath-toolong.c
new file mode 100644
index 0000000000..8bed772460
--- /dev/null
+++ b/stdlib/tst-realpath-toolong.c
@@ -0,0 +1,49 @@
+/* Verify that realpath returns NULL with ENAMETOOLONG if the result exceeds
+   NAME_MAX.
+   Copyright The GNU Toolchain Authors.
+   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 <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define BASENAME "tst-realpath-toolong."
+
+int
+do_test (void)
+{
+  char *base = support_create_and_chdir_toolong_temp_directory (BASENAME);
+
+  char buf[PATH_MAX + 1];
+  const char *res = realpath (".", buf);
+
+  /* canonicalize.c states that if the real path is >= PATH_MAX, then
+     realpath returns NULL and sets ENAMETOOLONG.  */
+  TEST_VERIFY (res == NULL);
+  TEST_VERIFY (errno == ENAMETOOLONG);
+
+  free (base);
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [COMMITTED 2.33 3/7] tst-realpath-toolong: Fix hurd build
  2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 2/7] realpath: Set errno to ENAMETOOLONG for result larger than PATH_MAX [BZ #28770] Aurelien Jarno
@ 2022-01-24 22:48 ` Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 4/7] support: Add xclone Aurelien Jarno
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable; +Cc: Siddhesh Poyarekar

From: Siddhesh Poyarekar <siddhesh@sourceware.org>

Define PATH_MAX to a constant if it isn't already defined, like in hurd.

Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
(cherry picked from commit 976db046bc3a3738f69255ae00b0a09b8e77fd9c)
---
 stdlib/tst-realpath-toolong.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/stdlib/tst-realpath-toolong.c b/stdlib/tst-realpath-toolong.c
index 8bed772460..4388890294 100644
--- a/stdlib/tst-realpath-toolong.c
+++ b/stdlib/tst-realpath-toolong.c
@@ -29,6 +29,10 @@
 
 #define BASENAME "tst-realpath-toolong."
 
+#ifndef PATH_MAX
+# define PATH_MAX 1024
+#endif
+
 int
 do_test (void)
 {
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [COMMITTED 2.33 4/7] support: Add xclone
  2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 2/7] realpath: Set errno to ENAMETOOLONG for result larger than PATH_MAX [BZ #28770] Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 3/7] tst-realpath-toolong: Fix hurd build Aurelien Jarno
@ 2022-01-24 22:48 ` Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 5/7] getcwd: Set errno to ERANGE for size == 1 (CVE-2021-3999) Aurelien Jarno
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable

From: Adhemerval Zanella <adhemerval.zanella@linaro.org>

It is a wrapper for Linux clone syscall, to simplify the call to the
use only the most common arguments and remove architecture specific
handling (such as ia64 different name and signature).

(cherry picked from commit de8995a2a04163617c1a233b4b81356ef9f9741f)
---
 support/Makefile |  1 +
 support/xclone.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++
 support/xsched.h | 34 ++++++++++++++++++++++++++++++++
 3 files changed, 85 insertions(+)
 create mode 100644 support/xclone.c
 create mode 100644 support/xsched.h

diff --git a/support/Makefile b/support/Makefile
index 3c0eb200c2..f631846c07 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -92,6 +92,7 @@ libsupport-routines = \
   xchdir \
   xchroot \
   xclock_gettime \
+  xclone \
   xclose \
   xchmod \
   xconnect \
diff --git a/support/xclone.c b/support/xclone.c
new file mode 100644
index 0000000000..924d2b8754
--- /dev/null
+++ b/support/xclone.c
@@ -0,0 +1,50 @@
+/* Auxiliary functions to issue the clone syscall.
+   Copyright (C) 2021 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/>.  */
+
+#ifdef __linux__
+# include <support/check.h>
+# include <stackinfo.h>  /* For _STACK_GROWS_{UP,DOWN}.  */
+# include <xsched.h>
+
+pid_t
+xclone (int (*fn) (void *arg), void *arg, void *stack, size_t stack_size,
+	int flags)
+{
+  pid_t r = -1;
+
+# ifdef __ia64__
+  extern int __clone2 (int (*fn) (void *arg), void *stack, size_t stack_size,
+		       int flags, void *arg, ...);
+  r = __clone2 (f, stack, stack_size, flags, arg, /* ptid */ NULL,
+		/* tls */ NULL, /* ctid  */ ctid);
+# else
+#  if _STACK_GROWS_DOWN
+  r = clone (fn, stack + stack_size, flags, arg, /* ptid */ NULL,
+	     /* tls */ NULL, /* ctid */  NULL);
+#  elif _STACK_GROWS_UP
+  r = clone (fn, stack, flags, arg, /* ptid */ NULL, /* tls */ NULL,
+	     &ctid);
+#  endif
+# endif
+
+  if (r < 0)
+    FAIL_EXIT1 ("clone: %m");
+
+  return r;
+}
+#endif
diff --git a/support/xsched.h b/support/xsched.h
new file mode 100644
index 0000000000..eefd731940
--- /dev/null
+++ b/support/xsched.h
@@ -0,0 +1,34 @@
+/* Wrapper for sched.h functions.
+   Copyright (C) 2021 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/>.  */
+
+#ifndef SUPPORT_XSCHED_H
+#define SUPPORT_XSCHED_H
+
+__BEGIN_DECLS
+
+#include <sched.h>
+#include <sys/types.h>
+
+#ifdef __linux__
+pid_t xclone (int (*fn) (void *arg), void *arg, void *stack,
+	      size_t stack_size, int flags);
+#endif
+
+__END_DECLS
+
+#endif
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [COMMITTED 2.33 5/7] getcwd: Set errno to ERANGE for size == 1 (CVE-2021-3999)
  2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
                   ` (2 preceding siblings ...)
  2022-01-24 22:48 ` [COMMITTED 2.33 4/7] support: Add xclone Aurelien Jarno
@ 2022-01-24 22:48 ` Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 6/7] realpath: Avoid overwriting preexisting error (CVE-2021-3998) Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 7/7] Linux: Detect user namespace support in io/tst-getcwd-smallbuff Aurelien Jarno
  5 siblings, 0 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable
  Cc: Siddhesh Poyarekar, Andreas Schwab, Adhemerval Zanella,
	Qualys Security Advisory

From: Siddhesh Poyarekar <siddhesh@sourceware.org>

No valid path returned by getcwd would fit into 1 byte, so reject the
size early and return NULL with errno set to ERANGE.  This change is
prompted by CVE-2021-3999, which describes a single byte buffer
underflow and overflow when all of the following conditions are met:

- The buffer size (i.e. the second argument of getcwd) is 1 byte
- The current working directory is too long
- '/' is also mounted on the current working directory

Sequence of events:

- In sysdeps/unix/sysv/linux/getcwd.c, the syscall returns ENAMETOOLONG
  because the linux kernel checks for name length before it checks
  buffer size

- The code falls back to the generic getcwd in sysdeps/posix

- In the generic func, the buf[0] is set to '\0' on line 250

- this while loop on line 262 is bypassed:

    while (!(thisdev == rootdev && thisino == rootino))

  since the rootfs (/) is bind mounted onto the directory and the flow
  goes on to line 449, where it puts a '/' in the byte before the
  buffer.

- Finally on line 458, it moves 2 bytes (the underflowed byte and the
  '\0') to the buf[0] and buf[1], resulting in a 1 byte buffer overflow.

- buf is returned on line 469 and errno is not set.

This resolves BZ #28769.

Reviewed-by: Andreas Schwab <schwab@linux-m68k.org>
Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
Signed-off-by: Qualys Security Advisory <qsa@qualys.com>
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
(cherry picked from commit 23e0e8f5f1fb5ed150253d986ecccdc90c2dcd5e)
---
 NEWS                                          |   7 +
 sysdeps/posix/getcwd.c                        |   7 +
 sysdeps/unix/sysv/linux/Makefile              |   7 +-
 .../unix/sysv/linux/tst-getcwd-smallbuff.c    | 241 ++++++++++++++++++
 4 files changed, 261 insertions(+), 1 deletion(-)
 create mode 100644 sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c

diff --git a/NEWS b/NEWS
index 5352599d7f..cc026f03b1 100644
--- a/NEWS
+++ b/NEWS
@@ -30,6 +30,12 @@ Security related changes:
   function could result in a memory leak and potential access of
   uninitialized memory.  Reported by Qualys.
 
+  CVE-2021-3999: Passing a buffer of size exactly 1 byte to the getcwd
+  function may result in an off-by-one buffer underflow and overflow
+  when the current working directory is longer than PATH_MAX and also
+  corresponds to the / directory through an unprivileged mount
+  namespace.  Reported by Qualys.
+
 The following bugs are resolved with this release:
 
   [15271] dlfcn function failure after dlmopen terminates process
@@ -50,6 +56,7 @@ The following bugs are resolved with this release:
   [28524] Conversion from ISO-2022-JP-3 with iconv may emit spurious NULs
   [28532] powerpc64[le]: CFI for assembly templated syscalls is incorrect
   [28768] CVE-2022-23218: Buffer overflow in sunrpc svcunix_create
+  [28769] CVE-2021-3999: Off-by-one buffer overflow/underflow in getcwd()
   [28770] CVE-2021-3998: Unexpected return value from realpath() for too long results
 \f
 Version 2.33
diff --git a/sysdeps/posix/getcwd.c b/sysdeps/posix/getcwd.c
index f11644aae7..242e4dbe45 100644
--- a/sysdeps/posix/getcwd.c
+++ b/sysdeps/posix/getcwd.c
@@ -187,6 +187,13 @@ __getcwd_generic (char *buf, size_t size)
   size_t allocated = size;
   size_t used;
 
+  /* A size of 1 byte is never useful.  */
+  if (allocated == 1)
+    {
+      __set_errno (ERANGE);
+      return NULL;
+    }
+
 #if HAVE_MINIMALLY_WORKING_GETCWD
   /* If AT_FDCWD is not defined, the algorithm below is O(N**2) and
      this is much slower than the system getcwd (at least on
diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index 472eab700d..9531641f82 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -282,7 +282,12 @@ sysdep_routines += xstatconv internal_statvfs internal_statvfs64 \
 
 sysdep_headers += bits/fcntl-linux.h
 
-tests += tst-fallocate tst-fallocate64 tst-o_path-locks
+tests += \
+  tst-fallocate \
+  tst-fallocate64 \
+  tst-getcwd-smallbuff \
+  tst-o_path-locks \
+# tests
 endif
 
 ifeq ($(subdir),elf)
diff --git a/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c b/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c
new file mode 100644
index 0000000000..d460d6e766
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c
@@ -0,0 +1,241 @@
+/* Verify that getcwd returns ERANGE for size 1 byte and does not underflow
+   buffer when the CWD is too long and is also a mount target of /.  See bug
+   #28769 or CVE-2021-3999 for more context.
+   Copyright The GNU Toolchain Authors.
+   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 <errno.h>
+#include <fcntl.h>
+#include <intprops.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xsched.h>
+#include <support/xunistd.h>
+
+static char *base;
+#define BASENAME "tst-getcwd-smallbuff"
+#define MOUNT_NAME "mpoint"
+static int sockfd[2];
+
+static void
+do_cleanup (void)
+{
+  support_chdir_toolong_temp_directory (base);
+  TEST_VERIFY_EXIT (rmdir (MOUNT_NAME) == 0);
+  free (base);
+}
+
+static void
+send_fd (const int sock, const int fd)
+{
+  struct msghdr msg = {0};
+  union
+    {
+      struct cmsghdr hdr;
+      char buf[CMSG_SPACE (sizeof (int))];
+    } cmsgbuf = {0};
+  struct cmsghdr *cmsg;
+  struct iovec vec;
+  char ch = 'A';
+  ssize_t n;
+
+  msg.msg_control = &cmsgbuf.buf;
+  msg.msg_controllen = sizeof (cmsgbuf.buf);
+
+  cmsg = CMSG_FIRSTHDR (&msg);
+  cmsg->cmsg_len = CMSG_LEN (sizeof (int));
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+  memcpy (CMSG_DATA (cmsg), &fd, sizeof (fd));
+
+  vec.iov_base = &ch;
+  vec.iov_len = 1;
+  msg.msg_iov = &vec;
+  msg.msg_iovlen = 1;
+
+  while ((n = sendmsg (sock, &msg, 0)) == -1 && errno == EINTR);
+
+  TEST_VERIFY_EXIT (n == 1);
+}
+
+static int
+recv_fd (const int sock)
+{
+  struct msghdr msg = {0};
+  union
+    {
+      struct cmsghdr hdr;
+      char buf[CMSG_SPACE(sizeof(int))];
+    } cmsgbuf = {0};
+  struct cmsghdr *cmsg;
+  struct iovec vec;
+  ssize_t n;
+  char ch = '\0';
+  int fd = -1;
+
+  vec.iov_base = &ch;
+  vec.iov_len = 1;
+  msg.msg_iov = &vec;
+  msg.msg_iovlen = 1;
+
+  msg.msg_control = &cmsgbuf.buf;
+  msg.msg_controllen = sizeof (cmsgbuf.buf);
+
+  while ((n = recvmsg (sock, &msg, 0)) == -1 && errno == EINTR);
+  if (n != 1 || ch != 'A')
+    return -1;
+
+  cmsg = CMSG_FIRSTHDR (&msg);
+  if (cmsg == NULL)
+    return -1;
+  if (cmsg->cmsg_type != SCM_RIGHTS)
+    return -1;
+  memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd));
+  if (fd < 0)
+    return -1;
+  return fd;
+}
+
+static int
+child_func (void * const arg)
+{
+  xclose (sockfd[0]);
+  const int sock = sockfd[1];
+  char ch;
+
+  TEST_VERIFY_EXIT (read (sock, &ch, 1) == 1);
+  TEST_VERIFY_EXIT (ch == '1');
+
+  if (mount ("/", MOUNT_NAME, NULL, MS_BIND | MS_REC, NULL))
+    FAIL_EXIT1 ("mount failed: %m\n");
+  const int fd = xopen ("mpoint",
+			O_RDONLY | O_PATH | O_DIRECTORY | O_NOFOLLOW, 0);
+
+  send_fd (sock, fd);
+  xclose (fd);
+
+  TEST_VERIFY_EXIT (read (sock, &ch, 1) == 1);
+  TEST_VERIFY_EXIT (ch == 'a');
+
+  xclose (sock);
+  return 0;
+}
+
+static void
+update_map (char * const mapping, const char * const map_file)
+{
+  const size_t map_len = strlen (mapping);
+
+  const int fd = xopen (map_file, O_WRONLY, 0);
+  xwrite (fd, mapping, map_len);
+  xclose (fd);
+}
+
+static void
+proc_setgroups_write (const long child_pid, const char * const str)
+{
+  const size_t str_len = strlen(str);
+
+  char setgroups_path[sizeof ("/proc//setgroups") + INT_STRLEN_BOUND (long)];
+
+  snprintf (setgroups_path, sizeof (setgroups_path),
+	    "/proc/%ld/setgroups", child_pid);
+
+  const int fd = open (setgroups_path, O_WRONLY);
+
+  if (fd < 0)
+    {
+      TEST_VERIFY_EXIT (errno == ENOENT);
+      FAIL_UNSUPPORTED ("/proc/%ld/setgroups not found\n", child_pid);
+    }
+
+  xwrite (fd, str, str_len);
+  xclose(fd);
+}
+
+static char child_stack[1024 * 1024];
+
+int
+do_test (void)
+{
+  base = support_create_and_chdir_toolong_temp_directory (BASENAME);
+
+  xmkdir (MOUNT_NAME, S_IRWXU);
+  atexit (do_cleanup);
+
+  TEST_VERIFY_EXIT (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfd) == 0);
+  pid_t child_pid = xclone (child_func, NULL, child_stack,
+			    sizeof (child_stack),
+			    CLONE_NEWUSER | CLONE_NEWNS | SIGCHLD);
+
+  xclose (sockfd[1]);
+  const int sock = sockfd[0];
+
+  char map_path[sizeof ("/proc//uid_map") + INT_STRLEN_BOUND (long)];
+  char map_buf[sizeof ("0  1") + INT_STRLEN_BOUND (long)];
+
+  snprintf (map_path, sizeof (map_path), "/proc/%ld/uid_map",
+	    (long) child_pid);
+  snprintf (map_buf, sizeof (map_buf), "0 %ld 1", (long) getuid());
+  update_map (map_buf, map_path);
+
+  proc_setgroups_write ((long) child_pid, "deny");
+  snprintf (map_path, sizeof (map_path), "/proc/%ld/gid_map",
+	    (long) child_pid);
+  snprintf (map_buf, sizeof (map_buf), "0 %ld 1", (long) getgid());
+  update_map (map_buf, map_path);
+
+  TEST_VERIFY_EXIT (send (sock, "1", 1, MSG_NOSIGNAL) == 1);
+  const int fd = recv_fd (sock);
+  TEST_VERIFY_EXIT (fd >= 0);
+  TEST_VERIFY_EXIT (fchdir (fd) == 0);
+
+  static char buf[2 * 10 + 1];
+  memset (buf, 'A', sizeof (buf));
+
+  /* Finally, call getcwd and check if it resulted in a buffer underflow.  */
+  char * cwd = getcwd (buf + sizeof (buf) / 2, 1);
+  TEST_VERIFY (cwd == NULL);
+  TEST_VERIFY (errno == ERANGE);
+
+  for (int i = 0; i < sizeof (buf); i++)
+    if (buf[i] != 'A')
+      {
+	printf ("buf[%d] = %02x\n", i, (unsigned int) buf[i]);
+	support_record_failure ();
+      }
+
+  TEST_VERIFY_EXIT (send (sock, "a", 1, MSG_NOSIGNAL) == 1);
+  xclose (sock);
+  TEST_VERIFY_EXIT (xwaitpid (child_pid, NULL, 0) == child_pid);
+
+  return 0;
+}
+
+#define CLEANUP_HANDLER do_cleanup
+#include <support/test-driver.c>
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [COMMITTED 2.33 6/7] realpath: Avoid overwriting preexisting error (CVE-2021-3998)
  2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
                   ` (3 preceding siblings ...)
  2022-01-24 22:48 ` [COMMITTED 2.33 5/7] getcwd: Set errno to ERANGE for size == 1 (CVE-2021-3999) Aurelien Jarno
@ 2022-01-24 22:48 ` Aurelien Jarno
  2022-01-24 22:48 ` [COMMITTED 2.33 7/7] Linux: Detect user namespace support in io/tst-getcwd-smallbuff Aurelien Jarno
  5 siblings, 0 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable; +Cc: Siddhesh Poyarekar, Andreas Schwab

From: Siddhesh Poyarekar <siddhesh@sourceware.org>

Set errno and failure for paths that are too long only if no other error
occurred earlier.

Related: BZ #28770

Reviewed-by: Andreas Schwab <schwab@linux-m68k.org>
Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
(cherry picked from commit 84d2d0fe20bdf94feed82b21b4d7d136db471f03)
---
 stdlib/canonicalize.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/stdlib/canonicalize.c b/stdlib/canonicalize.c
index 7a23a51b3a..e2d4244fc7 100644
--- a/stdlib/canonicalize.c
+++ b/stdlib/canonicalize.c
@@ -404,7 +404,7 @@ error:
     {
       if (dest - rname <= get_path_max ())
 	rname = strcpy (resolved, rname);
-      else
+      else if (!failed)
 	{
 	  failed = true;
 	  __set_errno (ENAMETOOLONG);
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [COMMITTED 2.33 7/7] Linux: Detect user namespace support in io/tst-getcwd-smallbuff
  2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
                   ` (4 preceding siblings ...)
  2022-01-24 22:48 ` [COMMITTED 2.33 6/7] realpath: Avoid overwriting preexisting error (CVE-2021-3998) Aurelien Jarno
@ 2022-01-24 22:48 ` Aurelien Jarno
  5 siblings, 0 replies; 7+ messages in thread
From: Aurelien Jarno @ 2022-01-24 22:48 UTC (permalink / raw)
  To: libc-stable; +Cc: Florian Weimer, Siddhesh Poyarekar

From: Florian Weimer <fweimer@redhat.com>

Otherwise the test fails with certain container runtimes.

Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
(cherry picked from commit 5b8e7980c5dabd9aaefeba4f0208baa8cf7653ee)
---
 sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c b/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c
index d460d6e766..55362f6060 100644
--- a/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c
+++ b/sysdeps/unix/sysv/linux/tst-getcwd-smallbuff.c
@@ -34,6 +34,7 @@
 #include <sys/un.h>
 #include <support/check.h>
 #include <support/temp_file.h>
+#include <support/test-driver.h>
 #include <support/xsched.h>
 #include <support/xunistd.h>
 
@@ -188,6 +189,23 @@ do_test (void)
   xmkdir (MOUNT_NAME, S_IRWXU);
   atexit (do_cleanup);
 
+  /* Check whether user namespaces are supported.  */
+  {
+    pid_t pid = xfork ();
+    if (pid == 0)
+      {
+	if (unshare (CLONE_NEWUSER | CLONE_NEWNS) != 0)
+	  _exit (EXIT_UNSUPPORTED);
+	else
+	  _exit (0);
+      }
+    int status;
+    xwaitpid (pid, &status, 0);
+    TEST_VERIFY_EXIT (WIFEXITED (status));
+    if (WEXITSTATUS (status) != 0)
+      return WEXITSTATUS (status);
+  }
+
   TEST_VERIFY_EXIT (socketpair (AF_UNIX, SOCK_STREAM, 0, sockfd) == 0);
   pid_t child_pid = xclone (child_func, NULL, child_stack,
 			    sizeof (child_stack),
-- 
2.34.1


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-01-24 22:49 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-01-24 22:48 [COMMITTED 2.33 1/7] support: Add helpers to create paths longer than PATH_MAX Aurelien Jarno
2022-01-24 22:48 ` [COMMITTED 2.33 2/7] realpath: Set errno to ENAMETOOLONG for result larger than PATH_MAX [BZ #28770] Aurelien Jarno
2022-01-24 22:48 ` [COMMITTED 2.33 3/7] tst-realpath-toolong: Fix hurd build Aurelien Jarno
2022-01-24 22:48 ` [COMMITTED 2.33 4/7] support: Add xclone Aurelien Jarno
2022-01-24 22:48 ` [COMMITTED 2.33 5/7] getcwd: Set errno to ERANGE for size == 1 (CVE-2021-3999) Aurelien Jarno
2022-01-24 22:48 ` [COMMITTED 2.33 6/7] realpath: Avoid overwriting preexisting error (CVE-2021-3998) Aurelien Jarno
2022-01-24 22:48 ` [COMMITTED 2.33 7/7] Linux: Detect user namespace support in io/tst-getcwd-smallbuff Aurelien Jarno

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).