Implementing Complete Mediation

Lecturer: Professor Fred B. Schneider

Lecture notes by Borislav Deianov


Recall, whether or not a system is secure is something that is relative to a security policy. The class of security policies that we have so far been discussing---Access Control Policies---involves a Complete Mediation mechanism and an Access Control Matrix. Last lecture covered one embodiment: JDK 1.2. Today we look at implementing this picture for a more general setting. Specifically, we discuss how to implement Complete Mediation. Subsequent lectures will discuss how to store the Access Control Matrix.

A reference monitor is a piece of software that checks every reference made by subjects to objects. (Note, a reference monitor is not the only way to implement Complete Mediation. You could also analyze the program before it is executed with an eye toward certifying that all accesses will be consistent with some stated access control policy.) As we discuss different reference monitor implementations, we will need some basis for comparing them. The basis we employ is simple: Complicated things are more likely to be flawed, hence to contain vulnerabilities. Computer Security researchers capture this notion by talking about the trusted computing base (TCB) for a system. The TCB comprises all of the (software and hardware) components that must function correctly for the system to implement its security policy. Thus, if any component in the TCB is compromised, then so is the system's security. By having the TCB be small, it is less likely to contain vulnerabilities, because it will be easier to understand, test, and analyze. Size and complexity of a reference monitor implementation is therefore a reasonable metric of quality.

Some system developers have structured operating systems in terms of a security kernel, a very small TCB on top of which the rest of the OS sits. This approach however has never caught on, probably because of the higher performance costs it entails. Services located in the kernel are often faster, and minimizing operating system overhead is perceived as an important goal. So, today, UNIX has a very large kenel as does Windows NT. The TCB's for these systems are far too large for anyone to have confidence that they are trustworthy.

How do we write a reference monitor? We discuss three approaches:

Approach 1. Use an interpreter. The target program does not execute directly on the underlying hardware but instead is interpreted by another program. Every target program instruction is thus executed only after it has been checked and found not to be violating the security policy being enforced.

This architecture has some implications:

We see the need to compromise some of the expressiveness for speed...

Approach 2. Use a wrapper. A wrapper is an environment that intercepts (and interprets or redirects) only some of the instruction issued by the target program. Thus, target execution is potentially faster because the wrapper-overhead is not seen for all instructions executed by that target.

Certain security policies cannot be implemented with wrappers, because a wrapper can only restrict those events that it sees. Security policies that involve target instructions not intercepted by the wrapper cannot be enforced by the wrapper. The performance improvement of this implementation approach depends on identifying instructions that should be intercepted. We might distinquish those instructions by looking at their opcodes (e.g. a call to READ is caught, ADD is not) or by looking at their operands (e.g. normal memory accesses are not caught but READ/WRITE to memory-mapped I/O is).

Approach 3 - Exploit hardware to intercept relevant instructions.

Today's processor architectures are designed based on some assumptions about processes:

In effect, this architecture allows a piece of software -- the operating system -- bo be a wrapper. Executing an SVC instruction forces activation of a wrapper routine; all other instructions are executed directly by the hardware. And we now have complete mediation on a subset of the instructions and partial mediation on the others (e.g check that the operands fall in allowed memory regions). As with all wrappers, the limitation of the architecture is is that some policies (e.g. "students are allowed to lower but not raise their grades") cannot be implemented. Specifically, only those polices that involve SVC instructions --- policies involving services supported by the operating system --- can be enforced.

For example, UNIX enforces a security policy on files. In UNIX, we can restrict/allow read/write access to files but cannot enforce a policy on reading/writing shared variables. The reason is that the instructions for manipulating shared variables are not implemented by the operating system.

Taking a step back, the architecture of the system we have been describing is as follows: Use memory protection to restrict processes from accessing memory belonging to other processes and to protect the integrity of the memory used by the OS. State associated with no process is accessed by processes indirectly, through the routines that are called using SVC.

Note, we can achieve the same effect by using operands of instructions instead of the opcode. We would need a way to invoke the OS for certain operands and not others. This can be done by using a memory architecture. Specifically, segmentation can be set up to cause a trap whenever a process tries to access a particular segment. This method is quite powerful, because by supporting very small segments, we could then associate a different policy with each method or procedure call (if segments are small). The Multics system was built this way.