git: 30758138f2e3 - stable/15 - unix: Preserve FD_RESOLVE_BENEATH when passing an fd
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 06 Aug 2026 15:01:31 UTC
The branch stable/15 has been updated by markj:
URL: https://cgit.FreeBSD.org/src/commit/?id=30758138f2e36d89bc30cb686796ccc4dfa8b475
commit 30758138f2e36d89bc30cb686796ccc4dfa8b475
Author: Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-07-24 20:06:05 +0000
Commit: Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-08-06 12:52:28 +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 bbea5ef3914c..ed3e22ad63f6 100644
--- a/sys/kern/uipc_usrreq.c
+++ b/sys/kern/uipc_usrreq.c
@@ -3488,15 +3488,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
@@ -3562,9 +3572,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);
}
@@ -3800,6 +3810,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 66bb406ea14e..566591522784 100644
--- a/tests/sys/kern/unix_passfd_test.c
+++ b/tests/sys/kern/unix_passfd_test.c
@@ -1189,6 +1189,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)
{
@@ -1239,6 +1269,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());