git: 441885dcfff3 - stable/14 - unix: Preserve FD_RESOLVE_BENEATH when passing an fd

Mark Johnston <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a7353be.46027.660ae3ec__43714.9719496787$1785942996$gmane$org@gitrepo.freebsd.org>
The branch stable/14 has been updated by markj:

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

commit 441885dcfff33d6ca797d54b6bda9dc5fd8eda89
Author:     Mark Johnston <[email protected]>
AuthorDate: 2026-07-24 20:06:05 +0000
Commit:     Mark Johnston <[email protected]>
CommitDate: 2026-08-05 15:15:14 +0000

    unix: Preserve FD_RESOLVE_BENEATH when passing an fd
    
    The FD_RESOLVE_BENEATH flag is supposed to be sticky.  It's set when you
    receive an fd from a different jail and preserved by openat(<dfd>) etc..
    However, if you send the fd to yourself, the flag is stripped since
    SCM_RIGHTS message don't preserve file descriptor flags.
    
    Fix this by preserving those flags and checking for UF_RESOLVE_BENEATH
    in restrict_rights().
    
    Fixes:          350ba9672a7f ("unix: Set O_RESOLVE_BENEATH on fds transferred between jails")
    Reviewed by:    kib
    MFC after:      1 week
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D58317
    
    (cherry picked from commit 586e2b3d89d6e70ab7e4a88497b5f36d78719423)
---
 sys/kern/uipc_usrreq.c            | 27 +++++++++++++++++++--------
 tests/sys/kern/unix_passfd_test.c | 31 +++++++++++++++++++++++++++++++
 2 files changed, 50 insertions(+), 8 deletions(-)

diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c
index c78b63005ca5..84e48d9b8274 100644
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -2452,15 +2452,25 @@ unp_freerights(struct filedescent **fdep, int fdcount)
 	free(fdep[0], M_FILECAPS);
 }
 
-static bool
-restrict_rights(struct file *fp, struct thread *td)
+/*
+ * Flags to set on the receiving side when externalizing a file descriptor.
+ * When transferring fds between jails, ensure that the receiver cannot use
+ * a dirfd to escape the jail chroot.
+ */
+static int
+externalize_fdflags(struct filedescent *fde, struct thread *td)
 {
 	struct prison *prison1, *prison2;
 
-	prison1 = fp->f_cred->cr_prison;
+	if ((fde->fde_flags & UF_RESOLVE_BENEATH) != 0)
+		return (O_RESOLVE_BENEATH);
+	prison1 = fde->fde_file->f_cred->cr_prison;
 	prison2 = td->td_ucred->cr_prison;
-	return (prison1 != prison2 && prison1->pr_root != prison2->pr_root &&
-	    prison2 != &prison0);
+	if (prison1 != prison2 && prison1->pr_root != prison2->pr_root &&
+	    prison2 != &prison0)
+		return (O_RESOLVE_BENEATH);
+	else
+		return (0);
 }
 
 static int
@@ -2525,9 +2535,9 @@ unp_externalize(struct mbuf *control, struct mbuf **controlp, int flags)
 				struct file *fp;
 
 				fp = fdep[i]->fde_file;
-				_finstall(fdesc, fp, *fdp, fdflags |
-				    (restrict_rights(fp, td) ?
-				    O_RESOLVE_BENEATH : 0), &fdep[i]->fde_caps);
+				_finstall(fdesc, fp, *fdp,
+				    fdflags | externalize_fdflags(fdep[i], td),
+				    &fdep[i]->fde_caps);
 				unp_externalize_fp(fp);
 			}
 
@@ -2763,6 +2773,7 @@ unp_internalize(struct mbuf **controlp, struct thread *td,
 				fdep[i]->fde_file = fde->fde_file;
 				filecaps_copy(&fde->fde_caps,
 				    &fdep[i]->fde_caps, true);
+				fdep[i]->fde_flags = fde->fde_flags;
 				unp_internalize_fp(fdep[i]->fde_file);
 			}
 			FILEDESC_SUNLOCK(fdesc);
diff --git a/tests/sys/kern/unix_passfd_test.c b/tests/sys/kern/unix_passfd_test.c
index dd68020e1bc4..9a5a3da71c96 100644
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -1076,6 +1076,36 @@ ATF_TC_CLEANUP(cross_jail_dirfd, tc)
 		err(1, "jail_remove");
 }
 
+/*
+ * Verify that FD_RESOLVE_BENEATH is preserved when an fd is passed over a UNIX
+ * domain socket.
+ */
+ATF_TC_WITHOUT_HEAD(resolve_beneath_preserved);
+ATF_TC_BODY(resolve_beneath_preserved, tc)
+{
+	int fd[2], getfd, putfd, fdflags;
+
+	domainsocketpair(fd);
+	tempfile(&putfd);
+
+	fdflags = fcntl(putfd, F_GETFD);
+	ATF_REQUIRE(fdflags != -1);
+	ATF_REQUIRE(fcntl(putfd, F_SETFD, fdflags | FD_RESOLVE_BENEATH) != -1);
+	ATF_REQUIRE((fcntl(putfd, F_GETFD) & FD_RESOLVE_BENEATH) != 0);
+
+	sendfd(fd[0], putfd);
+	recvfd(fd[1], &getfd, 0);
+
+	fdflags = fcntl(getfd, F_GETFD);
+	ATF_REQUIRE(fdflags != -1);
+	ATF_REQUIRE_MSG((fdflags & FD_RESOLVE_BENEATH) != 0,
+	    "FD_RESOLVE_BENEATH was not preserved across SCM_RIGHTS transfer");
+
+	ATF_REQUIRE(close(putfd) == 0);
+	ATF_REQUIRE(close(getfd) == 0);
+	closesocketpair(fd);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 
@@ -1095,6 +1125,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, copyout_rights_error);
 	ATF_TP_ADD_TC(tp, empty_rights_message);
 	ATF_TP_ADD_TC(tp, cross_jail_dirfd);
+	ATF_TP_ADD_TC(tp, resolve_beneath_preserved);
 
 	return (atf_no_error());
 }
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.