Index: lib/libc/sys/chroot.2 =================================================================== --- lib/libc/sys/chroot.2 (revision 267243) +++ lib/libc/sys/chroot.2 (working copy) @@ -61,7 +61,17 @@ .Fn chroot has no effect on the process's current directory. .Pp -This call is restricted to the super-user. +By default, this call is restricted to the super-user. If +.Ql kern.chroot_allow_unprivileged +is set to a non-zero value, all users are capable of performing the +.Fn chroot +call. When called by an unprivileged user, the process and its children +won't honor the setuid and setgid bits when performing an +.Xr execve 2 . +To prevent escape from a chroot established by a privileged process, +unprivileged processes also cannot execute +.Fn chroot +under any circumstances when already under a modified root. .Pp Depending on the setting of the .Ql kern.chroot_allow_open_directories @@ -141,6 +151,11 @@ open directories, or a MAC check), it is possible that this system call may return an error, with the working directory of the process left changed. +.Pp +When a call to +.Fn chroot +fails when invoked by an unprivileged user, the process is not properly +capable of executing setuid or setgid applications anymore. .Sh SECURITY CONSIDERATIONS The system have many hardcoded paths to files where it may load after the process starts. Index: sys/compat/svr4/svr4_misc.c =================================================================== --- sys/compat/svr4/svr4_misc.c (revision 267243) +++ sys/compat/svr4/svr4_misc.c (working copy) @@ -631,7 +631,7 @@ goto fail; #endif VOP_UNLOCK(vp, 0); - error = change_root(vp, td); + error = change_root(vp, td, 0); vrele(vp); return (error); fail: Index: sys/kern/kern_exec.c =================================================================== --- sys/kern/kern_exec.c (revision 267243) +++ sys/kern/kern_exec.c (working copy) @@ -695,7 +695,7 @@ ((oldcred->cr_flags & CRED_FLAG_CAPMODE) == 0) && #endif (imgp->vp->v_mount->mnt_flag & MNT_NOSUID) == 0 && - (p->p_flag & P_TRACED) == 0) { + (p->p_flag & P_TRACED) == 0 && (p->p_flag2 & P2_NOSUGID) == 0) { /* * Turn off syscall tracing for set-id programs, except for * root. Record any set-id flags first to make sure that Index: sys/kern/kern_fork.c =================================================================== --- sys/kern/kern_fork.c (revision 267243) +++ sys/kern/kern_fork.c (working copy) @@ -571,6 +571,7 @@ * been preserved. */ p2->p_flag |= p1->p_flag & P_SUGID; + p2->p_flag2 |= p1->p_flag2 & P2_NOSUGID; td2->td_pflags |= td->td_pflags & TDP_ALTSTACK; SESS_LOCK(p1->p_session); if (p1->p_session->s_ttyvp != NULL && p1->p_flag & P_CONTROLT) Index: sys/kern/kern_jail.c =================================================================== --- sys/kern/kern_jail.c (revision 267243) +++ sys/kern/kern_jail.c (working copy) @@ -2372,7 +2372,7 @@ goto e_unlock; #endif VOP_UNLOCK(pr->pr_root, 0); - if ((error = change_root(pr->pr_root, td))) + if ((error = change_root(pr->pr_root, td, 0))) goto e_revert_osd; newcred = crget(); Index: sys/kern/vfs_syscalls.c =================================================================== --- sys/kern/vfs_syscalls.c (revision 267243) +++ sys/kern/vfs_syscalls.c (working copy) @@ -855,6 +855,11 @@ &chroot_allow_open_directories, 0, "Allow a process to chroot(2) if it has a directory open"); +/* This one allows unprivileged users to run chroot */ +static int chroot_allow_unprivileged = 0; +SYSCTL_INT(_kern, OID_AUTO, chroot_allow_unprivileged, CTLFLAG_RW, + &chroot_allow_unprivileged, 0, ""); + /* * Change notion of root (``/'') directory. */ @@ -871,11 +876,35 @@ } */ *uap; { struct nameidata nd; + struct proc *p; + int disallow_nesting; int error; + disallow_nesting = 0; error = priv_check(td, PRIV_VFS_CHROOT); - if (error != 0) - return (error); + if (error) { + if (!chroot_allow_unprivileged) + return (error); + + /* + * Disallow this process and its children to use setuid + * bits. Users could hardlink setuid applications into a + * chroot which contains a fake C library to obtain + * super-user privileges. + */ + p = td->td_proc; + PROC_LOCK(p); + p->p_flag2 |= P2_NOSUGID; + PROC_UNLOCK(p); + + /* + * Don't allow unprivileged code to chroot more than once. + * This prevents escaping from a chroot after dropped + * privileges. + */ + disallow_nesting = 1; + } + NDINIT(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF | AUDITVNODE1, UIO_USERSPACE, uap->path, td); error = namei(&nd); @@ -890,7 +919,7 @@ goto e_vunlock; #endif VOP_UNLOCK(nd.ni_vp, 0); - error = change_root(nd.ni_vp, td); + error = change_root(nd.ni_vp, td, disallow_nesting); vrele(nd.ni_vp); NDFREE(&nd, NDF_ONLY_PNBUF); return (error); @@ -931,9 +960,10 @@ * authorize this operation. */ int -change_root(vp, td) +change_root(vp, td, disallow_nesting) struct vnode *vp; struct thread *td; + int disallow_nesting; { struct filedesc *fdp; struct vnode *oldvp; @@ -941,6 +971,10 @@ fdp = td->td_proc->p_fd; FILEDESC_XLOCK(fdp); + if (disallow_nesting && fdp->fd_rdir != rootvnode) { + FILEDESC_XUNLOCK(fdp); + return (EPERM); + } if (chroot_allow_open_directories == 0 || (chroot_allow_open_directories == 1 && fdp->fd_rdir != rootvnode)) { error = chroot_refuse_vdir_fds(fdp); Index: sys/sys/proc.h =================================================================== --- sys/sys/proc.h (revision 267243) +++ sys/sys/proc.h (working copy) @@ -646,6 +646,7 @@ /* These flags are kept in p_flag2. */ #define P2_INHERIT_PROTECTED 0x00000001 /* New children get P_PROTECTED. */ +#define P2_NOSUGID 0x00000002 /* Ignore set[ug]id on exec. */ /* * These were process status values (p_stat), now they are only used in Index: sys/sys/vnode.h =================================================================== --- sys/sys/vnode.h (revision 267243) +++ sys/sys/vnode.h (working copy) @@ -605,7 +605,7 @@ void cache_purge_negative(struct vnode *vp); void cache_purgevfs(struct mount *mp); int change_dir(struct vnode *vp, struct thread *td); -int change_root(struct vnode *vp, struct thread *td); +int change_root(struct vnode *vp, struct thread *td, int disallow_nesting); void cvtstat(struct stat *st, struct ostat *ost); void cvtnstat(struct stat *sb, struct nstat *nsb); int getnewvnode(const char *tag, struct mount *mp, struct vop_vector *vops,