Index: sys/kern/kern_exec.c =================================================================== --- sys/kern/kern_exec.c +++ sys/kern/kern_exec.c @@ -354,6 +354,17 @@ return (do_execve(td, args, mac_p, oldvmspace)); } +static void +execve_nosetid(struct image_params *imgp) +{ + + imgp->credential_setid = false; + if (imgp->newcred != NULL) { + crfree(imgp->newcred); + imgp->newcred = NULL; + } +} + /* * In-kernel implementation of execve(). All arguments are assumed to be * userspace pointers from the passed thread. @@ -640,7 +651,7 @@ vput(newtextvp); vm_object_deallocate(imgp->object); imgp->object = NULL; - imgp->credential_setid = false; + execve_nosetid(imgp); if (imgp->newcred != NULL) { crfree(imgp->newcred); imgp->newcred = NULL; @@ -769,6 +780,9 @@ signotify(td); } + if (p->p_flag2 & P2_NO_NEW_PRIVS) + execve_nosetid(imgp); + /* * Implement image setuid/setgid installation. */ Index: sys/kern/kern_fork.c =================================================================== --- sys/kern/kern_fork.c +++ sys/kern/kern_fork.c @@ -487,7 +487,7 @@ p2->p_flag2 = p1->p_flag2 & (P2_ASLR_DISABLE | P2_ASLR_ENABLE | P2_ASLR_IGNSTART | P2_NOTRACE | P2_NOTRACE_EXEC | P2_PROTMAX_ENABLE | P2_PROTMAX_DISABLE | P2_TRAPCAP | - P2_STKGAP_DISABLE | P2_STKGAP_DISABLE_EXEC); + P2_STKGAP_DISABLE | P2_STKGAP_DISABLE_EXEC | P2_NO_NEW_PRIVS); p2->p_swtick = ticks; if (p1->p_flag & P_PROFIL) startprofclock(p2); Index: sys/kern/kern_procctl.c =================================================================== --- sys/kern/kern_procctl.c +++ sys/kern/kern_procctl.c @@ -419,6 +419,14 @@ return (0); } +static int +no_new_privs(struct thread *td, struct proc *p) +{ + + p->p_flag2 |= P2_NO_NEW_PRIVS; + return (0); +} + static int protmax_ctl(struct thread *td, struct proc *p, int state) { @@ -607,6 +615,7 @@ break; case PROC_REAP_ACQUIRE: case PROC_REAP_RELEASE: + case PROC_NO_NEW_PRIVS: if (uap->data != NULL) return (EINVAL); data = NULL; @@ -710,6 +719,8 @@ return (trapcap_ctl(td, p, *(int *)data)); case PROC_TRAPCAP_STATUS: return (trapcap_status(td, p, data)); + case PROC_NO_NEW_PRIVS: + return (no_new_privs(td, p)); default: return (EINVAL); } @@ -788,6 +799,7 @@ case PROC_STACKGAP_STATUS: case PROC_TRACE_STATUS: case PROC_TRAPCAP_STATUS: + case PROC_NO_NEW_PRIVS: tree_locked = false; break; default: Index: sys/kern/vfs_syscalls.c =================================================================== --- sys/kern/vfs_syscalls.c +++ sys/kern/vfs_syscalls.c @@ -956,6 +956,10 @@ return (0); } +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. */ @@ -968,11 +972,20 @@ 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) - return (error); + 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, td); error = namei(&nd); Index: sys/sys/proc.h =================================================================== --- sys/sys/proc.h +++ sys/sys/proc.h @@ -833,6 +833,7 @@ after exec */ #define P2_ITSTOPPED 0x00002000 #define P2_PTRACEREQ 0x00004000 /* Active ptrace req */ +#define P2_NO_NEW_PRIVS 0x00008000 /* Ignore setuid */ /* Flags protected by proctree_lock, kept in p_treeflags. */ #define P_TREE_ORPHANED 0x00000001 /* Reparented, on orphan list */ Index: sys/sys/procctl.h =================================================================== --- sys/sys/procctl.h +++ sys/sys/procctl.h @@ -63,6 +63,7 @@ #define PROC_PROTMAX_STATUS 16 /* query implicit PROT_MAX status */ #define PROC_STACKGAP_CTL 17 /* en/dis stack gap on MAP_STACK */ #define PROC_STACKGAP_STATUS 18 /* query stack gap */ +#define PROC_NO_NEW_PRIVS 19 /* XXX */ /* Operations for PROC_SPROTECT (passed in integer arg). */ #define PPROT_OP(x) ((x) & 0xf) Index: usr.sbin/chroot/chroot.c =================================================================== --- usr.sbin/chroot/chroot.c +++ usr.sbin/chroot/chroot.c @@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$"); #include +#include #include #include @@ -51,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -69,11 +71,13 @@ uid_t uid; int ch, gids; long ngroups_max; + bool nonpriviledged; gid = 0; uid = 0; user = group = grouplist = NULL; - while ((ch = getopt(argc, argv, "G:g:u:")) != -1) { + nonpriviledged = false; + while ((ch = getopt(argc, argv, "G:g:u:n")) != -1) { switch(ch) { case 'u': user = optarg; @@ -90,6 +94,9 @@ if (*grouplist == '\0') usage(); break; + case 'n': + nonpriviledged = true; + break; case '?': default: usage(); @@ -153,6 +160,10 @@ } } + if (nonpriviledged && + procctl(P_PID, getpid(), PROC_NO_NEW_PRIVS, NULL) == -1) + err(1, "procctl"); + if (chdir(argv[0]) == -1 || chroot(".") == -1) err(1, "%s", argv[0]);