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;