BSD tty security, part 3: How to Fix It

Keith Muller muller at sdcc10.ucsd.edu
Thu May 2 20:06:45 AEST 1991


In article <7299:Apr2510:22:2091 at kramden.acf.nyu.edu>, brnstnd at 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 at ucsd.edu

(Copyright 1991 University of California)



More information about the Comp.unix.wizards mailing list