Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 9 Jul 1997 09:59:33 +0930 (CST)
From:      newton@communica.com.au (Mark Newton)
To:        robert@cyrus.watson.org (Robert Watson)
Cc:        newton@communica.com.au, sef@kithrup.com, security@FreeBSD.ORG
Subject:   Re: Security Model/Target for FreeBSD or 4.4?
Message-ID:  <9707090029.AA06358@communica.com.au>
In-Reply-To: <Pine.BSF.3.95q.970708114608.4712B-100000@cyrus.watson.org> from "Robert Watson" at Jul 8, 97 11:58:43 am

next in thread | previous in thread | raw e-mail | index | archive | help
Robert Watson wrote:

 > >  > On a related note, has anyone given any thought to making chroot() a
 > >  > user-accessible call? 
 > > Yow.  Extremely bad idea.
 > > However, taking it to the opposite extreme is a good idea.  chroot() loses
 > > its usefulness if a process in a "secured" sandbox can conceivably get 
 > > root privileges -- Solution:  Restrict the chroot() call for *all*
 > > processes that are already chroot()ed.
 > 
 > I was familiar with the actual behavior and implementation of chroot with
 > the processes root, but had never really used it for anything, as it tends
 > to be a more-restricted facility. 

... which is exactly why those who actually WANT to provide a more 
restricted facility use it, and will resist efforts to make it less
restricted!

 > Can the problems with chroot() safely be sumarized in the following
 > categories?
 > 1. Existing libraries and utilities assume that if they attempt to access
 > /etc/spwd.db (or such), they will get these files off of the absolute
 > root. 

Not necessarily - If I boot from the fixit floppy to repair a filesystem
problem, I'd kinda expect my system to behave in a way that closely 
approximates single-user modem booted from the hard disk if I execute
"chroot /rootdisk /bin/sh".  It's difficult to perform tests to make
sure that the filesystem problem has been fixed if executables I run
don't require the dodgy filesystem to run!

Also, if I'm using chroot() to make a sandbox for a user in a restricted
environment, I'm quite happy to assume that existing utilities will see
all files relative to their current root directories, rather than the
"absolute" root (if there is such a thing -- All we really have is a
vnode address, which eventually translates to an inode number, after all :-). 
I don't want a user in a restricted environment to type
"login unrestricteduser" and have a bash at guessing their password
or somesuch, all because the login command is using /etc/spwd.db in the
"real" root!  If I put a process in a sandbox, I expect it to *stay* 
in the sandbox.

 > This may not be changeable, as there are a number of environments
 > where this might be appropriate (boot from floppy, but chroot to mfs/mfs,
 > for example -- the mfs/nfs version of the user database should be used,
 > not the floppy version (same with libraries, etc.)) 

Bleych.  Strongly disagree.  If you want to run a binary and use libraries
on the "real" root, don't use chroot()!

If you've booted from a floppy you have root privileges anyway, and
can chroot() to your heart's content.

 > 2. Creation of hard links retains the setuid behavior of the program, as
 > the permissions and ownership of a file are stored in the inode, and a
 > hardlink merely duplicates the reference to the inode (so I can create
 > /tmp/su as a hard link to /usr/sbin/su, assuming they are both on /usr,
 > and retain the suid and ownership.)  This has upsides and downsides -- one
 > downside is if I chroot() myself to /tmp, the existing su will suffer from
 > problem 1.  The problem here is that I, as a normal user, can create
 > arbitrary references to suid programs (effectively, instances of them)
 > anywhere I can write to on the same file system.  /usr/tmp and /tmp are
 > the most common problem in this area.

That's not a "problem", it's just a feature.  Under normal circumstances,
creating additional references to setuid executables isn't a problem,
because those executables will (we hope) carry out suitable permission
checks before doing anything nasty.  The "problem" is the use of chroot()
by those who don't understand how it works, not the user of chroot() per se.

 > There are other side effects of this behavior -- as a normal user, I can
 > fill up /tmp with files I can't remove (as sticky bit prevents me from
 > removing references to files I don't own.) 

<shrug>  So?  You're not really "filling up" /tmp, you're just creating 
directory entries.  And you can do that with hard links whether the files
in question are setuid or not.  Nobody has really cared all that much for
the last fifteen years.

 > Additionally, I can hard link
 > /usr/sbin/su (old buggy version) to /usr/tmp/su before an upgrade to fix
 > the bug, and then the upgrade will leave the old version in /usr/tmp/su (I
 > assume it unlinks the /usr/sbin/su version during upgrade?)  Programs may
 > assume that by overwriting (unlinking first) the old version of a setuid
 > program, they are disposing of it, but this may not be true since hard
 > links have the same permissions and content (being the same file :).

Again, this isn't a problem with hard links, it's a problem with negligent
system administration.  If you've taken on the proactive task of hard
linking /usr/sbin/su to /usr/tmp/su, it is expected that you have sufficient
presence of mind to clean up after yourself when you don't need it anymore.

We don't go altering the semantics of fundamental syscall/kernel behaviour
just to make up for the personal deficiencies of sysadmins who don't know
what they're doing, or to cater for contrived scenarios which, you'd have
to admit, are unlikely to occur anyway.  We're not Microsoft:  We can't
run under the assumption that those looking after a UNIX system are
totally brainless.  If someone is stupid enough to make a mistake, it isn't
the OS's job to proect them from it.

 > Symlinks don't have the same problem in that they aren't suid (or such),

The problem is not the link.  The problem is the sysadmin.

 > 3. Current chroot() allows you to undo an old chroot().  If chroot were to
 > become a generally useable call (or even if not), chroot() should only be
 > able to move down the hierarchy, not up. 

