diff options
| author | Mark Johnston <markj@FreeBSD.org> | 2026-07-24 20:06:05 +0000 |
|---|---|---|
| committer | Mark Johnston <markj@FreeBSD.org> | 2026-09-28 18:17:20 +0000 |
| commit | 9d0644e7a4f526f12003d776c2224baaa10c1ade (patch) | |
| tree | 3915b79aaf03c8b735fa158ea34978504ab0ae96 | |
| parent | 1c1575015cc09b3021a72af1d86f9549a546c246 (diff) | |
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().
Approved by: so
Security: FreeBSD-SA-26:66.jail
Security: CVE-2026-101306
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)
(cherry picked from commit 441885dcfff33d6ca797d54b6bda9dc5fd8eda89)
| -rw-r--r-- | sys/kern/uipc_usrreq.c | 27 | ||||
| -rw-r--r-- | 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 4df36221bc6a..05381ca08af9 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -2433,15 +2433,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 @@ -2506,9 +2516,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); } @@ -2744,6 +2754,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()); } |
