BSD tty security, part 3: How to Fix It

Dan Bernstein brnstnd at kramden.acf.nyu.edu
Sat May 11 06:06:07 AEST 1991


Since John expressed some doubts, enclosed here is an informal but
reasonably detailed proof of the security of my proposed solution. Also
here is a justification of each of the required steps in my solution.
Someone who reads through this should understand why each step is
necessary and why in combination they are sufficient; if there's any
misunderstanding, send me e-mail, and I'll post a clarification.

In article <19253 at rpp386.cactus.org> jfh at rpp386.cactus.org (John F Haugh II) writes:
> No, the obvious solution is to provide for file access revocation.  What
> do you do to assure (because, remember, we have to provide assurances
> that our solution really works - handwaving doesn't cut it) that there
> isn't some process on the other side of the "privileged program" that
> is reading our hardware tty?

Huh? Under suggestion #24, no user process will ever open hardwired
/dev/tty* or /dev/modem* or whatever you want to call those devices.
Provided that the normal UNIX security mechanisms are in place, and
provided that /dev/modem* always have their permission bits properly
set, the system guarantees that no user program can ever open those
files. Are you doubting the concept of file permissions?

> Your suggestions are the grossest hack I've
> seen to date for solving this problem, and it doesn't even provide
> any assurance that it has been solved,

Wtf are you talking about? As I said at first, my solution provides
strong guarantees that the problem has been solved. Witness a proof of
security:

1. All /dev/pty* are initially group- and world-inaccessible, and owned
   by user pty, as required by the solution. Hence, at the outset, only
   programs running as root or pty may open those files. Provided that
   none of those programs ever changes the mode of /dev/pty*, they will
   continue to be group- and world-inaccessible.

2. A file may only be open in a process if it is (1) opened directly by
   that process, (2) passed in from another process, e.g., via fork().
   Provided that none of those root/pty programs ever passes an open
   /dev/ptyxx descriptor to another process, and given #1, we conclude
   that no process can have /dev/pty* open unless it is a root/pty
   program. (This means, more precisely, that it has at some point had
   root or pty permissions. Even more precisely, by ``process'' I
   distinguish between processes with the same pid and process state but
   separated by an exec(), and by ``passing'' I mean to include leaving
   the descriptor open through an exec().)

3. Provided that none of those root/pty programs ever passes an open
   /dev/ptyxx descriptor to another process, and because /dev/ptyxx may
   only be opened once, there always exists at most one process with
   /dev/ptyxx open.

4. I claim that if /dev/ptyxx is not open in any process, then
   /dev/ttyxx is not (group- or world-) accessible, and is owned by pty,
   provided that no root/pty process changes an inaccessible /dev/ttyxx
   to an accessible one (or one with a different owner) without having
   /dev/ptyxx open. Proof: The claim is true at first, as all /dev/tty*
   are initially inaccessible as required by the solution. By #2,
   /dev/ptyxx may only be open in a root/pty process. So by hypothesis
   we must only check that when such a process closes /dev/ptyxx,
   /dev/ttyxx is not accessible. But, as required by my solution, such a
   process must change /dev/ttyxx to be owner pty mode 600 before it
   closes /dev/ptyxx. Hence the claim is always true.
   
5. If /dev/ptyxx is not open in any process, then /dev/ttyxx is not open
   in any process except possibly a root/pty process. Proof: The claim
   is true at first. As in #4, /dev/ptyxx may only be open in a root/pty
   process. So we must only check that when a root/pty process closes
   /dev/ptyxx, /dev/ttyxx is not open except possibly in a root/pty
   process (which, in fact, can only be itself). But, as required by my
   solution, the process must open a separate open file description to
   /dev/ttyxx which it does not pass to any other process. Furthermore,
   also as required by my solution, before closing /dev/ptyxx, and after
   changing /dev/ttyxx to be owner pty mode 600, the process must be
   told by TIOCOPENCT that the only open file description to /dev/ttyxx
   is that separate descriptor, which again it has not passed to any
   other process. Hence the claim is true.

That's the basic idea. From these guarantees you can draw further
conclusions. If, for example, telnetd opens /dev/ptyxx and /dev/ttyxx
and then passes the descriptors to login, then by #5 we see that no
other process can have had /dev/ttyxx open from when /dev/ptyxx was last
open, and by #4 we see that no other process can have opened /dev/ttyxx
during the last period in which /dev/ptyxx was not open. Hence no other
process has /dev/ttyxx open.

Now, John, would you like to repeat your statement about assurances?

Sure, a stupid superuser (or a buggy root/pty program) can invalidate
any of the assumptions mentioned above: for example, he can change
/dev/ptyxx and /dev/ttyxx to mode 666 owner shmoe, and (guess what?)
it's insecure. There's no way to solve problems like this without adding
mandatory access controls.

At the top of this article I promised to explain why each of the
required steps in my solution was necessary. Well, steps 3, 4, and 9 are
used in guarantee #5, and steps 5, 6, and 10 are used in guarantees #1
and #4. I don't see any way to eliminate those steps without breaking
at least one of the guarantees; conversely, those steps together with
normal UNIX security do make the proofs work.

Steps 1, 2, 7, and 8 fix a separate problem. The above guarantees only
address the issue of having /dev/ttyxx open directly. Unfortunately,
today's UNIX systems have alternative ways of affecting the device
through a different mechanism than opening it: for example, /dev/tty.
Those steps eliminate the current /dev/tty and reduce that alternative
access mechanism to the original mechanism, namely file descriptors. I
should note that there are many other alternative access mechanisms
(e.g., process groups) that in some systems introduce their own sets of
security problems, but the only universal mechanism allowing complete
data corruption is /dev/tty. (Having two ptys with the same minor number
would be just as dangerous, but I have never seen anyone do that, and it
would certainly break the usual pty allocation code.)

Do I need to explain step 11, namely protecting /etc/utmp on Suns? This
is what I call a SCINUP---a Security Compromise Introduced in the Name
of User Power. I suppose it's like the example of a superuser making all
the tty files world-writable, but /etc/utmp is so easy to fix that I
felt compelled to mention it.

Finally, step 12 fixes an old SCINUP, namely having users leave their
ttys world-usable in order to allow user-to-user communication. This is
also only somewhat related to the basic problems of tty access. I leave
it to everyone else to seek out and destroy further SCINUPS embedded in
common programs.

There. Is there anyone left who doesn't understand why a particular step
is necessary, or who doesn't believe that my solution guarantees rather
strong security? (Again, if I need to clarify anything above, send me
some e-mail.) Those of you who've been shouting religious stupidities
about how you absolutely need to see break code to be convinced that my
fixes work---can you see the difference now between a proof of security
by logic and a ``proof'' of security by testing? (I will address this
point in detail in a coming message.)

---Dan



More information about the Comp.unix.wizards mailing list