git: e66bbe6e02b4 - stable/13 - libc: Check for readdir(2) errors in fts(3)

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Mon, 11 Apr 2022 13:44:04 UTC
The branch stable/13 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=e66bbe6e02b48d1fa57c52321a517dcf42d8eb69

commit e66bbe6e02b48d1fa57c52321a517dcf42d8eb69
Author:     Ganael LAPLANCHE <martymac@FreeBSD.org>
AuthorDate: 2022-03-28 14:54:02 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2022-04-11 13:43:28 +0000

    libc: Check for readdir(2) errors in fts(3)
    
    Previously, such errors were not distinguished from the end-of-directory
    condition.
    
    With improvements from Mahmoud Abumandour <ma.mandourr@gmail.com>.
    
    Reviewed by:    markj
    PR:             262038
    
    (cherry picked from commit 0cff70ca66547ca5b04030ef07e6a0b9759a0184)
---
 lib/libc/gen/fts-compat.c   | 31 ++++++++++++++++++++++++++++---
 lib/libc/gen/fts-compat11.c | 32 +++++++++++++++++++++++++++++---
 lib/libc/gen/fts.c          | 33 ++++++++++++++++++++++++++++++---
 3 files changed, 87 insertions(+), 9 deletions(-)

diff --git a/lib/libc/gen/fts-compat.c b/lib/libc/gen/fts-compat.c
index ccdd4f15905b..9f295110f1c7 100644
--- a/lib/libc/gen/fts-compat.c
+++ b/lib/libc/gen/fts-compat.c
@@ -610,6 +610,19 @@ __fts_set_clientptr_44bsd(FTS *sp, void *clientptr)
 	sp->fts_clientptr = clientptr;
 }
 
+static struct freebsd11_dirent *
+fts_safe_readdir(DIR *dirp, int *readdir_errno)
+{
+	struct freebsd11_dirent *ret;
+
+	errno = 0;
+	if (!dirp)
+		return (NULL);
+	ret = freebsd11_readdir(dirp);
+	*readdir_errno = errno;
+	return (ret);
+}
+
 /*
  * This is the tricky part -- do not casually change *anything* in here.  The
  * idea is to build the linked list of entries that are used by fts_children
@@ -634,7 +647,7 @@ fts_build(FTS *sp, int type)
 	DIR *dirp;
 	void *oldaddr;
 	int cderrno, descend, len, level, maxlen, nlinks, oflag, saved_errno,
-	    nostat, doadjust, dnamlen;
+	    nostat, doadjust, dnamlen, readdir_errno;
 	char *cp;
 
 	/* Set current node pointer. */
@@ -738,8 +751,9 @@ fts_build(FTS *sp, int type)
 
 	/* Read the directory, attaching each entry to the `link' pointer. */
 	doadjust = 0;
+	readdir_errno = 0;
 	for (head = tail = NULL, nitems = 0;
-	    dirp && (dp = freebsd11_readdir(dirp));) {
+	    (dp = fts_safe_readdir(dirp, &readdir_errno));) {
 		dnamlen = dp->d_namlen;
 		if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
 			continue;
@@ -839,6 +853,16 @@ mem1:				saved_errno = errno;
 		}
 		++nitems;
 	}
+
+	if (readdir_errno) {
+		cur->fts_errno = readdir_errno;
+		/*
+		 * If we've not read any items yet, treat
+		 * the error as if we can't access the dir.
+		 */
+		cur->fts_info = nitems ? FTS_ERR : FTS_DNR;
+	}
+
 	if (dirp)
 		(void)closedir(dirp);
 
@@ -877,7 +901,8 @@ mem1:				saved_errno = errno;
 
 	/* If didn't find anything, return NULL. */
 	if (!nitems) {
-		if (type == BREAD)
+		if (type == BREAD &&
+		    cur->fts_info != FTS_DNR && cur->fts_info != FTS_ERR)
 			cur->fts_info = FTS_DP;
 		return (NULL);
 	}
diff --git a/lib/libc/gen/fts-compat11.c b/lib/libc/gen/fts-compat11.c
index 288351d2008b..a18ce3f00a6a 100644
--- a/lib/libc/gen/fts-compat11.c
+++ b/lib/libc/gen/fts-compat11.c
@@ -607,6 +607,19 @@ freebsd11_fts_set_clientptr(FTS11 *sp, void *clientptr)
 	sp->fts_clientptr = clientptr;
 }
 
