Skip site navigation (1)Skip section navigation (2)
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>