git: 110f2567cb51 - main - linux_file.c: Fix handling of NFS getdents() emulation

From: Rick Macklem <rmacklem_at_FreeBSD.org>
Date: Sat, 07 Feb 2026 22:15:09 UTC
The branch main has been updated by rmacklem:

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

commit 110f2567cb51f1eeddbd5d9937000ad64f6dc746
Author:     Rick Macklem <rmacklem@FreeBSD.org>
AuthorDate: 2026-02-07 22:12:55 +0000
Commit:     Rick Macklem <rmacklem@FreeBSD.org>
CommitDate: 2026-02-07 22:12:55 +0000

    linux_file.c: Fix handling of NFS getdents() emulation
    
    Bugzilla PR#292282 reports a problem, where a Linux
    binary running in the Linuxulator gets bogus entries
    in a readdir()/getdents() reply when the directory is
    an NFS mount.
    
    This appears to be caused by the NFS client including
    entries with d_fileno == 0, which are always ignored by
    BSD, but are not ignored by Linux.
    
    This patch filters out the "d_fileno == 0" entries and
    the reporter of the bugzilla PR notes that it fixes the
    problem for him.
    
    It could be argued that the NFS client should filter out
    the "d_fileno == 0" entries, but the NFS client readdir
    code is "fragile" and any change to it runs a significant
    risk of causing regression type problems.
    
    As such, since the LInuxulator is already broken for this
    case, it seems safer to filter them out there.
    
    PR:     292282
    Tested by:      Jerry Williams <jwillia3@proton.me>
    Reviewed by:    markj
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D54679
---
 sys/compat/linux/linux_file.c | 178 ++++++++++++++++++++++++++----------------
 1 file changed, 110 insertions(+), 68 deletions(-)

