From 534e36bbf70c6209e4fbd1bb7df778d1ce8237fa Mon Sep 17 00:00:00 2001 From: Adhemerval Zanella Date: Thu, 22 Oct 2026 15:19:10 -0300 Subject: [PATCH] io: Reject fts_open root arguments longer than USHRT_MAX (BZ 34782) FTSENT::fts_namelen and fts_pathlen are unsigned short in glibc, but fts_alloc stores the length of a root argument without checking it. With an argument longer than USHRT_MAX both lengths wrap around, and the fts_path of the root entry is truncated and may not be NUL-terminated. glibc 2.43 failed with ENAMETOOLONG instead. Make fts_alloc fail with ENAMETOOLONG if the name does not fit, so fts_open fails as before. Checked on x86_64-linux-gnu and i686-linux-gnu. Reviewed-by: Collin Funk (cherry picked from commit 6b0ceace674c66fe7ca2e4941f20377d18df6bc7) --- io/Makefile | 1 + io/fts-common.c | 8 +++- io/tst-fts-bz34782-root.c | 80 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 io/tst-fts-bz34782-root.c diff --git a/io/Makefile b/io/Makefile index 427a14bec7..f80e8b8358 100644 --- a/io/Makefile +++ b/io/Makefile @@ -199,6 +199,7 @@ tests := \ tst-fts \ tst-fts-bz22944 \ tst-fts-bz34782 \ + tst-fts-bz34782-root \ tst-fts-cwdfd-follow \ tst-fts-follow \ tst-fts-lfs \ diff --git a/io/fts-common.c b/io/fts-common.c index d3219071d9..b3a75d2b90 100644 --- a/io/fts-common.c +++ b/io/fts-common.c @@ -2019,8 +2019,14 @@ fts_alloc (FTSOBJ *sp, const char *name, register size_t namelen) #else /* * For glibc, we use a wrapper struct to provide the extra required - * fields without changing the FSENT layout. + * fields without changing the FSENT layout. That layout also keeps + * fts_namelen and fts_pathlen as unsigned short, so reject a name + * that does not fit (only fts_open root arguments can be that long). */ + if (namelen > USHRT_MAX) { + __set_errno (ENAMETOOLONG); + return (NULL); + } len += sizeof (struct FTSENT_wrapper); struct FTSENT_wrapper *wrapper = malloc(len); if (wrapper == NULL) diff --git a/io/tst-fts-bz34782-root.c b/io/tst-fts-bz34782-root.c new file mode 100644 index 0000000000..5df4b09bb5 --- /dev/null +++ b/io/tst-fts-bz34782-root.c @@ -0,0 +1,80 @@ +/* Check fts_open with root arguments around USHRT_MAX bytes (BZ 34782). + Copyright (C) 2026 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* FTSENT::fts_namelen and FTSENT::fts_pathlen are unsigned short, so + fts_open must reject a root argument longer than USHRT_MAX instead of + truncating its length. */ +static void +check_root (size_t len) +{ + char *root = xmalloc (len + 1); + memset (root, 'a', len); + root[len] = '\0'; + char *paths[] = { root, NULL }; + + errno = 0; + FTS *ftsp = fts_open (paths, FTS_PHYSICAL, NULL); + if (len > USHRT_MAX) + { + if (ftsp != NULL) + { + support_record_failure (); + printf ("error: fts_open accepted a %zu byte root\n", len); + fts_close (ftsp); + } + else + TEST_COMPARE (errno, ENAMETOOLONG); + } + else + { + TEST_VERIFY_EXIT (ftsp != NULL); + FTSENT *ent = fts_read (ftsp); + TEST_VERIFY_EXIT (ent != NULL); + /* The name does not exist (and is longer than PATH_MAX on Linux), + but the entry must still describe it correctly. */ + TEST_COMPARE (ent->fts_info, FTS_NS); + TEST_COMPARE (ent->fts_namelen, len); + TEST_COMPARE (ent->fts_pathlen, len); + TEST_COMPARE (strnlen (ent->fts_path, len + 1), len); + TEST_VERIFY (memcmp (ent->fts_path, root, len) == 0); + TEST_COMPARE (fts_close (ftsp), 0); + } + + free (root); +} + +static int +do_test (void) +{ + check_root (USHRT_MAX); + check_root (USHRT_MAX + 1); + check_root (USHRT_MAX + 2); + check_root (2 * (USHRT_MAX + 1) + 10); + return 0; +} + +#include