That is a sound description of the current chroot() semantics -- Kinda.
Absolute pathnames supplied to chroot() are "relative" to the current
root directory.  "relative" pathnames are, well, anyone's guess.

 > This would make it more
 > transparent as far as the caller was concerned -- they would not have ot
 > know if they were already chroot'd.  If the entire filesystem is already
 > chroot("/mfsroot"), then an ftpd chroot("/usr/ftp") would chroot
 > effectively to "/mfsroot/usr/ftp", and be allowed because it does not
 > violate a previous chroot? 

That is what currently happens.

What I attempted to stress in my last message which I appear to have
to stress again is that the act of chroot()ing to /usr/ftp in that
case DOESN'T ALTER THE PROCESS' CURRENT DIRECTORY (caps used for 
emphasis, not shouting :-).  If my current directory is /mfsroot
and I execute `chroot("/usr/ftp")', my CURRENT directory relative
to the "real" root is *STILL* /mfsroot, even though my root directory
relative to the "real" root is /mfsroot/usr/ftp.  That is to say, 
by using chroot() I've managed to get myself outside my own root 
directory heirarchy.

The manpage stays this explicitly too, so you can't be forgiven for
missing it.  From chroot(2):

     It should be noted that chroot() has no effect on the process's current
     directory.

Despite the fact that I'm now NOT in a directory that's downstairs from
my root, the ".." directory will still take me to the parent of my 
current directory -- FURTHER AWAY from my root directory.  So, if my
root is /mfsroot/usr/ftp and my current directory relative to the
"real" root is /mfsroot, after a `chdir("..")' my current directory 
will be the "real" root.  Then all I need to do is `chroot(".")' and
hey presto, I'm out of the chroot()ed environment.

To stipulate that chroot() can't do that because "." is further up
the heirarchy than /mfsroot/usr/ftp is a difficult problem, because
the root directory is not recorded in the proc structure as a pathname:
it's recorded as a vnode address (which makes sense:  Otherwise every
reference to root() would need an additional namei() call, which
isn't exactly an efficient way to do things).  Even if pathnames were
used in the proc structure, that still wouldn't be a win because there's
nothing to say that the pathname recorded as your root isn't a symlink
or hard link to a directory further "up" the heirarchy anyway: If
/mfsroot/usr/ftp is a symlink to /mfsroot/ftp and I do `chroot(".")' 
when my current directory relative to the "real" root is "/mfsroot/ftp/foo"
I *should* be able to do the chroot() successfully, even though the
pathname recorded in my proc structure (/mfsroot/usr/ftp) appears to
be in a different subdirectory branch to the pathname I'm trying
to chroot().

Name to inode number translations are trivial (namei()).  Inode number to
name translations are non-trivial (find / -inum xxxxx -print).  They'd be
required to implement your semantics, however.

 > (since I don't chroot(), I don't know the existing behavior here.)

Same as what you're asking for, *EXCEPT* like essentially every other
kernel facility, chroot has no idea whether the pathname you're referencing
is further "up" the directory than some other arbitrary inode number.

The UNIX heirarchical filesystem structure is an illusion created by
name-to-inode-number mapping tables that we happen to call directories,
provided for no reason other than the fact that humans find it more
natural to deal with names than numbers.  Internally, the filesystem
can be thought of as a single flat address space of inodes.  In isolation,
an inode number does not provide any clue whatsoever about the location
of the file it references in the directory heirarchy because nothing
apart from namei() gives a toss about the directory heirarchy.

 > Are there any other chroot caveats?  Please figve any ignorance as to the
 > current behavior in BSD -- I'm relatively new to the kernel
 > implementation, although have been using and administering BSD-based
 > machines for a number of years.

Try this out:  Build a quick chroot()able environment on any UNIX system
(doesn't have to be BSD):

    unix# cd /
    unix# mkdir chroot
    unix# tar cfl - . | (cd chroot; tar xvfp -)

Change /chroot/root/.cshrc so that root's prompt is "chrooted# " instead
of "unix# " so that you can see which directory root is getting its
startup files from.

Compile the code fragment I posted yesterday which I claimed broke
out of a chroot() environment and stuff the executable into /chroot.
Then:

    unix# rm /chroot/etc/hosts
    unix# chroot /chroot /bin/csh
    chrooted# ls /etc/hosts                 # shoved into our jail
    ls: /etc/hosts: No such file or directory
    chrooted# ./breakout-code-fragment
    unix# ls /etc/hosts                     # wow!  we've broken out!
    /etc/hosts
    unix# 

Study the code carefully in light of this discussion.  Realize that
providing arbitrary users with the ability to run chroot() would allow
arbitrary users to break out of sandboxen.  Examine the patch I posted
and see how it prevents the scenario listed above, at the sacrifice
of a certain amount of existing functionality (I'm not arguing that
the patch be made standard;  I'm just happy to have it available for
those building firewalls and such who might actually need it).

Finally, I've been having trouble getting a clear idea of what you're 
trying to achieve here.  I suspect that you have a specific case in 
mind where providing arbitrary users with the ability to chroot() would
solve a problem you've been having.  If so, perhaps presenting the 
problem instead of putting the solution first might be helpful in this
discussion, and might enable ConcernedReaders to propose a solution that
doesn't involve fundamental redesign (design?  not really.  re-frobbing :-)
of a standard kernel facility that is heavily relied upon in security-
critical environments.

What applications do you see that'd be more readily implemented if 
arbitrary users could chroot()?

    - mark
      [ oh, for the old days, when "chroot" meant "cigar," and life
        was that much simpler <grin> ]

---
Mark Newton                               Email: newton@communica.com.au
Systems Engineer and Senior Trainer       Phone: +61-8-8303-3300
Communica Systems, a member of the        Fax:   +61-8-8303-4403
CAMTECH group of companies                WWW:   http://www.communica.com.au



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?9707090029.AA06358>