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
This commit is contained in:
Edward Tomasz Napierala 2024-11-29 07:46:07 +00:00
parent 347dd0539f
commit b165e9e3ea
11 changed files with 123 additions and 31 deletions

View file

@ -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);

View file

@ -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

View file

@ -378,6 +378,7 @@ FBSD_1.7 {
};
FBSD_1.8 {
fchroot;
getrlimitusage;
kcmp;
};

View file

@ -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

View file

@ -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 .

View file

@ -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);

View file

@ -3341,5 +3341,10 @@
_Out_ rlim_t *res
);
}
590 AUE_NULL STD {
int fchroot(
int fd
);
}
; vim: syntax=off

View file

@ -967,6 +967,45 @@ static int unprivileged_chroot = 0;
SYSCTL_INT(_security_bsd, OID_AUTO, unprivileged_chroot, CTLFLAG_RW,
&unprivileged_chroot, 0,
"Unprivileged processes can use chroot(2)");
/*
* Takes locked vnode, unlocks it before returning.
*/
static int
kern_chroot(struct thread *td, struct vnode *vp)
{
struct proc *p;
int error;
error = priv_check(td, PRIV_VFS_CHROOT);
if (error != 0) {
p = td->td_proc;
PROC_LOCK(p);
if (unprivileged_chroot == 0 ||
(p->p_flag2 & P2_NO_NEW_PRIVS) == 0) {
PROC_UNLOCK(p);
goto e_vunlock;
}
PROC_UNLOCK(p);
}
error = change_dir(vp, td);
if (error != 0)
goto e_vunlock;
#ifdef MAC
error = mac_vnode_check_chroot(td->td_ucred, vp);
if (error != 0)
goto e_vunlock;
#endif
VOP_UNLOCK(vp);
error = pwd_chroot(td, vp);
vrele(vp);
return (error);
e_vunlock:
vput(vp);
return (error);
}
/*
* Change notion of root (``/'') directory.
*/
@ -979,40 +1018,41 @@ int
sys_chroot(struct thread *td, struct chroot_args *uap)
{
struct nameidata nd;
struct proc *p;
int error;
error = priv_check(td, PRIV_VFS_CHROOT);
if (error != 0) {
p = td->td_proc;
PROC_LOCK(p);
if (unprivileged_chroot == 0 ||
(p->p_flag2 & P2_NO_NEW_PRIVS) == 0) {
PROC_UNLOCK(p);
return (error);
}
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);
if (error != 0)
goto e_vunlock;
#ifdef MAC
error = mac_vnode_check_chroot(td->td_ucred, nd.ni_vp);
if (error != 0)
goto e_vunlock;
#endif
VOP_UNLOCK(nd.ni_vp);
error = pwd_chroot(td, nd.ni_vp);
vrele(nd.ni_vp);
error = kern_chroot(td, nd.ni_vp);
return (error);
e_vunlock:
vput(nd.ni_vp);
}
/*
* 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);
}

View file

@ -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;

View file

@ -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 */

View file

@ -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" },