Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 24 Jul 2026 21:07:24 +0000
From:      Mark Johnston <markj@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 586e2b3d89d6 - main - unix: Preserve FD_RESOLVE_BENEATH when passing an fd
Message-ID:  <6a63d40c.37454.2052fd97@gitrepo.freebsd.org>

index | next in thread | raw e-mail

The branch main has been updated by markj:

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

commit 586e2b3d89d6e70ab7e4a88497b5f36d78719423
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-07-24 20:06:05 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-07-24 20:06:05 +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
---
 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 0b4e49c7db55..a2069ca18c59 100644
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -3504,15 +3504,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
@@ -3589,9 +3599,9 @@ unp_externalize(const struct socket *so, struct mbuf *control,
 				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);
 			}
 
@@ -3829,6 +3839,7 @@ unp_internalize(struct mbuf *control, struct mchain *mc, 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 c600b0937b0d..665fce767eb2 100644
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -1344,6 +1344,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_TC_WITHOUT_HEAD(listening_socket);
 ATF_TC_BODY(listening_socket, tc)
 {
@@ -1397,6 +1427,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, empty_rights_message);
 	ATF_TP_ADD_TC(tp, control_creates_records);
 	ATF_TP_ADD_TC(tp, cross_jail_dirfd);
+	ATF_TP_ADD_TC(tp, resolve_beneath_preserved);
 	ATF_TP_ADD_TC(tp, listening_socket);
 
 	return (atf_no_error());


home | help

Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a63d40c.37454.2052fd97>