Terminals are ridiculously insecure
Dan Bernstein
bernsten at phoenix.Princeton.EDU
Mon Jan 9 21:25:36 AEST 1989
It is unfortunate that terminals may be abused so easily under most
existing variants of UNIX. I will describe here my design of a program
that, under an ULTRIX 2.0 on a VAX 8700, was capable of completely
undermining all protection on terminals.
Most of the statements are based on man pages; some ``Fact''s are
based on what was observed on the ULTRIX system, a BSD 4.2 system,
and a mostly compliant BSD 4.3 variant. Comments on which facts
fail under System V version 7 are appreciated.
This is rather long, and no, I haven't included source at the end.
I've separated this into numbered sections. At the end I provide
my analysis of what this means for UNIX security. Skip a few ctrl-L's
if you don't like reading UNIX technical discussion.
1. The purpose of the program: To be able to produce output upon
any user's tty at any time, regardless of mesg n, TIOCEXCL (such
as in my recent posting about exclon/excloff), or any other possible
protection.
2. From the controller's point of view, this program is executed
as a single process that garners from its command line or input
the names of some number of terminals that it should watch. It
will slowly take control of the terminals as described below; the
process is expected to run until the next system crash.
Upon receiving signal SIGUSR1 (30), the process will check through
a set of input files, one per terminal. Any nonempty file will be
read, truncated back to empty, and typed on that tty.
3. The program, which is named ttyctrl, keeps an array of file
information, one per terminal. In particular, it attempts to keep
open a read-write file descriptor for each tty at all times.
It is reasonably obvious that if a valid write file descriptor
is open for a terminal, output to that terminal is trivial.
So we have the basic idea behind ttyctrl; now let's consider
objections.
4. Objection: How does one open the terminal for RDWR in the
first place? One solution is to open the terminal for RDWR while
the process's owner is on that terminal. The solution actually
used was to wait for a tty's owner to log off.
Fact A: A tty with nobody logged on is rw-rw-rw-. This is true
on every system tested.
Thus it is possible to open a terminal for reading and writing;
just wait until nobody is on it.
5. Objection: Doesn't vhangup() revoke permission for such processes
when a user logs in? Answer: No.
Fact B: On every system on which I have tested it, vhangup()
does not revoke permission for a process keeping ttys open
as per ttyctrl. In particular, ttyctrl never lost a file
descriptor when someone logged in or out.
It is not clear from vhangup()'s various man pages whether it is
actually supposed to revoke ttyctrl's access. But even if vhangup()
did revoke permission, it is quite explicitly stated that vhangup()
leaves /dev/tty as a valid means of access for a process. Thus a
single-terminal version of ttyctrl would work regardless of vhangup().
6. Objection: Don't processes also lose the valid file descriptor
when someone logs out? This is not commonly known, and it varies
from system to system. However, it is true quite often. ttyctrl
sidesteps the issue by reopening each tty as soon as someone on
it logs off (i.e., as soon as it becomes rw-rw-rw- root again).
It monitors /etc/wtmp for logins and logouts.
7. Objection: Aren't there any other ways that tty file descriptors
lose their access? Not in a normal case in any situation that could
apparently be encountered upon any of the systems tested. In particular,
Fact C: ttyctrl's reopening of each terminal upon each logout has
proven sufficient for keeping an open RDWR file descriptor during
every login session afterwards.
8. Objection: By stty tostop can't a user force a background process
writing to his terminal to stop? Answer: Yes for processes not in
the distinguished process control group of the tty---except that
as tty (4) points out, a process ignoring SIGTTOU is excepted and
allowed to produce output. So ttyctrl ignores SIGTTOU.
9. Objection: Doesn't ttyctrl get killed by SIGHUP and other signals
when the terminal line hangs up, etc.? No. All signals except SIGSTOP
and SIGKILL are catchable, and ttyctrl can do that. ttyctrl also
dodges many signals as described below.
10. Objection: Aren't there other ways to produce SIGSTOP or SIGKILL
when ttyctrl produces output? Not in the manual pages.
11. Objection: If ttyctrl opens a terminal, it is in the distinguished
process control group of that terminal and thus can be sent a signal
by the owner of that terminal, namely SIGKILL. This is true on some
systems; ttyctrl avoids the issue by immediately resetting its process
group and using TIOCNOTTY (revoke control terminal) every time it
opens a terminal.
12. Objection: If vhangup() did a more thorough job then ttyctrl might
have to be a single-process-per terminal program, keeping its tty as
its control terminal. Wouldn't it then be susceptible to 11? Answer:
It appears that setpgrp() alone is sufficient protection; the TIOCNOTTY
is only used for extra safety. In any case, the several premises for
this objection are not satisfied on the systems tested.
13. Objection: Doesn't ttyctrl get stuck every time it opens a terminal?
``After all, when I accidentally try to read or write to a no-user tty,
I get stuck.''
I included this objection since it is asked by inexperienced observers.
The answer, of course, is that all ttys are opened O_NDELAY.
14. A secondary purpose was added when it became clear that it was
possible: To be able to type input upon any user's tty at any time
as if they had typed it, as per TIOCSTI (see tty (4)). This is
obviously far more of a menace to security.
So upon receiving signal SIGUSR2 (31), ttyctrl will check through
another set of input files, one per terminal. Any nonempty file
will be read, truncated back to empty, and typed as terminal input
on that tty.
15. Objection: TIOCSTI must be done on a read-accessible descriptor.
As a matter of fact, this was not true until recent UNIX systems---
it used to be possible to type input on the screen of anyone with
messages on (under the old scheme where mesg y meant writable to
everybody for the tty). But it is true now.
Observe that the above discussion of ttyctrl's activities did not
discriminate against read descriptors. All terminals are opened
for both reading and writing and so this point is moot.
16. Objection: TIOCSTI must be done upon the control terminal;
it simply will not work on any other terminal.
This is correct and important. It means that ttyctrl must somehow
switch to the appropriate control terminal before it can TIOCSTI
it. Control terminals are established by opening a tty after
a TIOCNOTTY to the old control terminal.
Upon two of the systems, where the old scheme was in force and most
ttys were writable, ttyctrl established the control terminal by
opening the tty for writing, solving this problem. But on the
BSD 4.3 system, there is no access to a terminal where someone
is logged on, even with mesg y---write works on that system by
being setgid tty, where all the ttys are setgid tty. On that
system, two solutions are (1) to have ttyctrl not do TIOCNOTTY
and then hope that its control terminal matches the desired one
or (2) to ``warn'' ttyctrl which terminal it should pay attention
to.
(2) is somewhat susceptible to automation: ttyctrl knows that the
lowest-numbered unused tty is the next one for a login and can thus
situate itself there. But the controlling user must still be alert,
for if the desired victim logs in and then someone else logs in,
ttyctrl has little way of knowing that it should stay upon the
original control terminal.
The final solution to this is that ttyctrl maintains a list of
``favorite'' users, e.g., root and the sysadmins. Any of those
who log in become the primary target.
17. Objection: I just said that on old-scheme systems, ttyctrl
merely opens the terminal for writing, which it can do---but
it has to have a read descriptor in order to use TIOCSTI.
This is another easy objection to answer: ttyctrl does have the
read descriptor it needs. The only purpose to opening the terminal
for writing is to establish it as the control terminal. The reason
for opening it for writing is that that's the effect of mesg y on
such systems.
Let me now summarize the capability of ttyctrl as outlined above, upon
a system where the Facts mentioned are true at least for practical purposes.
Here I speak of the program as a generic entity, including any program
with similar purposes to those stated above that answers the objections
similarly to what is stated above. I will emphasize that I will not
entertain requests for an implementation of ttyctrl.
Used as merely an output center, it blows away any and all security.
It need not worry about control terminals; a single process can handle
enough ttys to practically take over the system. The BSD 4.3 new scheme
is *absolutely no protection* against this.
Used as a TIOCSTI center that can type actual input, ttyctrl becomes
a very dangerous tool. Under the old scheme, it can without trouble be
sure to be able to TIOCSTI any terminal with mesg y. It can in any
case be used to ``hit'' the last user who logged on or any user who it
was ``warned'' about. It is almost reckless for a sysadmin to su or
to log on as root if ttyctrl is waiting for him. The BSD 4.3 new scheme
is *absolutely no protection* against this.
Now ttyctrl does have problems and potential problems. A disadvantage
from a cracker's point of view is that ttyctrl must be run as a system
process, and so it may be tracked. A memory dump of all processes
followed by decompilation will let it be recognized. On a frequently
crashing system it must be reinstalled frequently. In any case, it may
be observed running, no matter how disguised, and is thus traceable.
If used with new scheme terminals or if used specifically to lie in
wait for certain users, ttyctrl must forego the possible protection
of constant TIOCNOTTY and thus may be open to some of the objections
above. In particular, it is possible that a KILL signal could be fed
to it at a crucial moment upon certain systems.
But I have not observed any adequate defense to a program such as
ttyctrl. Can someone provide one? I doubt it. Some schemes may
work against specific ttyctrls but practically nothing works against
single-process-per-terminal ttyctrls. The fact that something such
as ttyctrl is possible indicates some fundamental flaws in the
setup of UNIX terminals.
Now I'll give some heartfelt personal opinions upon this subject.
1. Why the @$*! is vhangup() so incompetent? I consider it insane
that getty() doesn't completely initialize the tty line it presents
to a user. How does the world think half the Trojan Horses under
UNIX work?! Sure, the realhangup() call doesn't have to be publicly
available, but I consider it simply stupid that any random process
can have control of the terminal that I think I'm logging in to.
PLEASE, UNIX operating system designers, listen. COMPLETELY INITIALIZE
A LOGIN TTY LINE. NO /dev/tty ACCESS. NONE. PERIOD. NONE. There is no
rational reason that a process should be able to keep access to a tty
once its owner is no longer the owner of the tty.
2. Of course, most of ttyctrl's work is based upon Fact A: tty's with
nobody on them are readable and writable to everybody. WHY????????
The only sensible excuse I've heard so far is that script(1) needs
to be able to get another tty. Translation: Someone doesn't think
that script should be setuid root long enough to getty for itself.
This is not a sufficient reason to simply open terminals to the
world.
I should think that a terminal with nobody logged on should be readable
and writable by nobody, i.e., mode 000. It doesn't make sense otherwise.
Think of the most trivial abuse of the current system: write to the tty
that somebody's about to log on to. Sure, you must pause as the device
driver waits for a connection, but this provides free and clear writing
to anybody's screen. That's stupid! A terminal should never be writable
or readable by anybody save the person logged onto it, and that
permission should be revoked at logoff if possible, or at the next logon
(by realhangup()) at worst. When someone logs on, their messages should
simply be off. Nobody should have any access to the terminal but them.
Can someone please tell me where I'm wrong on this point? What reason
is important enough (like script(1)) that terminal security should be
in such disarray?
3. This discussion brings up another point about UNIX. There are very
few ways to re-check all the open descriptors referencing a file for
permission on that file. vhangup() is about the closest things come.
Why not? It wouldn't be hard; it wouldn't take any time for the
processes with the descriptors; it would take time only for the
process forcing the check. Of course, there are many legitimate
reasons that the pathname shouldn't be re-namei'd and rechecked
for permission; but I can think of very few good applications
for a process that keeps a file handle in a file if that permission
has now been revoked.
Such a system call to actually revoke permission would not require
changes anywhere else in the kernel; EBADF already exists and seems
custom-designed for cases like this. It would be designed how I
imagine vhangup() is designed (though a little more simply, since
vhangup() [or at least realhangup(), what vhangup() should be]
must think about revoking /dev/tty permission as well as /dev/ttyxx).
Simply go through the appropriate file table (inodes? gnodes? well,
depends on the system, I guess) and check each file against the
permission on this one. It is easy to catch all links to the file
as well as the file.
What have I missed? Why doesn't such a system call exist?
Why do programs that ``grab that file/directory/passwdfile/tty
while it's readable and we have it forever!'' have to work?
Well, I'll get off the soapbox now.
And, dear reader, for getting this far, you win $10000!!!!!!!!! :-)
Just joking. I'm not *that* confident that nobody'll read this far.
I hope somebody's listening.
---Dan Bernstein, bernsten at phoenix.princeton.edu
More information about the Comp.unix.wizards
mailing list