+static struct freebsd11_dirent *
+fts_safe_readdir(DIR *dirp, int *readdir_errno)
+{
+	struct freebsd11_dirent *ret;
+
+	errno = 0;
+	if (!dirp)
+		return (NULL);
+	ret = freebsd11_readdir(dirp);
+	*readdir_errno = errno;
+	return (ret);
+}
+
 /*
  * This is the tricky part -- do not casually change *anything* in here.  The
  * idea is to build the linked list of entries that are used by fts_children
@@ -630,7 +643,8 @@ fts_build(FTS11 *sp, int type)
 	DIR *dirp;
 	void *oldaddr;
 	char *cp;
-	int cderrno, descend, oflag, saved_errno, nostat, doadjust;
+	int cderrno, descend, oflag, saved_errno, nostat, doadjust,
+	    readdir_errno;
 	long level;
 	long nlinks;	/* has to be signed because -1 is a magic value */
 	size_t dnamlen, len, maxlen, nitems;
@@ -736,8 +750,9 @@ fts_build(FTS11 *sp, int type)
 
 	/* Read the directory, attaching each entry to the `link' pointer. */
 	doadjust = 0;
+	readdir_errno = 0;
 	for (head = tail = NULL, nitems = 0;
-	    dirp && (dp = freebsd11_readdir(dirp));) {
+	    (dp = fts_safe_readdir(dirp, &readdir_errno));) {
 		dnamlen = dp->d_namlen;
 		if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
 			continue;
@@ -823,6 +838,16 @@ mem1:				saved_errno = errno;
 		}
 		++nitems;
 	}
+
+	if (readdir_errno) {
+		cur->fts_errno = readdir_errno;
+		/*
+		 * If we've not read any items yet, treat
+		 * the error as if we can't access the dir.
+		 */
+		cur->fts_info = nitems ? FTS_ERR : FTS_DNR;
+	}
+
 	if (dirp)
 		(void)closedir(dirp);
 
@@ -859,7 +884,8 @@ mem1:				saved_errno = errno;
 
 	/* If didn't find anything, return NULL. */
 	if (!nitems) {
-		if (type == BREAD)
+		if (type == BREAD &&
+		    cur->fts_info != FTS_DNR && cur->fts_info != FTS_ERR)
 			cur->fts_info = FTS_DP;
 		return (NULL);
 	}
diff --git a/lib/libc/gen/fts.c b/lib/libc/gen/fts.c
index d0705e123775..5186ae047a3b 100644
--- a/lib/libc/gen/fts.c
+++ b/lib/libc/gen/fts.c
@@ -604,6 +604,19 @@ fts_set_clientptr(FTS *sp, void *clientptr)
 	sp->fts_clientptr = clientptr;
 }
 
+static struct dirent *
+fts_safe_readdir(DIR *dirp, int *readdir_errno)
+{
+	struct dirent *ret;
+
+	errno = 0;
+	if (!dirp)
+		return (NULL);
+	ret = readdir(dirp);
+	*readdir_errno = errno;
+	return (ret);
+}
+
 /*
  * This is the tricky part -- do not casually change *anything* in here.  The
  * idea is to build the linked list of entries that are used by fts_children
@@ -627,7 +640,8 @@ fts_build(FTS *sp, int type)
 	DIR *dirp;
 	void *oldaddr;
 	char *cp;
-	int cderrno, descend, oflag, saved_errno, nostat, doadjust;
+	int cderrno, descend, oflag, saved_errno, nostat, doadjust,
+	    readdir_errno;
 	long level;
 	long nlinks;	/* has to be signed because -1 is a magic value */
 	size_t dnamlen, len, maxlen, nitems;
@@ -733,7 +747,9 @@ fts_build(FTS *sp, int type)
 
 	/* Read the directory, attaching each entry to the `link' pointer. */
 	doadjust = 0;
-	for (head = tail = NULL, nitems = 0; dirp && (dp = readdir(dirp));) {
+	readdir_errno = 0;
+	for (head = tail = NULL, nitems = 0;
+	    (dp = fts_safe_readdir(dirp, &readdir_errno));) {
 		dnamlen = dp->d_namlen;
 		if (!ISSET(FTS_SEEDOT) && ISDOT(dp->d_name))
 			continue;
@@ -819,6 +835,16 @@ mem1:				saved_errno = errno;
 		}
 		++nitems;
 	}
+
+	if (readdir_errno) {
+		cur->fts_errno = readdir_errno;
+		/*
+		 * If we've not read any items yet, treat
+		 * the error as if we can't access the dir.
+		 */
+		cur->fts_info = nitems ? FTS_ERR : FTS_DNR;
+	}
+
 	if (dirp)
 		(void)closedir(dirp);
 
@@ -855,7 +881,8 @@ mem1:				saved_errno = errno;
 
 	/* If didn't find anything, return NULL. */
 	if (!nitems) {
-		if (type == BREAD)
+		if (type == BREAD &&
+		    cur->fts_info != FTS_DNR && cur->fts_info != FTS_ERR)
 			cur->fts_info = FTS_DP;
 		return (NULL);
 	}