Xref: utzoo comp.unix.wizards:25333 alt.security:2403 Path: utzoo!utgpu!news-server.csri.toronto.edu!rpi!zaphod.mps.ohio-state.edu!caen!hellgate.utah.edu!dog.ee.lbl.gov!ucsd!sdcc6!sdcc10!muller From: muller@sdcc10.ucsd.edu (Keith Muller) Newsgroups: comp.unix.wizards,alt.security Subject: Re: BSD tty security, part 3: How to Fix It Summary: a different approach for prior reference attacks Message-ID: <18924@sdcc6.ucsd.edu> Date: 2 May 91 10:06:45 GMT References: <7299:Apr2510:22:2091@kramden.acf.nyu.edu> Sender: news@sdcc6.ucsd.edu Followup-To: comp.unix.wizards,alt.security Lines: 146 In article <7299:Apr2510:22:2091@kramden.acf.nyu.edu>, brnstnd@kramden.acf.nyu.edu (Dan Bernstein) writes: >Here's one way to fix the BSD 4.[234] tty system, i.e., to provide some >strong guarantees that pty and tty sessions are safe and not subject to >corruption or denial of service, with minimal changes to the kernel and >to application programs. This is also meant to apply to systems derived >from BSD, such as SunOS, Ultrix, etc. ..... >9. In each of the tty-handling programs, do the following upon slave >exit: (a) Clean up everything except (if it is convenient) [uw]tmp. >Close 0, 1, 2, and any other random descriptors lying around, except >/dev/ptyxx and /dev/ttyxx. (b) Test /dev/ttyxx with TIOCOPEN*. If >someone else still has it open, continue to step (c); otherwise skip to >step (d). (c) Fork, and exit in the parent. Repeatedly test /dev/ttyxx >(a five-second sleep is fine) until it is closed. (d) Clean up [uw]tmp >and exit. Note that steps (b) and (c) can fit into a simple library ..... While this may look useful for patching an existing system, it might be more profitable in the long term to step back from the details of the current problem and investigate the underlying problem with ttys. I really do not want to depend on the actions of step 9 (especially step 9c....) to promote security (what if the master side process dies prematurely for example; not all servers can be that robust all the time). This also greatly increases the complexity of servers (even if it is in a library) and divides the task of resource protection between the kernel and numerous user processes in a complex way. If not all tty allocators are robust 100% of the time security is lost. --- Since kernel changes are being proposed in the above solution, maybe a different approach would provide a less complex solution. In simple terms a major problem with ttys has always been the semantics of the revocation of access rights to a tty (pseudo or real). The difficulty is created by both "valid" processes and "revoked" processes sharing access to common data structures (inode and device tty structures) and the routines which implement tty semantics. The technique described below is based on controlling access rights through the system open file table. This method adds a system call I shall call revoke(tty) just for the context of this description. Revoke(tty) has the semantics where all other open file references (except for the one passed as an arg) to a tty (real or slave side of a pty) are revoked. Clearly this is a root only syscall. Revoke() can be expanded to support any semantics required for i/o requests by jobs on file descriptors pointing to invalidated ttys as required. It can also function as a way to attach background jobs after a tty session is terminated. The most basic implementation of revoke() (for tty protection only) would be to add a flag to the f_flag field in the open file table which will be set whenever access rights are revoked. Revoke() will set this flag in all other entries in the open file table (except the one passed as an arg) that point to the same tty as the arg (you check by same inode and major/minor in all active inodes if ttys have multiple nodes in the file system). The revoked access flag is then checked whenever a tty related routine is entered (like ttread(), ttwrite()) and before performing an operation on the tty (like fchmod()). Whenever a process awakes from sleeping in one of these tty routines, the flag is checked. If the flag is found set, the appropriate errno is set (if that is what is required) and the system call returns (you could even return EOF on a read, with a -1 on later calls). To check this flag in the tty routines requires that a pointer to the open file table related to this tty system call must be passed to the device and on to the tty routines as an arg (a simple change from the current code). This simple implementation of revoke() is NOT a major kernel mod. A more complex implementation of revoke() would address the two issues of last close on a tty not being called when background jobs are hung and the ability to attach background jobs. In this case revoke() could be called as the result of a close on the master side of a pty, or by init when a login shell dies. In addition to the flag in the open file table, revoke() would be expanded to function similar to a dup2() to modify the f_fileops and f_data fields in each open file table structure being modified. Revoke() modifies f_data to point at a memory structure that would be used to contain information for later re-attachment of the tty (it could contain a description of tty modes for example). By changing f_data, the references to the ttys inode in the active inode table is decremented (which is what f_data used to point at). This allows the device close routines to be called. F_fileops are changed to point at a collection of routines which implement the "required" semantics of a detached terminal (allows you to specify more complex ops other than failing i/o from background jobs). These new f_fileops could use the new structure pointed to by f_data for current use and/or future attachment needs. So when a background process does a read for example, rwuio() would call these new routines instead of the device (and then the line discipline) routines. In this method wakeups in tty routines would find the revoke flag set in f_flags and return to the kernel entry point routine such as rwuio() (with an indication they detected the revoke flag). The kernel entry routine would then call the new f_fileops routine (they were reset when the flag was set) (Future calls will always call the f_fileops routines directly). The use of the open file table allows revocation of a tty access regardless of the state of the process (even if it is swapped). It is the single data structure which represent a processes (or several peer processes) specific reference to a tty that is not shared by future access to the tty device or by any other unrelated processes. This method eliminates "terrorist" techniques like allocating all ptys, it stops hanging background jobs from grabbing plain text passwords, etc. It also provides a mechanism for supporting whatever semantics are deemed necessary for tty operations from detached processes (e.g. do reads block or does the new routines set EOF state....). The reattachment of background jobs would use the f_data before pointing it at the inode table entry of the new tty and would replace the f_fileops back to the tty routines (clearing the revoke flag in f_flags also). ---- How to use this call for tty allocation only (protection from prior references to ttys. Done as root.) ---- a) open the master side (for pty allocation only) If this succeeds, then no other process is using the pty. This assumes the current semantics of on a single reference to the master side. b) open slave side (entry point for "real" tty allocation also) c) set exclusive use on (slave) tty. d) revoke(tty) -- where revoke() is described above e) set owner,group and access bits on (slave) tty (following current techniques like group tty etc). f) clear exclusive use on (slave) tty. g) at this point the tty is secure from prior reference attacks. Future reference can then be controlled via owner,group. No looping ioctls to check for link count. No wedged ptys. No complex utmp hacking etc... (Of course write etc still have to be fixed as dan and other have already described...) Some of this technique has already been tried on a production system. Part of the tty protection phase stuff was implemented last year on a 4.3BSD Tahoe system (I needed to stop some bozo user from stealing plaintext passwords. We cannot limit our user population, nor can we easily remove "aggressive" users from the machines). In my simple implementation I defaulted to semantics that made revoked access equate to a failed system call (similar to the results of what the old vhangup() would do. This may not be the right thing in all cases). I need to look at how this works with sessions as implemented into 4.3 Reno before saying more. Clearly I haven't implemented reattaching ttys... Keith Muller University of California kmuller@ucsd.edu (Copyright 1991 University of California)