Re: "user" chroot (patch)

2017-03-11 Thread Mateusz Guzik
On Sat, Mar 11, 2017 at 05:02:14AM +, Kristaps Dzonsons wrote:
> In running risky non-root applications, it'd be great to chroot(2)
> without needing to be root.  But the manpage says no.  So I added a
> system call, uchroot(2), that does the following:
> 
>  (1) performs the change-root w/o checking for root
>  (2) disables setuid (flag is inherited)
> 
> The (2) plugs the hole wherein a non-root normal chroot could allow for
> privilege escalation by a cooked master.passwd and setuid.
> 
> Enclosed is a patch.  (Without the bits generated from make syscalls: I
> don't know whether that's preferred.  Also doesn't include the libc
> parts.)  It can't be this easy, can it?  What did I miss?
> 

fd tables can be shared between processes (see FORK_SHAREFILES). So in
particular if one process uchroots and another one performs execve, the
execve is done against the chroot while "ignore suid" is not set.

While this bit is easily fixable I think this is too hairy in general
and the feature is unnecessary for the intended purpose.

> My initial motivation is CGI scripts, which can and do pledge, yes, but
> I'd like to limit *path-pledged processes to a directory so scripts
> can't read everything else in /var/www.  For example, within /var/www,
> to (say) /vhosts/foo.com.
> 

I think the way to go is "will only use lookups against a file
descriptor" pledge. Nice added value is that programs can now use
different places in the tree with directory granulalrity as opposed to
having to chroot to the common parent.

This poses a problem with confining ".." lookups.  There is a hack in
FreeBSD to explicitly track them, but perhaps you will be fine enough
with disallowing ".."s in the first place.

-- 
Mateusz Guzik 



"user" chroot (patch)

2017-03-10 Thread Kristaps Dzonsons
In running risky non-root applications, it'd be great to chroot(2)
without needing to be root.  But the manpage says no.  So I added a
system call, uchroot(2), that does the following:

 (1) performs the change-root w/o checking for root
 (2) disables setuid (flag is inherited)

The (2) plugs the hole wherein a non-root normal chroot could allow for
privilege escalation by a cooked master.passwd and setuid.

Enclosed is a patch.  (Without the bits generated from make syscalls: I
don't know whether that's preferred.  Also doesn't include the libc
parts.)  It can't be this easy, can it?  What did I miss?

My initial motivation is CGI scripts, which can and do pledge, yes, but
I'd like to limit *path-pledged processes to a directory so scripts
can't read everything else in /var/www.  For example, within /var/www,
to (say) /vhosts/foo.com.

Best,

Kristaps
Index: src/sys/kern/kern_prot.c
===
RCS file: /cvs/src/sys/kern/kern_prot.c,v
retrieving revision 1.67
diff -u -p -u -r1.67 kern_prot.c
--- src/sys/kern/kern_prot.c	7 Nov 2016 00:26:32 -	1.67
+++ src/sys/kern/kern_prot.c	11 Mar 2017 05:37:47 -
@@ -1077,6 +1077,10 @@ proc_cansugid(struct proc *p)
 	if ((p->p_p->ps_flags & PS_TRACED) != 0)
 		return (0);
 
+	/* not if we've done a uchroot(2) */
+	if (p->p_p->ps_uchroot)
+		return (0);
+
 	/* processes with shared filedescriptors shouldn't. */
 	if (p->p_fd->fd_refcnt > 1)
 		return (0);
Index: src/sys/kern/syscalls.master
===
RCS file: /cvs/src/sys/kern/syscalls.master,v
retrieving revision 1.174
diff -u -p -u -r1.174 syscalls.master
--- src/sys/kern/syscalls.master	4 Sep 2016 17:22:40 -	1.174
+++ src/sys/kern/syscalls.master	11 Mar 2017 05:37:47 -
@@ -563,3 +563,4 @@
 328	OBSOL		__tfork51
 329	STD NOLOCK	{ void sys___set_tcb(void *tcb); }
 330	STD NOLOCK	{ void *sys___get_tcb(void); }
+331	STD		{ int sys_uchroot(const char *path); }
Index: src/sys/kern/vfs_syscalls.c
===
RCS file: /cvs/src/sys/kern/vfs_syscalls.c,v
retrieving revision 1.271
diff -u -p -u -r1.271 vfs_syscalls.c
--- src/sys/kern/vfs_syscalls.c	15 Feb 2017 03:36:58 -	1.271
+++ src/sys/kern/vfs_syscalls.c	11 Mar 2017 05:37:47 -
@@ -726,6 +726,41 @@ sys_chroot(struct proc *p, void *v, regi
 	return (0);
 }
 
+int
+sys_uchroot(struct proc *p, void *v, register_t *retval)
+{
+	struct sys_chroot_args /* {
+		syscallarg(const char *) path;
+	} */ *uap = v;
+	struct filedesc *fdp = p->p_fd;
+	struct vnode *old_cdir, *old_rdir;
+	int error;
+	struct nameidata nd;
+
+	NDINIT(, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE,
+	SCARG(uap, path), p);
+	if ((error = change_dir(, p)) != 0)
+		return (error);
+
+	p->p_p->ps_uchroot = 1;
+
+	if (fdp->fd_rdir != NULL) {
+		/*
+		 * A chroot() done inside a changed root environment does
+		 * an automatic chdir to avoid the out-of-tree experience.
+		 */
+		vref(nd.ni_vp);
+		old_rdir = fdp->fd_rdir;
+		old_cdir = fdp->fd_cdir;
+		fdp->fd_rdir = fdp->fd_cdir = nd.ni_vp;
+		vrele(old_rdir);
+		vrele(old_cdir);
+	} else
+		fdp->fd_rdir = nd.ni_vp;
+
+	return (0);
+}
+
 /*
  * Common routine for chroot and chdir.
  */
Index: src/sys/sys/proc.h
===
RCS file: /cvs/src/sys/sys/proc.h,v
retrieving revision 1.236
diff -u -p -u -r1.236 proc.h
--- src/sys/sys/proc.h	5 Mar 2017 06:40:18 -	1.236
+++ src/sys/sys/proc.h	11 Mar 2017 05:37:47 -
@@ -223,6 +223,8 @@ struct process {
 	uint64_t ps_pledge;
 	struct whitepaths *ps_pledgepaths;
 
+	u_short	ps_uchroot; 		/* Are we user-chrooted? */
+
 	int64_t ps_kbind_cookie;
 	u_long  ps_kbind_addr;