{"id":2225254,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2225254/?format=json","web_url":"http://patchwork.ozlabs.org/project/glibc/patch/20260420174349.3804322-1-adhemerval.zanella@linaro.org/","project":{"id":41,"url":"http://patchwork.ozlabs.org/api/1.1/projects/41/?format=json","name":"GNU C Library","link_name":"glibc","list_id":"libc-alpha.sourceware.org","list_email":"libc-alpha@sourceware.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20260420174349.3804322-1-adhemerval.zanella@linaro.org>","date":"2026-04-20T17:43:20","name":"[v2] io: Fix silent readdir failures in ftw/nftw (BZ 33085)","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"866599a0b9ae444aac1a459bd5265e51f714784f","submitter":{"id":66065,"url":"http://patchwork.ozlabs.org/api/1.1/people/66065/?format=json","name":"Adhemerval Zanella","email":"adhemerval.zanella@linaro.org"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/glibc/patch/20260420174349.3804322-1-adhemerval.zanella@linaro.org/mbox/","series":[{"id":500653,"url":"http://patchwork.ozlabs.org/api/1.1/series/500653/?format=json","web_url":"http://patchwork.ozlabs.org/project/glibc/list/?series=500653","date":"2026-04-20T17:43:20","name":"[v2] io: Fix silent readdir failures in ftw/nftw (BZ 33085)","version":2,"mbox":"http://patchwork.ozlabs.org/series/500653/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2225254/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2225254/checks/","tags":{},"headers":{"Return-Path":"<libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org>","X-Original-To":["incoming@patchwork.ozlabs.org","libc-alpha@sourceware.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","libc-alpha@sourceware.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=cklplUX5;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org\n (client-ip=2620:52:6:3111::32; helo=vm01.sourceware.org;\n envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org;\n receiver=patchwork.ozlabs.org)","sourceware.org;\n\tdkim=pass (2048-bit key,\n unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256\n header.s=google header.b=cklplUX5","sourceware.org;\n dmarc=pass (p=none dis=none) header.from=linaro.org","sourceware.org; spf=pass smtp.mailfrom=linaro.org","server2.sourceware.org;\n arc=none smtp.remote-ip=2607:f8b0:4864:20::e35"],"Received":["from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fztCS16Vkz1yD4\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 21 Apr 2026 03:44:20 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 29CFA4BA23CF\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 20 Apr 2026 17:44:18 +0000 (GMT)","from mail-vs1-xe35.google.com (mail-vs1-xe35.google.com\n [IPv6:2607:f8b0:4864:20::e35])\n by sourceware.org (Postfix) with ESMTPS id 55C924BA23CF\n for <libc-alpha@sourceware.org>; Mon, 20 Apr 2026 17:43:56 +0000 (GMT)","by mail-vs1-xe35.google.com with SMTP id\n ada2fe7eead31-60fa5eb3ee1so1082078137.2\n for <libc-alpha@sourceware.org>; Mon, 20 Apr 2026 10:43:56 -0700 (PDT)","from mandiga.. ([2804:1b3:a7c3:d5d0:f11f:bea9:f78d:1be4])\n by smtp.gmail.com with ESMTPSA id\n a1e0cc1a2514c-9589098226dsm4885178241.5.2026.04.20.10.43.52\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 20 Apr 2026 10:43:53 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 29CFA4BA23CF","OpenDKIM Filter v2.11.0 sourceware.org 55C924BA23CF"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 55C924BA23CF","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 55C924BA23CF","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1776707036; cv=none;\n b=YBC4gPouvWpm7+hWvACddHHB0cs9kW0unoRGXSygfjFYcnUyiouw7tEcXAm98bzExSRdBakSEPO9NwtjlPvYfVV84KW5gAzLw5w2KbCffOUEPNLSf3E24isA1c989ilFQHtdiyOnSuZdzdCEoLBeU/DpBkem1BJNIWIhUKWXdb4=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1776707036; c=relaxed/simple;\n bh=jWe5BkkbdnDOE8pRDwQBm1UWIE4KsN3fO8JYP11uWsM=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=OSuWosYi3245c2AsBfUzA/3HtD9gnFqUZ1z04AadHfKf+s3W87cxDpjJ15NG7t4wFJTdhXoe9hm6sYt5pMe+V6mwrO4Fiemg/V814yQfPW+EQbXlMFqvvDptFJr/CJICMTIGIwZZ9biHfCG07weFz2KV0qpfxX4CoWhJ1rcsS6I=","ARC-Authentication-Results":"i=1; server2.sourceware.org","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=linaro.org; s=google; t=1776707034; x=1777311834; darn=sourceware.org;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:from:to:cc:subject:date:message-id:reply-to;\n bh=XrZ/nZZgiF/JrjO/sqRpgtdTPQOoKFgsOjAmhOtLjc8=;\n b=cklplUX5Cu9/Zcedq70mZqMTEf/fmnKyvlNHGIfJxDvu2T9X00XUZKDMwD78jhGddM\n ebEEm3+oBSYKPFuN62R5Fj/xQUAdg4HL4I/yMB+DOPYGOg/rYLHzrtL2DQQ9HFGJTrpc\n xnPwBwpLsFOlDimitGfI4/F5/q7YRPWhUXB90DesGzsoUf3ZEmjeNEDxOtJniglMLqJO\n nKf8KzcEMG9lVdkUtuf7xGdborxYXDUKlNscLAMhRi9MYgmSDVSZzy0TJHQe5gRzO4lq\n 0tcoeYsc629beOdOWI0NU+3FnYZFQRQJJsHxh2SjUIYXSbsv5g0anEKbc+8hMERAyrml\n gPGA==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1776707034; x=1777311834;\n h=content-transfer-encoding:mime-version:message-id:date:subject:cc\n :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date\n :message-id:reply-to;\n bh=XrZ/nZZgiF/JrjO/sqRpgtdTPQOoKFgsOjAmhOtLjc8=;\n b=pEIE5xGG2zdFIiSY7H6lu+jk2PoOuyU1DDk10v/MmYaoM7SO8NAQydgbo2TBsQ5ukl\n hz+EINmsps39RaYleCpsrNCbDCqwoKScp0RxRndCbhRQ8nntBgz1B+P8zbos8qbZMijt\n x5ZC4/3edkToR93eimVJYo9SxTI6BqqfPXLEZ3V78IepV/eojWj5+q3pXdfahHxBVgXa\n Lx2+/6FK8EdnLbUeHL7TWSYdsHRlSuyLe3uYerVt+dCE0niBwJweH7zutUduMy6O/Pop\n dYrXEMuvRJosjJgWEilNvlFxNC1eNEhwg6jtrKtkd3wQiM3HYTzcJGsehDg7k4cKzaWj\n ERJg==","X-Gm-Message-State":"AOJu0YwDOTeOUw+PMFsAVuLJfXDXSZ/e1KP8fFbiXGyh5Gf5r11NH/Yl\n ubzpHQoy4FPctcOHbaD7W/Fz7TUTNFWJ9nZHlR+DlB4sWwvmZnZUXpFyGk3TY0xTAEhN1dQYal6\n z/H8g","X-Gm-Gg":"AeBDiesbKHQ6/wJt/hv1/mpRFi1/JMjQfBrTWAs9h63sL2nIgy2/DCGImFu97kDOmoj\n /M5QT51SMgVprhG0qIu2CyL6hQ8I0jSeA9n3Fk+B01DTB+I9eWNpNE8+tfeQzfOtjVqbbAP99tK\n 7AwtSWnBuJypPQltZlBeXvysRTrfrqIwezB2eaL3AdfyLo7FbnIQdBh/w+YYKwVxH9r1NGGrTrD\n WDuxHepA8sSGbXq8Jj/+ps0bbW+WD7xSCXRYHjSmU/wtMzRECKq43mDWrs/KcwIEcsD+sV/lW7M\n AfZVvds/pYqNp6yp2jzR/kxP6KoyXfcgnPoRIZ14pKepec+erbsb3+yji1JPqnEc73MeXhii2Wi\n Jer7V7aeT4JVidQoTfneYSXJH+kViKPWa44ap/UhjBOAxPhXWqCQqEBcutYFDfA6fP2BVxX9REH\n aLxwMCfdhmQj6Vq8HvWgmx8+UfNUfRkWsLiJtGa3Nxvps1bQdoAnZ0xHXl","X-Received":"by 2002:a05:6102:a4c:b0:604:e063:4fa8 with SMTP id\n ada2fe7eead31-616f7e6deb8mr5391953137.26.1776707034173;\n Mon, 20 Apr 2026 10:43:54 -0700 (PDT)","From":"Adhemerval Zanella <adhemerval.zanella@linaro.org>","To":"libc-alpha@sourceware.org","Cc":"DJ Delorie <dj@redhat.com>","Subject":"[PATCH v2] io: Fix silent readdir failures in ftw/nftw (BZ 33085)","Date":"Mon, 20 Apr 2026 14:43:20 -0300","Message-ID":"<20260420174349.3804322-1-adhemerval.zanella@linaro.org>","X-Mailer":"git-send-email 2.43.0","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-BeenThere":"libc-alpha@sourceware.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Libc-alpha mailing list <libc-alpha.sourceware.org>","List-Unsubscribe":"<https://sourceware.org/mailman/options/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=unsubscribe>","List-Archive":"<https://sourceware.org/pipermail/libc-alpha/>","List-Post":"<mailto:libc-alpha@sourceware.org>","List-Help":"<mailto:libc-alpha-request@sourceware.org?subject=help>","List-Subscribe":"<https://sourceware.org/mailman/listinfo/libc-alpha>,\n <mailto:libc-alpha-request@sourceware.org?subject=subscribe>","Errors-To":"libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org"},"content":"The {n}ftw functions fails to distinguish between the end of a directory\nstream and an read error.  The existing implementation treated all\nNULL returns as the end of the stream, silently swallowing file system\nor I/O errors and incorrectly reporting a successful traversal.\n\nThis patch fixes the issue in two places within io/ftw.c:\n\n1. In open_dir_stream (the fallback loop used when the file\n   descriptor limit is exhausted and directory entries must be cached).\n\n2. In ftw_dir (the main directory reading loop, specifically within\n   FTW_STATE_STREAM_LOOP).\n\nThe testcasuse uses the FUSE libsupport to trigger getdents failure\nin this two readdir calls.\n\nChecked on x86_64-linux-gnu and i686-linux-gnu.\n--\nChanges from v1:\n- Add extra free on open_dir_stream early exit.\n- Fixed typos.\n---\n io/Makefile          |   1 +\n io/ftw-common.c      |  20 ++++-\n io/tst-ftw-bz33085.c | 195 +++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 214 insertions(+), 2 deletions(-)\n create mode 100644 io/tst-ftw-bz33085.c","diff":"diff --git a/io/Makefile b/io/Makefile\nindex 2a5b4dcb65..151dd36418 100644\n--- a/io/Makefile\n+++ b/io/Makefile\n@@ -202,6 +202,7 @@ tests := \\\n   tst-fts-newflags \\\n   tst-ftw-bz26353 \\\n   tst-ftw-bz28126 \\\n+  tst-ftw-bz33085 \\\n   tst-ftw-lnk \\\n   tst-futimens \\\n   tst-futimes \\\ndiff --git a/io/ftw-common.c b/io/ftw-common.c\nindex 07df0ab25a..17349a284d 100644\n--- a/io/ftw-common.c\n+++ b/io/ftw-common.c\n@@ -246,8 +246,19 @@ open_dir_stream (int *dfdp, struct ftw_data *data, struct dir_data *dirp)\n \t  struct dirent64 *d;\n \t  size_t actsize = 0;\n \n-\t  while ((d = __readdir64 (st)) != NULL)\n+\t  while (1)\n \t    {\n+\t      errno = 0;\n+\t      d = __readdir64 (st);\n+\t      if (d == NULL)\n+\t\t{\n+\t\t  if (errno != 0)\n+\t\t    {\n+\t\t      free (buf);\n+\t\t      return -1;\n+\t\t    }\n+\t\t  break;\n+\t\t}\n \t      size_t this_len = NAMLEN (d);\n \t      if (actsize + this_len + 2 >= bufsize)\n \t\t{\n@@ -607,6 +618,7 @@ ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)\n \t      continue;\n \t    }\n \n+\t  errno = 0;\n \t  struct dirent64 *d = __readdir64 (frame->dir.stream);\n \t  if (d != NULL)\n \t    {\n@@ -631,7 +643,11 @@ ftw_dir (struct ftw_data *data, const struct STRUCT_STAT *st)\n \t\t}\n \t    }\n \t  else\n-\t    frame->state = FTW_STATE_CLEANUP;\n+\t    {\n+\t      frame->state = FTW_STATE_CLEANUP;\n+\t      if (errno != 0)\n+\t\tresult = -1;\n+\t    }\n \t}\n       else if (frame->state == FTW_STATE_CONTENT_LOOP)\n \t{\ndiff --git a/io/tst-ftw-bz33085.c b/io/tst-ftw-bz33085.c\nnew file mode 100644\nindex 0000000000..c318b5a9f9\n--- /dev/null\n+++ b/io/tst-ftw-bz33085.c\n@@ -0,0 +1,195 @@\n+/* Test if nftw correctly handles readdir errors.\n+   Copyright (C) 2026 Free Software Foundation, Inc.\n+   This file is part of the GNU C Library.\n+\n+   The GNU C Library is free software; you can redistribute it and/or\n+   modify it under the terms of the GNU Lesser General Public\n+   License as published by the Free Software Foundation; either\n+   version 2.1 of the License, or (at your option) any later version.\n+\n+   The GNU C Library is distributed in the hope that it will be useful,\n+   but WITHOUT ANY WARRANTY; without even the implied warranty of\n+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n+   Lesser General Public License for more details.\n+\n+   You should have received a copy of the GNU Lesser General Public\n+   License along with the GNU C Library; if not, see\n+   <https://www.gnu.org/licenses/>.  */\n+\n+#include <ftw.h>\n+#include <dirent.h>\n+#include <errno.h>\n+#include <string.h>\n+#include <stdio.h>\n+#include <stdbool.h>\n+#include <sys/stat.h>\n+\n+#include <support/check.h>\n+#include <support/fuse.h>\n+#include <support/support.h>\n+\n+/* The test stresses the readdir calls from nftw, where failures should not\n+   handled as end of stream.  The first is at 'ftw_dir'\n+   (FTW_STATE_STREAM_LOOP) for the default entries read.  The another one is\n+   at open_dir_stream where it is triggered when there is a file description\n+   exaustion and the code must close an existing stream to make room for the\n+   new subdirectory stream.  */\n+\n+static _Atomic bool readdir_triggered = false;\n+\n+static void\n+fuse_thread (struct support_fuse *f, void *closure)\n+{\n+  struct fuse_in_header *inh;\n+\n+  while ((inh = support_fuse_next (f)) != NULL)\n+    {\n+      switch (inh->opcode)\n+        {\n+        case FUSE_GETATTR:\n+          {\n+            /* We need to respond for both the root (1) and dir1 (2) */\n+            if (inh->nodeid == 1 || inh->nodeid == 2)\n+              {\n+                struct fuse_attr_out out = { 0 };\n+                out.attr_valid = 60;\n+                out.attr.ino = inh->nodeid;\n+                out.attr.mode = S_IFDIR | 0755;\n+                out.attr.nlink = 3; /* Force nftw to look for subdirs */\n+                support_fuse_reply (f, &out, sizeof (out));\n+              }\n+            else\n+              support_fuse_reply_error (f, ENOENT);\n+            break;\n+          }\n+\n+        case FUSE_LOOKUP:\n+          {\n+            const char *name = (const char *) (inh + 1);\n+            if (strcmp (name, \"dir1\") == 0)\n+              {\n+                struct fuse_entry_out out = { 0 };\n+                out.nodeid = 2;\n+                out.attr_valid = 60;\n+                out.entry_valid = 60;\n+                out.attr.ino = 2;\n+                out.attr.mode = S_IFDIR | 0755;\n+                out.attr.nlink = 2;\n+                support_fuse_reply (f, &out, sizeof (out));\n+              }\n+            else\n+              support_fuse_reply_error (f, ENOENT);\n+            break;\n+          }\n+\n+        case FUSE_OPENDIR:\n+          {\n+            struct fuse_open_out out = { 0 };\n+            out.fh = inh->nodeid;\n+            support_fuse_reply (f, &out, sizeof (out));\n+            break;\n+          }\n+\n+        case FUSE_READDIR:\n+          {\n+            const struct fuse_read_in *rin = support_fuse_cast (READ, inh);\n+\n+            if (inh->nodeid == 1) /* Reading the Root directory */\n+              {\n+                if (rin->offset == 0)\n+                  {\n+                    /* First readdir, this happens in FTW_STATE_STREAM_LOOP.\n+                       We yield \"dir1\", prompting nftw to descend.  */\n+                    char buf[256] = { 0 };\n+                    struct fuse_dirent *d = (struct fuse_dirent *) buf;\n+\n+                    d->ino = 2;\n+                    d->off = 1;\n+                    d->type = DT_DIR;\n+                    d->namelen = 4;\n+                    strcpy (d->name, \"dir1\");\n+\n+                    size_t d_size =\n+\t\t      FUSE_DIRENT_ALIGN (sizeof (struct fuse_dirent)\n+\t\t\t\t\t + d->namelen);\n+                    support_fuse_reply (f, buf, d_size);\n+                  }\n+                else\n+                  {\n+\t\t    /* Second readdir, this ONLY happens inside\n+\t\t       open_dir_stream() when nftw tries to cache the\n+\t\t       remaining entries before closing the stream to descend\n+\t\t       into \"dir1\".  */\n+                    readdir_triggered = true;\n+                    support_fuse_reply_error (f, EIO);\n+                  }\n+              }\n+            else\n+              /* Subdirectory logic (shouldn't be reached in this test) */\n+              support_fuse_reply_empty (f);\n+            break;\n+          }\n+\n+        case FUSE_READDIRPLUS:\n+          support_fuse_reply_error (f, EIO);\n+          break;\n+\n+        case FUSE_ACCESS:\n+        case FUSE_RELEASEDIR:\n+          support_fuse_reply_empty (f);\n+          break;\n+\n+        default:\n+          support_fuse_reply_error (f, EIO);\n+        }\n+    }\n+}\n+\n+static int\n+nftw_cb (const char *fpath, const struct stat *sb, int typeflag,\n+\t struct FTW *ftwbuf)\n+{\n+  return 0;\n+}\n+\n+static int\n+do_test (void)\n+{\n+  support_fuse_init ();\n+  struct support_fuse *f = support_fuse_mount (fuse_thread, NULL);\n+\n+  {\n+    /* This forces nftw to immediately exhaust its FD limit when it tries\n+       to descend into 'dir1', forcing it into the open_dir_stream fallback\n+       loop. */\n+    errno = 0;\n+    readdir_triggered = false;\n+    int ret = nftw (support_fuse_mountpoint (f), nftw_cb, 1, FTW_PHYS);\n+    /* Assert that we successfully hit the caching __readdir64 block */\n+    TEST_VERIFY (readdir_triggered);\n+\n+    /* Assert that nftw correctly aborted and propagated the EIO */\n+    TEST_COMPARE (ret, -1);\n+    TEST_COMPARE (errno, EIO);\n+  }\n+\n+  {\n+    /* Use a high descriptor count (10) so nftw doesn't fall back to\n+       caching */\n+    errno = 0;\n+    readdir_triggered = false;\n+    int ret = nftw (support_fuse_mountpoint (f), nftw_cb, 10, FTW_PHYS);\n+    /* Assert that the second readdir in the main loop was actually hit */\n+    TEST_VERIFY (readdir_triggered);\n+\n+    /* Assert that nftw correctly aborts and propagates the EIO\n+       This will fail until the `#if 0` block in ftw.c is patched) */\n+    TEST_COMPARE (ret, -1);\n+    TEST_COMPARE (errno, EIO);\n+  }\n+\n+  support_fuse_unmount (f);\n+  return 0;\n+}\n+\n+#include <support/test-driver.c>\n","prefixes":["v2"]}