Why is thttpd Secure?

Design Features of the Secure Daemons

In order to assure the desired security properties, we have made several important design decisions that we would now like to detail.

Small Size

In the field of information security, it has long been recognized that writing secure software becomes far more complex as the size of the software increases. Proof of program correctness to verify even simple security properties, for example, grows almost exponentially with the number of program statements. Verifying a 100 line limited-language program for the simple security properties associated with the Bell-LaPadula model of security takes about 24 hours of CPU time on a Cray supercomputer. The source code for the NCSA W3 server in widespread use today is about 6600 lines long, so there is no computer around today that is likely to be able to verify its security (or more likely demonstrate its insecurity). Several subtle weaknesses (e.g., buffer overflows overwriting program areas) have been discovered in the last several months, and many more weaknesses are to be expected in the coming years. The size of the secure daemon is very small when compared to the size of the other daemons now being used to provide similar services. The secure http daemon contains only about 80 lines of source code. This is about 1/80th of the size of the more common server daemons. By way of reference, the part of this white paper you have read to get to this point is more than 80 lines long. So understanding the operation of the daemon is about as hard as understanding the portion of this white paper you have already read.

Confinement of the Daemon's Operating Space

After the first few instructions, the daemon operates with no more privileges than a normal non-privileged user on the server. This is accomplished by use of the Unix SetUID operating system call that sets the effective user ID of the daemon's process to that of a user specified for the purpose. We will call this user "www".

By running the daemon as user www, all of the basic operating system protection features (e.g., access control, process separation, limited input and output capabilities) that are used by millions of people every day are used to protect the server against the actions of the daemon.

By running the daemon as user www, we provide an ability to trace the activities of the daemon and differentiate those activities from all other server activities. This makes automatic detection of anomolous behavior very easy, and when combined with the other confinement properties, makes detection of security violations by the daemon very simple.

Confinement of the Daemon's File System

After the first few instructions, the daemon is confined to operate in a subpart of the normal server's file structure. This is accomplished through the Unix "Chroot" operating system call that makes the file structure appear to the daemon as being rooted in the subtree of the file structure containing the information provided for export by the daemon's service.

As a good example, many attacks against servers concentrate on getting a copy of the password file and then using a password guessing program to find a valid entry point to the server. With the confined file system provided by Chroot, there is no password file available within the file system seen by the program. In fact, if properly configured, the only information that resides in the virtual file system seen by the daemon is the information designated for release through the daemon.

Since the daemon runs with no special privileges and there are (normally) no programs or resources in the available file system area that extend privileges (i.e., no setUID programs, no devices other than /dev/null), the daemon is limited to providing the information that has been explicitly made available to users of the service and running programs explicitly made available for outside use. Except in the rarest of circumstances, no additional services are required in order to facilitate the http daemon services, and thus the daemon operates in a limited function environment.

Confinement of File Output

The daemon only writes output to one log file which is defined at compile time. In order to corrupt information, it is necessary that there be a means by which the corrupt information can travel from the attacker to the server. By limiting output to a single pre-defined activity log file, the ability to corrupt any of the files provided by the server to clients is severely limited. Unless the attacker can cause the daemon program to be modified as it operates in the server's memory, it is impossible for an attacker to modify any information on the server other than the activity log file as a result of any weakness in the daemon.

This does not mean that it is impossible for an attacker to corrupt the daemon or the server. It only means that it is impossible to do so through the daemon itself unless the attacker can cause the daemon to become corrupt as it operates on the client's request.

Confinement of Information Flow

The daemon only reads one request of fixed maximum length from one TCP channel setup by the operating system and stores it in a fixed length array for analysis and use. It also reads two values corresponding to the requesting client's host name and user name from output generated by the TCP wrappers program now in widespread use.

Since these are the only inputs to the daemon that come from a possible attacker, tracing the flow of these input through the program can guarantee us that the information provided by an attacker cannot end up anywhere it should not go and that it cannot effect the server in any but a limited number of well-defined ways.

The input buffers cannot be overrun because the read operations limit the number of input bytes to less than the buffer size. From the point where input is taken by the program through program termination, all information derived from external sources is confined to a specific set of routines and variables and it is demonstrated later in this paper that this confinement can only affect a limited set of things within the daemon. By exhaustion, it is shown that none of the things that inputs can affect can result in program misoperation.

Confinement of File Inputs

Except for the log file described earlier, the daemon only opens files in a read-only mode and only opens files if they are owned by the user www and readable by "world". This means that the daemon will not read from any source that hasn't explicitly been designated for the purpose of providing information to outside users.

Even if, by mistake, some other user on the server places information in the server area, that information cannot be read by an attacker unless the ownership of the file is explicitly given to the user www and the protection setting is made to allow read access by the world. Even then, the file cannot be written by the daemon.

To allow access to information, a user on the server's computer must set ownership to the user www, put it in a file in the special chroot area, and make it readable by the world. By default, information does not meet these constraints, and it takes a specific operation effort to make information available via the server. System defaults do not allow access.

Defense-in-Depth

If the previous information is carefully reviewed, it can be seen that there is redundancy in the protection provided by different methods used by the secure daemon. This is intentional.

Many authors use a single protection mechanism under the assumption that it is perfect and will take care of everything. They invariably find that some assumption is flawed, some software component is defective, or some unanticipated combination of events causes protection to break down. Our perspective is quite different.

We expect that if any one of these precautions were taken, the daemon would probably be moderately secure, but we don't want a moderately secure daemon. Experience tells us that as soon as one person finds a way through a moderately secure daemon they will leave a path that others will follow. We take a multitude of precautions so that if and when one fails, the others will prevent harm, give warning, and allow response to counter the detected weakness before it becomes a full-fledged vulnerability.

By way of example, consider the following scenarios:

Design Principles Summary

In summary, the secure daemon is designed in such a way that we can prove (subject to the propriety of compilers, operating system functions, and other things in the environment that the daemon depends on) that once the daemon is started, only the desired affects result.

In addition, the redundant protective features cause the daemon to behave well even under conditions where the surrounding environment has succombed to security failures or is fundamentally flawed. This makes the daemon secure even when one or more of the assumptions made about its environment break down.