The somewhat surprising history of chroot()

August 28, 2015

The chroot() system call is one of those that I think of as being really old. Up until a while back, if you'd pressed me I'd have guessed that it originated in V7. Then I dug into it and in that tweet claimed it was from 4.2 BSD. This is what you'd kind of expect and what it sort of looks like, but it turns out to be wrong and the history of chroot() seems to be much more interesting than I expected.

The classic use of chroot() is for safely confining network daemons, and indeed this is what ftpd uses it for in 4.2 BSD. But there's another use of it in the 4.2 BSD tree lurking in a file called /usr/src/games/compat/runcompat.c. As a manpage and a Readme there make clear, this is a system to let you run PDP-11 V6 and V7 programs on your VAX. It uses chroot() to optionally put those programs inside a familiar V6 or V7 directory hierarchy. This appears to go more or less all the way back to 32V, the first Unix version to run on a VAX and thus the first one where this desire would come up. But it's not where chroot(2) was added.

Much to my surprise, it turns out that chroot(2) is in V7 after all. It's not obvious because there's no separate manpage for it; instead it's part of the chdir(2) manpage. I don't know why it's there, apart from 'someone thought it was an obvious feature'; tuhs.org has more or less complete V7 source, and there's no programs in the source tree that use it that I can find. Apparently it was added first and then people found various uses for it later. What we now think of as its most prominent uses (for things like confining anonymous ftp) are relatively latecomers to the game, since they started years after chroot() itself came into existence.

(I don't believe that the V7 chroot() was a security measure.)

Sidebar: why chroot is in the chdir manpage

V7 implements chdir(2) and chroot(2) with basically the same code; you can see it in /usr/sys/sys/sys4.c. It's pretty clever:

chdir()
{
    chdirec(&u.u_cdir);
}

chroot()
{
    if (suser())
        chdirec(&u.u_rdir);
}

chdirec(ipp)
register struct inode **ipp;
{
[...]

In other words, the only real difference between chdir and chroot is what field in the u user structure they act on. chdir acts on the 'current directory' field, chroot acts on the 'root directory' field.

The actual implementation of chroot requires a bit more kernel code than this, because of course the u_rdir field didn't exist before chroot was added. But there turn out to be remarkably few places that deal with u_rdir in the V7 kernel. By the way, reading this code makes me suspect that a V7 chroot was remarkably permeable; I don't see any reason why you can't just 'cd ..' your way right out of one (although I may be missing something subtle in the code). That obviously changed later.

(Note that the V7 manpage doesn't claim that chroot() confines processes; it simply says that it changes what '/' means to them. Which is literally what the kernel code does.)


Comments on this page:

By Alan at 2015-08-28 08:30:01:

There are definitely people who argue that chroot is not a security measure. Including writers of LWN articles. The runcompat use-case fits in with that.

Nice history! I think you're right, there's no code to stop "/.." escaping there :).

By Greg A. Woods at 2015-09-24 20:48:43:

I have a vague recollection of hearing, and/or participating in, a discussion about the chroot() system call and chroot command at BSDCan this spring (2015), possibly with Steve Bourne and Kirk McKusick. As I recall the consensus of the discussion was that existence of the chroot() system call in V7 was intended mostly for testing, particularly of OS distributions. I suspect there are no programs using it in the distribution because such test software was considered throw-away and thus not included in the release distribution.

The chroot() system call does not exist in PWB Unix, and it's not likely in CB Unix either, both having started with v6 and gaining v7 additions in bits and pieces.

However the chroot() system call is in UNIX System III, as is a new chroot command, and nami.c has had a few small changes which make chroot() secure, at least with respect to chdir("/.."). It is also included as a feature in the login command to restrict users on login to a limited subsystem if their shell is specified as an asterisk (by chrooting to their home directory and then execing another login program from the restricted subsystem), so it is clearly intended for security purposes on pre-networked systems.

I suspect though that the security implications of chroot() were considered in V7 (given it was restricted to the superuser), though not so seriously as to consider how one might have broken out from it, and so the missing checks to prevent chdir("/..") from working were merely an oversight -- one that got corrected when the first commercial/external production release, namely System III, was created. Perhaps Norman Wilson might have further insight into some of this history.

The creation of restricted access to timesharing Unix systems goes almost as far back as Unix has been in use outside Research. Wood and Kochan's book "UNIX System Security" from 1985 (with a focus on System V Release 2) has a great deal of material and sample code showing how to set up chroot-ed environments for restricting users to limited working environments on non-networked timesharing systems, but it's far from the first such guide. They also point out that the login command already has built-in support for doing chroot(). They too missed that V7 included a chroot() call.

Ferbrache and Shearer's book "UNIX Installation Security & Integrity" from 1993 suggests testing software in restricted chroot-ed areas to detect trojans even on non-networked systems, though they warn of running anything as root since any call to mknod() could then help break out of the chroot sub-directory.

Written on 28 August 2015.
« Some notes on using Solaris kstat(s) in a program
The mailing list thread to bug tracking system problem »

Page tools: View Source, View Normal, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Fri Aug 28 02:42:31 2015
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.