diff --git a/sys/compat/linux/linux_file.c b/sys/compat/linux/linux_file.c
index daddafa325ad..96326b894d05 100644
--- a/sys/compat/linux/linux_file.c
+++ b/sys/compat/linux/linux_file.c
@@ -398,12 +398,50 @@ struct l_dirent64 {
     roundup(offsetof(struct l_dirent64, d_name) + (namlen) + 1,		\
     sizeof(uint64_t))
 
+/*
+ * Do kern_getdirentries() and then skip over any invalid entries.
+ * (Repeat, if there are no valid entries.)
+ * Adjust bufp and lenp.
+ */
+static int
+linux_getdirentries(struct thread *td, int fd, caddr_t *bufp, int buflen,
+    off_t *basep, int *lenp)
+{
+	struct dirent *bdp;
+	caddr_t buf;
+	int error, len;
+
+	/* Loop around until a valid entry is found or at EOF. */
+	for (;;) {
+		error = kern_getdirentries(td, fd, *bufp, buflen,
+		    basep, NULL, UIO_SYSSPACE);
+		if (error != 0)
+			return (error);
+		len = td->td_retval[0];
+		if (len == 0) {
+			*lenp = 0;
+			return (0);
+		}
+		buf = *bufp;
+		while (len > 0) {
+			bdp = (struct dirent *)buf;
+			if (bdp->d_fileno != 0) {
+				*bufp = buf;
+				*lenp = len;
+				return (0);
+			}
+			buf += bdp->d_reclen;
+			len -= bdp->d_reclen;
+		}
+	}
+}
+
 #ifdef LINUX_LEGACY_SYSCALLS
 int
 linux_getdents(struct thread *td, struct linux_getdents_args *args)
 {
 	struct dirent *bdp;
-	caddr_t inp, buf;		/* BSD-format */
+	caddr_t inp, buf, bufsav;	/* BSD-format */
 	int len, reclen;		/* BSD-format */
 	caddr_t outp;			/* Linux-format */
 	int resid, linuxreclen;		/* Linux-format */
@@ -413,11 +451,11 @@ linux_getdents(struct thread *td, struct linux_getdents_args *args)
 	int buflen, error;
 	size_t retval;
 
-	buflen = min(args->count, MAXBSIZE);
-	buf = malloc(buflen, M_LINUX, M_WAITOK);
+	buflen = min(roundup2(args->count, DEV_BSIZE), MAXBSIZE);
+	bufsav = buf = malloc(buflen, M_LINUX, M_WAITOK);
 
-	error = kern_getdirentries(td, args->fd, buf, buflen,
-	    &base, NULL, UIO_SYSSPACE);
+	error = linux_getdirentries(td, args->fd, &buf, buflen,
+	    &base, &len);
 	if (error != 0) {
 		error = linux_getdents_error(td, args->fd, error);
 		goto out1;
@@ -425,7 +463,6 @@ linux_getdents(struct thread *td, struct linux_getdents_args *args)
 
 	lbuf = malloc(LINUX_RECLEN(LINUX_NAME_MAX), M_LINUX, M_WAITOK | M_ZERO);
 
-	len = td->td_retval[0];
 	inp = buf;
 	outp = (caddr_t)args->dent;
 	resid = args->count;
@@ -434,44 +471,47 @@ linux_getdents(struct thread *td, struct linux_getdents_args *args)
 	while (len > 0) {
 		bdp = (struct dirent *) inp;
 		reclen = bdp->d_reclen;
-		linuxreclen = LINUX_RECLEN(bdp->d_namlen);
-		/*
-		 * No more space in the user supplied dirent buffer.
-		 * Return EINVAL.
-		 */
-		if (resid < linuxreclen) {
-			error = EINVAL;
-			goto out;
+		/* Copy a valid entry out. */
+		if (bdp->d_fileno != 0) {
+			linuxreclen = LINUX_RECLEN(bdp->d_namlen);
+			/*
+			 * No more space in the user supplied dirent buffer.
+			 * Return EINVAL.
+			 */
+			if (resid < linuxreclen) {
+				error = EINVAL;
+				goto out;
+			}
+
+			linux_dirent = (struct l_dirent*)lbuf;
+			linux_dirent->d_ino = bdp->d_fileno;
+			linux_dirent->d_off = bdp->d_off;
+			linux_dirent->d_reclen = linuxreclen;
+			/*
+			 * Copy d_type to last byte of l_dirent buffer
+			 */
+			lbuf[linuxreclen - 1] = bdp->d_type;
+			strlcpy(linux_dirent->d_name, bdp->d_name,
+			    linuxreclen - offsetof(struct l_dirent, d_name)-1);
+			error = copyout(linux_dirent, outp, linuxreclen);
+			if (error != 0)
+				goto out;
+			retval += linuxreclen;
+			outp += linuxreclen;
+			resid -= linuxreclen;
 		}
 
-		linux_dirent = (struct l_dirent*)lbuf;
-		linux_dirent->d_ino = bdp->d_fileno;
-		linux_dirent->d_off = bdp->d_off;
-		linux_dirent->d_reclen = linuxreclen;
-		/*
-		 * Copy d_type to last byte of l_dirent buffer
-		 */
-		lbuf[linuxreclen - 1] = bdp->d_type;
-		strlcpy(linux_dirent->d_name, bdp->d_name,
-		    linuxreclen - offsetof(struct l_dirent, d_name)-1);
-		error = copyout(linux_dirent, outp, linuxreclen);
-		if (error != 0)
-			goto out;
-
 		inp += reclen;
 		base += reclen;
 		len -= reclen;
 
-		retval += linuxreclen;
-		outp += linuxreclen;
-		resid -= linuxreclen;
 	}
 	td->td_retval[0] = retval;
 
 out:
 	free(lbuf, M_LINUX);
 out1:
-	free(buf, M_LINUX);
+	free(bufsav, M_LINUX);
 	return (error);
 }
 #endif
@@ -480,7 +520,7 @@ int
 linux_getdents64(struct thread *td, struct linux_getdents64_args *args)
 {
 	struct dirent *bdp;
-	caddr_t inp, buf;		/* BSD-format */
+	caddr_t inp, buf, bufsav;	/* BSD-format */
 	int len, reclen;		/* BSD-format */
 	caddr_t outp;			/* Linux-format */
 	int resid, linuxreclen;		/* Linux-format */
@@ -489,11 +529,11 @@ linux_getdents64(struct thread *td, struct linux_getdents64_args *args)
 	int buflen, error;
 	size_t retval;
 
-	buflen = min(args->count, MAXBSIZE);
-	buf = malloc(buflen, M_LINUX, M_WAITOK);
+	buflen = min(roundup2(args->count, DEV_BSIZE), MAXBSIZE);
+	bufsav = buf = malloc(buflen, M_LINUX, M_WAITOK);
 
-	error = kern_getdirentries(td, args->fd, buf, buflen,
-	    &base, NULL, UIO_SYSSPACE);
+	error = linux_getdirentries(td, args->fd, &buf, buflen,
+	    &base, &len);
 	if (error != 0) {
 		error = linux_getdents_error(td, args->fd, error);
 		goto out1;
@@ -502,7 +542,6 @@ linux_getdents64(struct thread *td, struct linux_getdents64_args *args)
 	linux_dirent64 = malloc(LINUX_RECLEN64(LINUX_NAME_MAX), M_LINUX,
 	    M_WAITOK | M_ZERO);
 
-	len = td->td_retval[0];
 	inp = buf;
 	outp = (caddr_t)args->dirent;
 	resid = args->count;
@@ -511,40 +550,43 @@ linux_getdents64(struct thread *td, struct linux_getdents64_args *args)
 	while (len > 0) {
 		bdp = (struct dirent *) inp;
 		reclen = bdp->d_reclen;
-		linuxreclen = LINUX_RECLEN64(bdp->d_namlen);
-		/*
-		 * No more space in the user supplied dirent buffer.
-		 * Return EINVAL.
-		 */
-		if (resid < linuxreclen) {
-			error = EINVAL;
-			goto out;
+		/* Copy a valid entry out. */
+		if (bdp->d_fileno != 0) {
+			linuxreclen = LINUX_RECLEN64(bdp->d_namlen);
+			/*
+			 * No more space in the user supplied dirent buffer.
+			 * Return EINVAL.
+			 */
+			if (resid < linuxreclen) {
+				error = EINVAL;
+				goto out;
+			}
+
+			linux_dirent64->d_ino = bdp->d_fileno;
+			linux_dirent64->d_off = bdp->d_off;
+			linux_dirent64->d_reclen = linuxreclen;
+			linux_dirent64->d_type = bdp->d_type;
+			strlcpy(linux_dirent64->d_name, bdp->d_name,
+			    linuxreclen - offsetof(struct l_dirent64, d_name));
+			error = copyout(linux_dirent64, outp, linuxreclen);
+			if (error != 0)
+				goto out;
+			retval += linuxreclen;
+			outp += linuxreclen;
+			resid -= linuxreclen;
 		}
 
-		linux_dirent64->d_ino = bdp->d_fileno;
-		linux_dirent64->d_off = bdp->d_off;
-		linux_dirent64->d_reclen = linuxreclen;
-		linux_dirent64->d_type = bdp->d_type;
-		strlcpy(linux_dirent64->d_name, bdp->d_name,
-		    linuxreclen - offsetof(struct l_dirent64, d_name));
-		error = copyout(linux_dirent64, outp, linuxreclen);
-		if (error != 0)
-			goto out;
-
 		inp += reclen;
 		base += reclen;
 		len -= reclen;
 
-		retval += linuxreclen;
-		outp += linuxreclen;
-		resid -= linuxreclen;
 	}
 	td->td_retval[0] = retval;
 
 out:
 	free(linux_dirent64, M_LINUX);
 out1:
-	free(buf, M_LINUX);
+	free(bufsav, M_LINUX);
 	return (error);
 }
 
@@ -553,22 +595,22 @@ int
 linux_readdir(struct thread *td, struct linux_readdir_args *args)
 {
 	struct dirent *bdp;
-	caddr_t buf;			/* BSD-format */
+	caddr_t buf, bufsav;		/* BSD-format */
 	int linuxreclen;		/* Linux-format */
 	off_t base;
 	struct l_dirent *linux_dirent;	/* Linux-format */
-	int buflen, error;
+	int buflen, error, len;
 
-	buflen = sizeof(*bdp);
-	buf = malloc(buflen, M_LINUX, M_WAITOK);
+	buflen = DEV_BSIZE;
+	bufsav = buf = malloc(buflen, M_LINUX, M_WAITOK);
 
-	error = kern_getdirentries(td, args->fd, buf, buflen,
-	    &base, NULL, UIO_SYSSPACE);
+	error = linux_getdirentries(td, args->fd, &buf, buflen,
+	    &base, &len);
 	if (error != 0) {
 		error = linux_getdents_error(td, args->fd, error);
 		goto out;
 	}
-	if (td->td_retval[0] == 0)
+	if (len == 0)
 		goto out;
 
 	linux_dirent = malloc(LINUX_RECLEN(LINUX_NAME_MAX), M_LINUX,
@@ -588,7 +630,7 @@ linux_readdir(struct thread *td, struct linux_readdir_args *args)
 
 	free(linux_dirent, M_LINUX);
 out:
-	free(buf, M_LINUX);
+	free(bufsav, M_LINUX);
 	return (error);
 }
 #endif /* __i386__ || (__amd64__ && COMPAT_LINUX32) */