Security Kernels
10.2 THE THREE PRINCIPLES
The reference monitor and the security kernel must satisfy three fundamental principles: • Completeness: it must be impossible to bypass.
• Isolation: it must be tamperproof.
• Verifiability: it must be shown to be properly implemented.
We shall examine each of these principles in detail, focusing on their design implications.
Realistically, no large system is likely ever to satisfy all three principles fully. The goals of the security kernel approach are to follow these principles as closely as possible—nobody would claim that a large system based on a security kernel guarantees perfect security.
10.2.1 Completeness
The principle of completeness requires that a subject not reference an object without invoking the reference monitor. It implies that all access to information must be mediated by the kernel. At first, you might think that this principle is quite reasonable, and that most operating systems today probably attempt to adhere to it. There are, however, a number of important differences between the unequivocal demand made by the completeness principle and the way operating systems are generally implemented.
An operating system usually considers the information in a system to lie in obvious places such as files, memory, and I/O buffers; and the operating system makes reasonable attempts to control access to these objects. The completeness principle is not satisfied with an ad hoc definition of the objects. Any repository of information, regardless of its size or intended use, is a potential object.
Among the additional objects where information can be stored are file names (which themselves constitute information), directories (which may include information about files), status registers, and active dynamic data maintained by the operating system and containing information about logged-in users, processes, resources consumed, and so on. You might recognize some of these items as potential covert channels (see section 7.2). The completeness principle insists that you make an explicit decision as to how the kernel will enforce access to each of these objects.
The completeness principle also places requirements on the hardware that supports a kernel- based system. If the kernel is to permit efficient execution of untrusted programs without checking each machine instruction, the hardware must ensure that the program cannot bypass access controls specified by the kernel. All references to memory, registers, and I/O devices must be checked for proper access through mechanisms such as memory management with access control (section 8.3). The kernel must be able to isolate processes from each other (section 8.2),
and to ensure that the processes cannot communicate without kernel mediation. A computer that allowed all processes unconstrained access to a common page of physical memory, for example, would not be a suitable base for a security kernel.
10.2.2 Isolation
The isolation principle—which states that the kernel must be tamperproof—is, like the completeness principle, a common-sense goal for most systems. Even the most primitive operating systems make a reasonable effort to protect themselves, at least against most accidental and casual attempts at break-in.
Enforcing the isolation principle in a practical way requires a combination of both hardware and software. The primary hardware feature that enables the kernel to prevent user programs from accessing kernel code or data is the same memory management mechanism that the kernel uses to prevent processes from accessing each other’s data. User programs must also be prevented from executing privileged instructions that the kernel uses to control the memory management mechanism. This requires some type of domain control, such as protection rings (section 8.4).
In a system equipped with the necessary hardware features, there is little chance that a user program could succeed in a direct attack on the kernel by writing the kernel’s memory, executing a privileged instruction, or modifying the kernel software. While you might be tempted to provide additional isolation by fixing the kernel code in hardware read-only memory, direct writing of the kernel software is rarely a profitable route to penetration. A far more common penetration technique involves tricking the system into running your (the penetrator’s) own program in privileged mode, thereby giving you control of the system without your having to touch either the kernel or any of its data (Karger and Schell 1974).
10.2.3 Verifiability
The principle of verifiability is addressed through relentless devotion to a number of design criteria:
• Employing state-of-the-art software engineering techniques, including structured design, modularity, information hiding, layering, abstract specification, and the use of appropriate high-order languages
• Emphasizing simplicity of functions at the kernel interface
• Minimizing the size of the kernel by excluding functions not related to security
If you keep these goals in mind while building a kernel, you will be able to convince yourself and others that the kernel is correct by using a combination of techniques:
• Code inspection • Thorough testing
It is important to understand that the kernel approach does not require the use of a specific verification technique. Your choice depends on the degree of assurance you seek. If you do not intend to devote any appreciable effort to demonstrating its correctness, however, then developing a kernel is a waste of time.
Code inspection and thorough testing are of course commonly used for most systems, yet most systems are replete with bugs. Unless we do something different with these techniques, we have little reason to expect that they will work better for a security kernel.
The primary technique that supports the verifiability argument for a security kernel is the development of a mathematical model of security. The model precisely defines security, and the functions in the model are formally proved to be consistent with this definition. The model must be structured in a way that lends itself to some kind of correspondence demonstration—that is, to an argument that the kernel implementation adheres to the model. Chapter 9 discusses security models in detail, and section 9.7 provides guidelines for demonstrating this correspondence informally.
When the reference monitor approach was first proposed, it was thought possible to build a kernel that would be small enough to be verified by exhaustive testing. Model-to-implementation correspondence in such a case would consist of testing all possible security states of the system as defined by the model—or at least enough states to satisfy the tester that a security bug would be highly unlikely, given the designer’s dedication to structuring and simplicity in the kernel’s design. But except with respect to experimental kernels having limited functions, exhaustive testing is out of the question. While testing is certainly important, few people now believe that testing alone can provide enough assurance: some additional model-to-code correspondence work is required.
For a time, people had the dream of formally verifying (mathematically proving) this correspondence by relating the bits in memory to the abstract model. A number of formal specification languages, proof techniques, and automated tools were developed to help bridge the huge gap in level of detail between model and code. Some of these techniques, already under development for other reasons, were adapted to security correspondence. It quickly became evident, however, that, (like exhaustive testing), complete formal correspondence would not be practical for a long time, and that less-than-perfect assurance would have to suffice. Formal specification and verification are discussed in chapter 12.
If you are using state-of-the-art software engineering techniques, the process of developing and proving a model, writing an informal system specification, and informally showing correspondence between the code and the model will get you at least 80 percent of the way to full assurance. Writing a formal specification will get you another 10 percent of the way there, and all the known formal verification techniques will add at most another 5 percent. While many may quarrel with these percentages, few will argue that the effort to do formal verification is only justified in the highest-security environments.
Rather than trying to develop your own security model from scratch, you should seriously consider using or building upon an existing model—either the Bell and La Padula model
discussed in section 9.5.3 or one of the handful of others discussed in section 9.3. If you decide to write a formal specification (an entirely feasible and useful exercise), use one of the specification languages discussed in chapter 12, and look at examples of secure systems specified in that language (Landwehr 1983). If you decide to go all the way and do proofs of your specification, you must obtain the automated processing tools appropriate for the verification system you have selected. It is useless to try to prove a specification by hand, without having a tool to check your proof (specific tools are listed in section 12.1).
If you want to go one step further and verify the code, stop and think again. There are no practical tools for proving a complete code correspondence of a large system or for checking the correctness of such a proof, so a proof of this kind would have to be supported by a huge manual effort. You are exceedingly unlikely to be able to do a convincing manual proof, given that the proof would have to be many times larger than the code and the specification combined. Section 12.8 gives you some feel for this process, though most of it is theory since only small examples have been carried out in practice. While people are still working on developing practical code proof techniques, your best bet for code correspondence is to carry out an informal demonstration, using systematic code review in conjunction with the formal specifications. Such an effort need not be greater than that required for any good code review process, but the use of a formal specification to guide your review will add credibility and objectivity to the process. Code correspondence can and should be used to guide system testing as well.