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 <collin.funk1@gmail.com>
This commit is contained in:
Adhemerval Zanella committed 2026-10-27 09:23:32 -03:00
1 parent a8c5a1366f
commit 6b0ceace67
3 files changed
+88 -1

No files matched your search

+1
View File
@@ -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 \
+7 -1
View File
@@ -2024,8 +2024,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)
+80
View File
@@ -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
<https://www.gnu.org/licenses/>. */
#include <errno.h>
#include <fts.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <support/check.h>
#include <support/support.h>
/* 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 <support/test-driver.c>