git: b165e9e3ea4e - main - Add fchroot(2)

From: Edward Tomasz Napierala <trasz_at_FreeBSD.org>
Date: Fri, 29 Nov 2024 12:22:16 UTC
The branch main has been updated by trasz:

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

commit b165e9e3ea4e327fc421d81c2a89242bd8720780
Author:     Edward Tomasz Napierala <trasz@FreeBSD.org>
AuthorDate: 2024-11-29 07:46:07 +0000
Commit:     Edward Tomasz Napierala <trasz@FreeBSD.org>
CommitDate: 2024-11-29 12:10:02 +0000

    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 48155bb2971b..8574b2ba9915 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 4be64a98bb96..04e767d50f86 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 85373b1f9cda..3e2f14497b07 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 af187bf30b2c..4c06e3673e03 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 3e5e18fc65d8..0c24f6b45f88 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 1f3a181a91cb..a97c16d6d7df 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 2bbd20b5a5b0..e7f577d48426 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 ab0e562e73aa..7a1677c945e3 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 32ae05172e24..62711545114d 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 5c6813d2a450..9b50986ede0a 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 359c2d13c86d..bd9bbfc358c9 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" },