When FTS_TIGHT_CYCLE_CHECK is enabled, fts_build() leaves directory and
then re-enters it again with updated stat information, which leads to
double hash removal of the same directory in a case the second call to
fstat() fails as then the directory is not reentered, but the caller
of fts_build(), the function fts_add(), stil removes it upon fts_build()
return. This commonly happens on /proc filesystem where permission to
stat process files and directories depends on that process euid and
ptraceability configuration.

Rework the code to not remove the old entry until the new entry is
succesfully inserted to the hash table, as a similar problem could
arise also when the updated entry insertion fails (on a cycle or memory
allocation failure).
---
 ChangeLog | 18 ++++++++++++++++++
 lib/fts.c | 30 ++++++++++++++++++++++--------
 2 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6084329d2e..de51b29c91 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2025-08-15  Petr Malat  <[email protected]>
+
+       fts: Avoid crash if fstat() fails
+       
+       When FTS_TIGHT_CYCLE_CHECK is enabled, fts_build() leaves directory and
+       then re-enters it again with updated stat information, which leads to
+       double hash removal of the same directory in a case the second call to
+       fstat() fails as then the directory is not reentered, but the caller
+       of fts_build(), the function fts_add(), stil removes it upon fts_build()
+       return. This commonly happens on /proc filesystem where permission to
+       stat process files and directories depends on that process euid and
+       ptraceability configuration.
+       
+       Rework the code to not remove the old entry until the new entry is
+       succesfully inserted to the hash table, as a similar problem could
+       arise also when the updated entry insertion fails (on a cycle or memory
+       allocation failure).
+
 2025-08-11  Paul Eggert  <[email protected]>
 
        manywarnings: update C warnings for GCC 15.2
diff --git a/lib/fts.c b/lib/fts.c
index b611e997d4..aafc0dc310 100644
--- a/lib/fts.c
+++ b/lib/fts.c
@@ -1326,9 +1326,8 @@ fts_build (register FTS *sp, int type)
                    benefit/suffer from this feature for now.  */
                 || ISSET (FTS_TIGHT_CYCLE_CHECK))
               {
-                if (!stat_optimization)
-                  LEAVE_DIR (sp, cur, "4");
-                if (fstat (dir_fd, cur->fts_statp) != 0)
+                struct stat statbuf;
+                if (fstat (dir_fd, &statbuf) != 0)
                   {
                     int fstat_errno = errno;
                     closedir_and_clear (cur->fts_dirp);
@@ -1342,13 +1341,28 @@ fts_build (register FTS *sp, int type)
                   }
                 if (stat_optimization)
                   cur->fts_info = FTS_D;
-                else if (! enter_dir (sp, cur))
+                else if (statbuf.st_ino != cur->fts_statp->st_ino ||
+                         statbuf.st_dev != cur->fts_statp->st_dev)
                   {
-                    int err = errno;
-                    closedir_and_clear (cur->fts_dirp);
-                    __set_errno (err);
-                    return NULL;
+                    struct stat stattmp;
+                    bool enter_ok;
+
+                    stattmp = *cur->fts_statp;
+                    *cur->fts_statp = statbuf;
+                    enter_ok = enter_dir (sp, cur);
+                    *cur->fts_statp = stattmp;
+
+                    if (enter_ok)
+                      LEAVE_DIR (sp, cur, "4");
+                    else
+                      {
+                        int err = errno;
+                        closedir_and_clear (cur->fts_dirp);
+                        __set_errno (err);
+                        return NULL;
+                      }
                   }
+                *cur->fts_statp = statbuf;
               }
           }
 
-- 
2.39.5


Reply via email to