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 -0000	1.67
+++ src/sys/kern/kern_prot.c	11 Mar 2017 05:37:47 -0000
@@ -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 -0000	1.174
+++ src/sys/kern/syscalls.master	11 Mar 2017 05:37:47 -0000
@@ -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 -0000	1.271
+++ src/sys/kern/vfs_syscalls.c	11 Mar 2017 05:37:47 -0000
@@ -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(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE,
+	    SCARG(uap, path), p);
+	if ((error = change_dir(&nd, 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 -0000	1.236
+++ src/sys/sys/proc.h	11 Mar 2017 05:37:47 -0000
@@ -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;
 

Reply via email to