Message ID | 20220119082147.3352868-2-siddhesh@sourceware.org |
---|---|
State | New |
Headers | show |
Series | Fixes for CVE-2021-3998 and CVE-2021-3999 | expand |
On 19/01/2022 13:51, Siddhesh Poyarekar via Libc-alpha wrote: > 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. > > Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org> > --- > support/temp_file.c | 173 +++++++++++++++++++++++++++++++++++++++++--- > support/temp_file.h | 9 +++ > 2 files changed, 173 insertions(+), 9 deletions(-) > > diff --git a/support/temp_file.c b/support/temp_file.c > index e7bb8aadb9..59060e573f 100644 > --- a/support/temp_file.c > +++ b/support/temp_file.c > @@ -1,5 +1,6 @@ > /* Temporary file handling for tests. > 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,32 @@ 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; > +static bool toolong_subdir_initialized; > + > +/* Return the maximum size of path on the target. */ > +static inline size_t > +get_path_max (void) > +{ > +#ifdef PATH_MAX > + return PATH_MAX; > +#else > + size_t path_max = pathconf ("/", _PC_PATH_MAX); > + return (path_max < 0 ? 1024 > + : path_max <= PTRDIFF_MAX ? path_max : PTRDIFF_MAX); > +#endif > +} > + > +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 +74,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 +118,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 +127,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_subdir_initialized (void) > +{ > + if (!toolong_subdir_initialized) > + FAIL_EXIT1 ("uninitialized toolong directory tree\n"); > +} > + > +static void > +initialize_toolong_subdir (void) > +{ > + size_t sz = NAME_MAX; > + char testname[NAME_MAX]; > + int ret; > + > + if (toolong_subdir_initialized) > + return; > + > + memset (testname, 'X', sz - 1); > + > + /* Explore the name limit on the file system. This should typically be > + NAME_MAX, but it could be less on some fuse filesystems. */ > + do > + { > + struct stat statbuf; > + > + testname[sz - 1] = '\0'; > + ret = stat (testname, &statbuf); > + } > + while (ret != 0 && errno == ENAMETOOLONG && (sz = sz / 2) > 0); > + > + if (ret != 0 && errno == ENAMETOOLONG) > + FAIL_EXIT1 ("stat (%s) failed with ENAMETOOLONG\n", testname); > + > + toolong_subdir = xmalloc (sz); > + strcpy (toolong_subdir, testname); > + toolong_subdir_initialized = true; > +} > + > +char * > +support_create_and_chdir_toolong_temp_directory (const char *basename) > +{ > + size_t path_max = get_path_max (); > + > + char *base = create_temp_directory_internal (basename, true); > + > + xchdir (base); > + > + initialize_toolong_subdir (); > + > + 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 <= path_max / sz; i++) > + { > + xmkdir (toolong_subdir, S_IRWXU); This still doesn't work on the trybot, which somehow fails with ENAMETOOLONG even though stat() on that length worked in initialize_toolong_subdir. I'm going to need to reproduce the environment properly to figure out what's going on. Siddhesh > + xchdir (toolong_subdir); > + } > + return base; > +} > > void > -support_set_test_dir (const char *path) > +support_chdir_toolong_temp_directory (const char *base) > { > - test_dir = path; > + size_t path_max = get_path_max (); > + ensure_toolong_subdir_initialized (); > + > + xchdir (base); > + > + size_t sz = strlen (toolong_subdir); > + for (size_t i = 0; i <= path_max / sz; i++) > + xchdir (toolong_subdir); > +} > + > +/* Helper functions called by the test skeleton follow. */ > + > +static void > +remove_toolong_subdirs (const char *base) > +{ > + size_t path_max = get_path_max (); > + > + ensure_toolong_subdir_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 <= path_max / sz; levels++) > + if (chdir (toolong_subdir) != 0) > + { > + printf ("warning: toolong cleanup failed: chdir (\"%s\"): %m\n", > + toolong_subdir); > + return; > + } > + > + /* 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 +267,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); > @@ -133,6 +280,8 @@ support_delete_temp_files (void) > free (temp_name_list); > temp_name_list = next; > } > + > + free (toolong_subdir); > } > > void > @@ -147,3 +296,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 50a443abe4..8459ddda72 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 */
On Wed, Jan 19, 2022 at 7:21 AM Siddhesh Poyarekar <siddhesh@gotplt.org> wrote: > > On 19/01/2022 13:51, Siddhesh Poyarekar via Libc-alpha wrote: > > 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. Note that recentish linux kernel versions do not allow d_name 's larger than kernel 's PATH_MAX definition, in that case getdents returns EIO, this behavior is since kernel 5.5.
* Cristian Rodríguez: > On Wed, Jan 19, 2022 at 7:21 AM Siddhesh Poyarekar <siddhesh@gotplt.org> wrote: >> >> On 19/01/2022 13:51, Siddhesh Poyarekar via Libc-alpha wrote: >> > 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. > > Note that recentish linux kernel versions do not allow d_name 's > larger than kernel 's PATH_MAX definition, in that case getdents > returns EIO, this behavior is since kernel 5.5. NAME_MAX or PATH_MAX? d_name should be capped by NAME_MAX, but historically it is not. Thanks, Florian
On 20/01/2022 20:17, Cristian Rodríguez wrote: > On Wed, Jan 19, 2022 at 7:21 AM Siddhesh Poyarekar <siddhesh@gotplt.org> wrote: >> >> On 19/01/2022 13:51, Siddhesh Poyarekar via Libc-alpha wrote: >>> 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. > > Note that recentish linux kernel versions do not allow d_name 's > larger than kernel 's PATH_MAX definition, in that case getdents > returns EIO, this behavior is since kernel 5.5. > The d_name in these helpers is limited to _PC_NAME_MAX so AFAICT we shouldn't run into issues with that. Thanks, Siddhesh
On Thu, Jan 20, 2022 at 11:56 AM Florian Weimer <fweimer@redhat.com> wrote: > > * Cristian Rodríguez: > > > On Wed, Jan 19, 2022 at 7:21 AM Siddhesh Poyarekar <siddhesh@gotplt.org> wrote: > >> > >> On 19/01/2022 13:51, Siddhesh Poyarekar via Libc-alpha wrote: > >> > 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. > > > > Note that recentish linux kernel versions do not allow d_name 's > > larger than kernel 's PATH_MAX definition, in that case getdents > > returns EIO, this behavior is since kernel 5.5. > > NAME_MAX or PATH_MAX? d_name should be capped by NAME_MAX, but > historically it is not. PATH_MAX.. I wrote that correctly, see https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2c6b7bcd747201441923a0d3062577a8d1fbd8f8
diff --git a/support/temp_file.c b/support/temp_file.c index e7bb8aadb9..59060e573f 100644 --- a/support/temp_file.c +++ b/support/temp_file.c @@ -1,5 +1,6 @@ /* Temporary file handling for tests. 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,32 @@ 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; +static bool toolong_subdir_initialized; + +/* Return the maximum size of path on the target. */ +static inline size_t +get_path_max (void) +{ +#ifdef PATH_MAX + return PATH_MAX; +#else + size_t path_max = pathconf ("/", _PC_PATH_MAX); + return (path_max < 0 ? 1024 + : path_max <= PTRDIFF_MAX ? path_max : PTRDIFF_MAX); +#endif +} + +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 +74,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 +118,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 +127,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_subdir_initialized (void) +{ + if (!toolong_subdir_initialized) + FAIL_EXIT1 ("uninitialized toolong directory tree\n"); +} + +static void +initialize_toolong_subdir (void) +{ + size_t sz = NAME_MAX; + char testname[NAME_MAX]; + int ret; + + if (toolong_subdir_initialized) + return; + + memset (testname, 'X', sz - 1); + + /* Explore the name limit on the file system. This should typically be + NAME_MAX, but it could be less on some fuse filesystems. */ + do + { + struct stat statbuf; + + testname[sz - 1] = '\0'; + ret = stat (testname, &statbuf); + } + while (ret != 0 && errno == ENAMETOOLONG && (sz = sz / 2) > 0); + + if (ret != 0 && errno == ENAMETOOLONG) + FAIL_EXIT1 ("stat (%s) failed with ENAMETOOLONG\n", testname); + + toolong_subdir = xmalloc (sz); + strcpy (toolong_subdir, testname); + toolong_subdir_initialized = true; +} + +char * +support_create_and_chdir_toolong_temp_directory (const char *basename) +{ + size_t path_max = get_path_max (); + + char *base = create_temp_directory_internal (basename, true); + + xchdir (base); + + initialize_toolong_subdir (); + + 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 <= path_max / sz; i++) + { + xmkdir (toolong_subdir, S_IRWXU); + xchdir (toolong_subdir); + } + return base; +} void -support_set_test_dir (const char *path) +support_chdir_toolong_temp_directory (const char *base) { - test_dir = path; + size_t path_max = get_path_max (); + ensure_toolong_subdir_initialized (); + + xchdir (base); + + size_t sz = strlen (toolong_subdir); + for (size_t i = 0; i <= path_max / sz; i++) + xchdir (toolong_subdir); +} + +/* Helper functions called by the test skeleton follow. */ + +static void +remove_toolong_subdirs (const char *base) +{ + size_t path_max = get_path_max (); + + ensure_toolong_subdir_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 <= path_max / sz; levels++) + if (chdir (toolong_subdir) != 0) + { + printf ("warning: toolong cleanup failed: chdir (\"%s\"): %m\n", + toolong_subdir); + return; + } + + /* 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 +267,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); @@ -133,6 +280,8 @@ support_delete_temp_files (void) free (temp_name_list); temp_name_list = next; } + + free (toolong_subdir); } void @@ -147,3 +296,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 50a443abe4..8459ddda72 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 */
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. Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org> --- support/temp_file.c | 173 +++++++++++++++++++++++++++++++++++++++++--- support/temp_file.h | 9 +++ 2 files changed, 173 insertions(+), 9 deletions(-)