From b165e9e3ea4e327fc421d81c2a89242bd8720780 Mon Sep 17 00:00:00 2001 From: Edward Tomasz Napierala Date: Fri, 29 Nov 2024 07:46:07 +0000 Subject: [PATCH] Add fchroot(2) This is similar to chroot(2), but takes a file descriptor instead of path. Same syscall exists in NetBSD and Solaris. It is part of a larger patch to make absolute pathnames usable in Capsicum mode, but should be useful in other contexts too. Reviewed By: brooks Sponsored by: Innovate UK Differential Revision: https://reviews.freebsd.org/D41564 --- include/unistd.h | 1 + lib/libsys/Makefile.sys | 1 + lib/libsys/Symbol.sys.map | 1 + lib/libsys/chroot.2 | 40 ++++++++++++++- share/man/man4/rights.4 | 5 +- sys/kern/subr_capability.c | 2 + sys/kern/syscalls.master | 5 ++ sys/kern/vfs_syscalls.c | 84 +++++++++++++++++++++++-------- sys/sys/caprights.h | 1 + sys/sys/capsicum.h | 7 +-- usr.bin/procstat/procstat_files.c | 1 + 11 files changed, 120 insertions(+), 28 deletions(-) diff --git a/include/unistd.h b/include/unistd.h index 48155bb2971bdf..8574b2ba991537 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -507,6 +507,7 @@ int exect(const char *, char * const *, char * const *); int execvP(const char *, const char *, char * const *); int execvpe(const char *, char * const *, char * const *); int feature_present(const char *); +int fchroot(int); char *fflagstostr(u_long); int getdomainname(char *, int); int getentropy(void *, size_t); diff --git a/lib/libsys/Makefile.sys b/lib/libsys/Makefile.sys index 4be64a98bb9669..04e767d50f86da 100644 --- a/lib/libsys/Makefile.sys +++ b/lib/libsys/Makefile.sys @@ -399,6 +399,7 @@ MLINKS+=chmod.2 fchmod.2 \ MLINKS+=chown.2 fchown.2 \ chown.2 fchownat.2 \ chown.2 lchown.2 +MLINKS+=chroot.2 fchroot.2 MLINKS+=clock_gettime.2 clock_getres.2 \ clock_gettime.2 clock_settime.2 MLINKS+=closefrom.2 close_range.2 diff --git a/lib/libsys/Symbol.sys.map b/lib/libsys/Symbol.sys.map index 85373b1f9cda13..3e2f14497b0779 100644 --- a/lib/libsys/Symbol.sys.map +++ b/lib/libsys/Symbol.sys.map @@ -378,6 +378,7 @@ FBSD_1.7 { }; FBSD_1.8 { + fchroot; getrlimitusage; kcmp; }; diff --git a/lib/libsys/chroot.2 b/lib/libsys/chroot.2 index af187bf30b2cd3..4c06e3673e0349 100644 --- a/lib/libsys/chroot.2 +++ b/lib/libsys/chroot.2 @@ -25,11 +25,12 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd September 29, 2020 +.Dd July 15, 2024 .Dt CHROOT 2 .Os .Sh NAME -.Nm chroot +.Nm chroot , +.Nm fchroot .Nd change root directory .Sh LIBRARY .Lb libc @@ -37,6 +38,8 @@ .In unistd.h .Ft int .Fn chroot "const char *dirname" +.Ft int +.Fn fchroot "int fd" .Sh DESCRIPTION The .Fa dirname @@ -92,6 +95,12 @@ will bypass the check for open directories, mimicking the historic insecure behavior of .Fn chroot still present on other systems. +.Pp +The +.Fn fchroot +system call is identical to +.Fn chroot +except it takes a file descriptor instead of path. .Sh RETURN VALUES .Rv -std .Sh ERRORS @@ -124,6 +133,29 @@ An I/O error occurred while reading from or writing to the file system. .It Bq Er EINTEGRITY Corrupted data was detected while reading from the file system. .El +.Pp +The +.Fn fchroot +system call +will fail and the root directory will be unchanged if: +.Bl -tag -width Er +.It Bq Er EACCES +Search permission is denied for the directory referenced by the +file descriptor. +.It Bq Er EBADF +The argument +.Fa fd +is not a valid file descriptor. +.It Bq Er EIO +An I/O error occurred while reading from or writing to the file system. +.It Bq Er EINTEGRITY +Corrupted data was detected while reading from the file system. +.It Bq Er ENOTDIR +The file descriptor does not reference a directory. +.It Bq Er EPERM +The effective user ID is not the super-user, or one or more +filedescriptors are open directories. +.El .Sh SEE ALSO .Xr chdir 2 , .Xr jail 2 @@ -137,6 +169,10 @@ It was marked as in .St -susv2 , and was removed in subsequent standards. +The +.Fn fchroot +system call first appeared in +.Fx 15.0 . .Sh BUGS If the process is able to change its working directory to the target directory, but another access control check fails (such as a check for diff --git a/share/man/man4/rights.4 b/share/man/man4/rights.4 index 3e5e18fc65d8ca..0c24f6b45f88e8 100644 --- a/share/man/man4/rights.4 +++ b/share/man/man4/rights.4 @@ -30,7 +30,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 27, 2024 +.Dd May 1, 2024 .Dt RIGHTS 4 .Os .Sh NAME @@ -209,6 +209,9 @@ An alias to .Dv CAP_FCHOWN and .Dv CAP_LOOKUP . +.It Dv CAP_FCHROOT +Permit +.Xr fchroot 2 . .It Dv CAP_FCNTL Permit .Xr fcntl 2 . diff --git a/sys/kern/subr_capability.c b/sys/kern/subr_capability.c index 1f3a181a91cb4e..a97c16d6d7dff0 100644 --- a/sys/kern/subr_capability.c +++ b/sys/kern/subr_capability.c @@ -59,6 +59,7 @@ __read_mostly cap_rights_t cap_fchdir_rights; __read_mostly cap_rights_t cap_fchflags_rights; __read_mostly cap_rights_t cap_fchmod_rights; __read_mostly cap_rights_t cap_fchown_rights; +__read_mostly cap_rights_t cap_fchroot_rights; __read_mostly cap_rights_t cap_fcntl_rights; __read_mostly cap_rights_t cap_fexecve_rights; __read_mostly cap_rights_t cap_flock_rights; @@ -108,6 +109,7 @@ cap_rights_sysinit(void *arg) cap_rights_init_one(&cap_fchflags_rights, CAP_FCHFLAGS); cap_rights_init_one(&cap_fchmod_rights, CAP_FCHMOD); cap_rights_init_one(&cap_fchown_rights, CAP_FCHOWN); + cap_rights_init_one(&cap_fchroot_rights, CAP_FCHROOT); cap_rights_init_one(&cap_fcntl_rights, CAP_FCNTL); cap_rights_init_one(&cap_fexecve_rights, CAP_FEXECVE); cap_rights_init_one(&cap_flock_rights, CAP_FLOCK); diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master index 2bbd20b5a5b0b9..e7f577d4842672 100644 --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -3341,5 +3341,10 @@ _Out_ rlim_t *res ); } +590 AUE_NULL STD { + int fchroot( + int fd + ); + } ; vim: syntax=off diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c index ab0e562e73aa6c..7a1677c945e316 100644 --- a/sys/kern/vfs_syscalls.c +++ b/sys/kern/vfs_syscalls.c @@ -967,18 +967,13 @@ static int unprivileged_chroot = 0; SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_chroot, CTLFLAG_RW, &unprivileged_chroot, 0, "Unprivileged processes can use chroot(2)"); + /* - * Change notion of root (``/'') directory. + * Takes locked vnode, unlocks it before returning. */ -#ifndef _SYS_SYSPROTO_H_ -struct chroot_args { - char *path; -}; -#endif -int -sys_chroot(struct thread *td, struct chroot_args *uap) +static int +kern_chroot(struct thread *td, struct vnode *vp) { - struct nameidata nd; struct proc *p; int error; @@ -989,30 +984,75 @@ sys_chroot(struct thread *td, struct chroot_args *uap) if (unprivileged_chroot == 0 || (p->p_flag2 & P2_NO_NEW_PRIVS) == 0) { PROC_UNLOCK(p); - return (error); + goto e_vunlock; } PROC_UNLOCK(p); } - NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | AUDITVNODE1, - UIO_USERSPACE, uap->path); - error = namei(&nd); - if (error != 0) - return (error); - NDFREE_PNBUF(&nd); - error = change_dir(nd.ni_vp, td); + + error = change_dir(vp, td); if (error != 0) goto e_vunlock; #ifdef MAC - error = mac_vnode_check_chroot(td->td_ucred, nd.ni_vp); + error = mac_vnode_check_chroot(td->td_ucred, vp); if (error != 0) goto e_vunlock; #endif - VOP_UNLOCK(nd.ni_vp); - error = pwd_chroot(td, nd.ni_vp); - vrele(nd.ni_vp); + VOP_UNLOCK(vp); + error = pwd_chroot(td, vp); + vrele(vp); return (error); e_vunlock: - vput(nd.ni_vp); + vput(vp); + return (error); +} + +/* + * Change notion of root (``/'') directory. + */ +#ifndef _SYS_SYSPROTO_H_ +struct chroot_args { + char *path; +}; +#endif +int +sys_chroot(struct thread *td, struct chroot_args *uap) +{ + struct nameidata nd; + int error; + + NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | AUDITVNODE1, + UIO_USERSPACE, uap->path); + error = namei(&nd); + if (error != 0) + return (error); + NDFREE_PNBUF(&nd); + error = kern_chroot(td, nd.ni_vp); + return (error); +} + +/* + * Change notion of root directory to a given file descriptor. + */ +#ifndef _SYS_SYSPROTO_H_ +struct fchroot_args { + int fd; +}; +#endif +int +sys_fchroot(struct thread *td, struct fchroot_args *uap) +{ + struct vnode *vp; + struct file *fp; + int error; + + error = getvnode_path(td, uap->fd, &cap_fchroot_rights, &fp); + if (error != 0) + return (error); + vp = fp->f_vnode; + vrefact(vp); + fdrop(fp, td); + vn_lock(vp, LK_SHARED | LK_RETRY); + error = kern_chroot(td, vp); return (error); } diff --git a/sys/sys/caprights.h b/sys/sys/caprights.h index 32ae05172e248c..62711545114d58 100644 --- a/sys/sys/caprights.h +++ b/sys/sys/caprights.h @@ -66,6 +66,7 @@ extern cap_rights_t cap_fchdir_rights; extern cap_rights_t cap_fchflags_rights; extern cap_rights_t cap_fchmod_rights; extern cap_rights_t cap_fchown_rights; +extern cap_rights_t cap_fchroot_rights; extern cap_rights_t cap_fcntl_rights; extern cap_rights_t cap_fexecve_rights; extern cap_rights_t cap_flock_rights; diff --git a/sys/sys/capsicum.h b/sys/sys/capsicum.h index 5c6813d2a450b0..9b50986ede0a7d 100644 --- a/sys/sys/capsicum.h +++ b/sys/sys/capsicum.h @@ -201,6 +201,9 @@ /* Allows for renameat(2) (target directory descriptor). */ #define CAP_RENAMEAT_TARGET (CAP_LOOKUP | 0x0000040000000000ULL) +/* Allows for fchroot(2). */ +#define CAP_FCHROOT CAPRIGHT(0, 0x0000080000000000ULL) + #define CAP_SOCK_CLIENT \ (CAP_CONNECT | CAP_GETPEERNAME | CAP_GETSOCKNAME | CAP_GETSOCKOPT | \ CAP_PEELOFF | CAP_RECV | CAP_SEND | CAP_SETSOCKOPT | CAP_SHUTDOWN) @@ -210,11 +213,9 @@ CAP_SETSOCKOPT | CAP_SHUTDOWN) /* All used bits for index 0. */ -#define CAP_ALL0 CAPRIGHT(0, 0x000007FFFFFFFFFFULL) +#define CAP_ALL0 CAPRIGHT(0, 0x00000FFFFFFFFFFFULL) /* Available bits for index 0. */ -#define CAP_UNUSED0_44 CAPRIGHT(0, 0x0000080000000000ULL) -/* ... */ #define CAP_UNUSED0_57 CAPRIGHT(0, 0x0100000000000000ULL) /* INDEX 1 */ diff --git a/usr.bin/procstat/procstat_files.c b/usr.bin/procstat/procstat_files.c index 359c2d13c86d52..bd9bbfc358c963 100644 --- a/usr.bin/procstat/procstat_files.c +++ b/usr.bin/procstat/procstat_files.c @@ -149,6 +149,7 @@ static struct cap_desc { { CAP_FCHFLAGS, "cf" }, { CAP_FCHMOD, "cm" }, { CAP_FCHOWN, "cn" }, + { CAP_FCHROOT, "ct" }, { CAP_FCNTL, "fc" }, { CAP_FLOCK, "fl" }, { CAP_FPATHCONF, "fp" },