bernsten@phoenix.Princeton.EDU (Dan Bernstein) (01/09/89)
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@phoenix.princeton.edu
gwyn@smoke.BRL.MIL (Doug Gwyn ) (01/10/89)
In article <5228@phoenix.Princeton.EDU> bernsten@phoenix.Princeton.EDU (Dan Bernstein) writes: >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. You can argue this for interactive login ports, but it's definitely false for other tty ports. If there is a serial printer on /dev/ttyP, there is no reason in the world anyone with proper permissions should not be able to "cat file >/dev/ttyP". (This is the simplest example; don't divert this into a discussion of use of spoolers.) >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. That's certainly one approach that addresses the REAL security hole. Some time ago, we had a discussion about use of "public filesystem resource (including device) allocators", the idea being that free resources could belong to the allocator daemon, which of course would be the sole UID with access permission. There was also some discussion about allocation of ptys and corresponding utmp maintenance. If somebody wants to work on this "problem" and really solve the tty administration RIGHT, it would be wise to factor in those notions. >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. There really are applications that rely on the ability to continue access to an open descriptor after the inode permission have been changed. That in itself is not a security problem. Let's fix the real holes and not try to remove useful abilities from UNIX simply out of paranoia.
peter@ficc.uu.net (Peter da Silva) (01/10/89)
Even with totally fascist hangups, a simple trojan horse that established the link for the duration of a tty session would be amazingly useful. Since it would be a sleeper, and could disguise itself, it could run undetected for months, on and off, until someone wants to spoof root. No matter what you do, TIOCSTI is a major security hole and should be eliminated... along with all the terminals that respond to transmit screen/ line/status-line/function-key sequences. I'm totally amazed that such a capability is in the terminal driver. And, as I pointed out in my old "Usenet Virus" article, there's really no long-term protection against a trojan horse. -- Peter da Silva, Xenix Support, Ferranti International Controls Corporation. Work: uunet.uu.net!ficc!peter, peter@ficc.uu.net, +1 713 274 5180. `-_-' Home: bigtex!texbell!sugar!peter, peter@sugar.uu.net. 'U` Opinions may not represent the policies of FICC or the Xenix Support group.
gwyn@smoke.BRL.MIL (Doug Gwyn ) (01/18/89)
In article <4685@xenna.Encore.COM> bzs@Encore.COM (Barry Shein) writes: >Don't try to solve "people problems" with software, it's futile >and a wrong thought. Exactly right! Not only that, but attempted technical solutions often cause even more problems -- for the innocent.
jc@minya.UUCP (John Chambers) (01/23/89)
> >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. > > That's certainly one approach that addresses the REAL security hole. Yeah, and it prevents uucico from initiating a call on that port. That would certainly improve a lot of systems' security. -- John Chambers <{adelie,ima,mit-eddie}!minya!{jc,root}> (617/484-6393) [Any errors in the above are due to failures in the logic of the keyboard, not in the fingers that did the typing.]