Date: Mon, 11 Jan 1999 15:04:49 +1030 (CST) From: Mark Newton <newton@atdot.dotat.org> To: Don.Lewis@tsc.tdk.com (Don Lewis) Cc: robert+freebsd@cyrus.watson.org, newton@camtech.com.au, eivind@yes.no, casper@acc.am, freebsd-security@FreeBSD.ORG Subject: Re: About chroot Message-ID: <199901110434.PAA16078@atdot.dotat.org> In-Reply-To: <199901071001.CAA03365@salsa.gv.tsc.tdk.com> from "Don Lewis" at Jan 7, 99 02:01:41 am
next in thread | previous in thread | raw e-mail | index | archive | help
Don Lewis wrote: > } > > kern/9183 > } > > } > It seems like a neat idea. However, enabling vfs.hard_chroot may break > } > security in certain environments--in particular, environments where a > } > chroot is used as part of the system bootup, and where existing > } > services will try to use chroot to create sandboxes. > } > } Agreed. I figured those cases would make up a minority. > > There's at least one place where I need two levels of chroot(). The hack > that I'm currently using added another rdir-like entry to struct filedesc. > I'm currently in the process of reimplementing this as a stack. The main > main problem I'm having is a lack of time to work on the code. > One tricky thing is that once you allow more than one level of chroot(), > you create the possibility of breaking out of the jail. I've got some > ideas on how to protect against this. Care to share? Although multiple recursive levels of chroot() are one of those things that would be "nice to have", do you think the implementational complexity of an in-kernel root directory stack are worth it considering that people have been able to work around chroot() limitations for the last 20 years or so? [ ooooh, I'm gonna get it in the neck for that question... ] > } Of course -- But that's relatively easy: The same check for > } p_rdir that's used to work out whether chroot() should fail can also > } be placed into mknod(2) and mount(2). I wouldn't even need to > } unstaticize hard_chroot to make those changes happen. > > I did something like this a couple years ago (don't forget about umount()). > My current preference would be to set a flag in struct proc that disables > access to these. The process that puts the suspect process in jail would > do the chroot() and set the flag. I've modified my initial patch to vfs_syscalls.c; It now affects the syscalls described below if the caller's root directory is not the same as init's root directory AND vfs.hard_chroot != 0 AND if an additional supplementary condition evaluates to true, regardless of the uid of the caller: system call supplementary condition ------------------------------------------------------------------ mount true (:-) umount true chroot true mknod true (only affects mknod(2); FIFOs are still allowed) chmod mode & S_ISUID lchmod mode & S_ISUID fchmod mode & S_ISUID The last three are to prevent a chroot'ed user from making setuid binaries of any kind regardless of who owns them. The vfs.hard_chroot test is performed by means of a macro called FAIL_HARD_CHROOT() defined in the same place as the hard_chroot sysctl. It takes one parameter, which is the supplementary conditional used to fail the syscall. FHC_ALWAYS is defined to 1 for the cases marked as "true" in the table above. > Even better would be finer grained access control. It's too bad that POSIX > wasn't able to standardize something. <shrug> IRIX, HP-UX and Solaris have ACLs. Mind you, they also have filesystems which support multiple "forks" per file to support them... > } We'd probably also want to restrict a user's ability to access LKM/KLD > } interfaces, assuming they're in the kernel in the first place. reboot(2) > } should also be restricted. Are there any others? (deny bind()ing to > } privileged ports? nah, that'd break ftpd, which is part of the > } intended audience of the patch, with very few security benefits). > > I disabled access to lkms, blocked access to processes outside the > jail by kill() and ptrace(). On further reflection, the lkm (or kld) and ptrace cases are already covered by kern.securelevel. The kill case could be covered by unstaticizing hard_chroot, putting the FAIL_HARD_CHROOT() macro into a .h file, and putting something like the following (untested code) at the front of kill()/killpg()/etc in kern_sig.c: FAIL_HARD_CHROOT((pfind(uap->pid))->p_fd->fd_rdir != cp->p_fd->fd_rdir); i.e.: fail the kill() operation if the root directory of the target is not the same as the root directory of the caller. This would mean that processes in one jail couldn't kill processes in other jails on the same system too. vfs_syscalls.c patch against 3.0-RELEASE follows (sans documentation). - mark *** vfs_syscalls.c.3.0-R Thu Dec 24 17:01:07 1998 --- vfs_syscalls.c Mon Jan 11 14:34:52 1999 *************** *** 86,92 **** --- 86,107 ---- static int setutimes __P((struct proc *, struct vnode *, struct timeval *, int)); static int usermount = 0; /* if 1, non-root can mount fs. */ + /* + * hard_chroot - If 1, we can fail some syscalls with EPERM for any processes + * which are chroot()ed. + */ + #define FAIL_HARD_CHROOT(when) if (hard_chroot) { \ + if (p->p_fd->fd_rdir != initproc->p_fd->fd_rdir) \ + if ((when)) { \ + return(EPERM); \ + } \ + } + #define FHC_ALWAYS 1 + + static int hard_chroot = 0; + SYSCTL_INT(_vfs, OID_AUTO, usermount, CTLFLAG_RW, &usermount, 0, ""); + SYSCTL_INT(_vfs, OID_AUTO, hard_chroot, CTLFLAG_RW, &hard_chroot, 0, ""); /* * Virtual File System System Calls *************** *** 123,128 **** --- 138,145 ---- struct nameidata nd; char fstypename[MFSNAMELEN]; + FAIL_HARD_CHROOT(FHC_ALWAYS); + if (usermount == 0 && (error = suser(p->p_ucred, &p->p_acflag))) return (error); *************** *** 400,405 **** --- 417,424 ---- int error; struct nameidata nd; + FAIL_HARD_CHROOT(FHC_ALWAYS); + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_USERSPACE, SCARG(uap, path), p); if (error = namei(&nd)) *************** *** 832,837 **** --- 851,858 ---- int error; struct nameidata nd; + FAIL_HARD_CHROOT(FHC_ALWAYS); + error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); *************** *** 1017,1022 **** --- 1038,1045 ---- int whiteout = 0; struct nameidata nd; + FAIL_HARD_CHROOT(FHC_ALWAYS); + error = suser(p->p_ucred, &p->p_acflag); if (error) return (error); *************** *** 1920,1925 **** --- 1943,1950 ---- int error; struct nameidata nd; + FAIL_HARD_CHROOT(uap->mode & S_ISUID); + NDINIT(&nd, LOOKUP, FOLLOW, UIO_USERSPACE, SCARG(uap, path), p); if (error = namei(&nd)) return (error); *************** *** 1949,1954 **** --- 1974,1981 ---- int error; struct nameidata nd; + FAIL_HARD_CHROOT(uap->mode & S_ISUID); + NDINIT(&nd, LOOKUP, NOFOLLOW, UIO_USERSPACE, SCARG(uap, path), p); if (error = namei(&nd)) return (error); *************** *** 1977,1982 **** --- 2004,2011 ---- { struct file *fp; int error; + + FAIL_HARD_CHROOT(uap->mode & S_ISUID); if (error = getvnode(p->p_fd, SCARG(uap, fd), &fp)) return (error); -------------------------------------------------------------------- I tried an internal modem, newton@atdot.dotat.org but it hurt when I walked. Mark Newton ----- Voice: +61-4-1958-3414 ------------- Fax: +61-8-83034403 ----- To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-security" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199901110434.PAA16078>