Implement unprivileged chroot

This builds on recently introduced NO_NEW_PRIVS flag to implement
unprivileged chroot, enabled by `security.bsd.unprivileged_chroot`.
It allows non-root processes to chroot(2), provided they have the
NO_NEW_PRIVS flag set.

The chroot(8) utility gets a new flag, -n, which sets NO_NEW_PRIVS
before chrooting.

Reviewed By:	kib
Sponsored By:	EPSRC
Relnotes:	yes
Differential Revision:	https://reviews.freebsd.org/D30130
This commit is contained in:
Edward Tomasz Napierala 2021-07-20 08:56:04 +00:00
parent 27ab791a55
commit a40cf4175c
3 changed files with 44 additions and 6 deletions

View File

@ -955,6 +955,10 @@ kern_chdir(struct thread *td, const char *path, enum uio_seg pathseg)
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.
*/
@ -967,11 +971,20 @@ 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)
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);

View File

@ -28,7 +28,7 @@
.\" @(#)chroot.8 8.1 (Berkeley) 6/9/93
.\" $FreeBSD$
.\"
.Dd June 27, 2020
.Dd July 20, 2021
.Dt CHROOT 8
.Os
.Sh NAME
@ -39,6 +39,7 @@
.Op Fl G Ar group Ns Op Cm \&, Ns Ar group ...
.Op Fl g Ar group
.Op Fl u Ar user
.Op Fl n
.Ar newroot
.Op Ar command Op Ar arg ...
.Sh DESCRIPTION
@ -61,6 +62,16 @@ Run the command with the permissions of the specified
.It Fl u Ar user
Run the command as the
.Ar user .
.It Fl n
Use the
.Dv PROC_NO_NEW_PRIVS_CTL
.Xr procctl 2
command before chrooting, effectively disabling SUID/SGID bits
for the calling process and its descendants.
If
.Dv security.bsd.unprivileged_chroot
sysctl is set to 1, it will make it possible to chroot without
superuser privileges.
.El
.Sh ENVIRONMENT
The following environment variable is referenced by

View File

@ -44,6 +44,7 @@ static char sccsid[] = "@(#)chroot.c 8.1 (Berkeley) 6/9/93";
__FBSDID("$FreeBSD$");
#include <sys/types.h>
#include <sys/procctl.h>
#include <ctype.h>
#include <err.h>
@ -51,6 +52,7 @@ __FBSDID("$FreeBSD$");
#include <limits.h>
#include <paths.h>
#include <pwd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -67,13 +69,15 @@ main(int argc, char *argv[])
const char *shell;
gid_t gid, *gidlist;
uid_t uid;
int ch, gids;
int arg, ch, error, 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 @@ main(int argc, char *argv[])
if (*grouplist == '\0')
usage();
break;
case 'n':
nonpriviledged = true;
break;
case '?':
default:
usage();
@ -153,6 +160,13 @@ main(int argc, char *argv[])
}
}
if (nonpriviledged) {
arg = PROC_NO_NEW_PRIVS_ENABLE;
error = procctl(P_PID, getpid(), PROC_NO_NEW_PRIVS_CTL, &arg);
if (error != 0)
err(1, "procctl");
}
if (chdir(argv[0]) == -1 || chroot(".") == -1)
err(1, "%s", argv[0]);
@ -179,6 +193,6 @@ static void
usage(void)
{
(void)fprintf(stderr, "usage: chroot [-g group] [-G group,group,...] "
"[-u user] newroot [command]\n");
"[-u user] [-n ] newroot [command]\n");
exit(1);
}