4. Statically Checking Confidentiality of Low-Level Operating-System Code
4.3. Typing a Size-Aligned Virtual-Memory Read
Sabelfeld [Sab01a], Mantel et al. [SM02], O Neill et al. [OCsC06] and Russo et al. [RS06] follow the same principle approach to check programs that invoke a certain functionality of the underlying operating system. Interactions with the underlying hardware are not considered, however, the same principle approach could also be applied to hardware side effects. It works as follows:
1. The semantics of the programming language is extended with a formal model of the OS functionality to consider;
2. Specialized typing rules are developed to check the information flows that this OS func- tionality contains; and
3. The specialized typing rules are proven sound against the extended semantics.
In the following, I demonstrate the complexity of this approach with the help of a simple, size- aligned, virtual-memory read operation.
In a microkernel-based system, programs (and typically also the kernel) run in a paged proces- sor mode. A read of the variable a therefore involves an address translation of the variable’s
virtual addressvato the physical addresspa. During this address translation, the MMU checks
permission bits in the involved page-table entries and updates the accessed bits of these entries. To formalize this hardware side effect, we have to model page-table entries and their accessed bits. In addition, we have to propagate accessed bits to all virtual aliases of these page-table entries. Virtual aliases of page-table entries occur because on many processor architectures the microkernel can access page tables only if they are mapped to the kernel address space. Because the approach extends an existing type system for a programming language, we have to expect an application-level memory model. In such a memory model, the propagation of accessed bits results in complex evaluation rules for virtual-memory reads, and likewise, in complex typing rules. The evaluation rule in Equation4.2 and the typing rule in Equation4.3 are such rules. They are shown here merely to illustrate the complexity of contemporary approaches. The remainder of this thesis is intelligible without these rules. Figure4.1 illustrates their basic operation.
The memory models of the standard semantics for C / C++ [Nor98,Pap98, Wal93] is a map- ping from (virtual) addresses to bytes: mem : V → Byte or, more precisely, from the virtual
addresses in address blocks of the form [o, o + sizeof < T >) to bytes. The following is
Figure 4.1.: Propagation of accessed bits to the virtual aliases of involved page-table entries.
processors [Cor09,§ 4.3 Vol. 3a].
LetEp denote the set of 4-byte aligned physical addresses at which valid page-table entries of
the programp reside. The function accessedp : Ep → bool denotes whether the accessed bits
of these page-table entries are set. The partial function ptep : V × Lvl ⇀ Epmaps each virtual
address va ∈ V to the page-table entries used in the translation of va. The partial function virt to physp : V ⇀ P maintains the mapping from virtual to physical addresses. I write short v2p for virt to physp. I assume the page-table base register (PDBR) is not changed while p
executes. For the function ptep, it holds that
(v, 2)∈ domain(ptep)⇒ (v, 1) ∈ domain(ptep) and that ptep(v, 1) = ptep(v′, 1)⇒ ⌊ v 4MB⌋ = ⌊ v′ 4MB⌋
The first property ensures that a second-level page-table entry is used for the translation of v
only if there is also a first-level page-table entry. The second property ensures that the same first-level page-table entry is used to translate addresses within the same size-aligned 4MB region. However, for the following discussion, these constraints are irrelevant.
val = load var(s, va)
s′ = s
accessed(ptep(va, i)) 7→ true
mem 7→ λv′. set bit(5, mem(v′)) if v2p(v′) = ptep(va, i)
mem(v′) otherwise
.
s read−→ (s(a) ′, val )
Equation4.2contains the evaluation rule for a memory read access. In the semantics of read(a),
we have to update the accessed bits of the page-table entries ptep(va, i), i ∈ {1, 2}. In addition,
we have to propagate this update to all addresses v that alias the updated page-table entries.
Equation4.3 shows a corresponding typing rule for a control-flow-sensitive security type sys- tem: lres = F v M(v) va ≤ v < va+ size M′ = M accessed(ptep(va, i)) 7→ lip mem 7→ λv′. mem(v ′)⊔ l ip if v2p(v′) = ptep(va, i) mem(v′) otherwise . [lip]⊢ M read(a) −→ (M′, l res) (4.3)
It returns the secrecy level lres of the read address, updates the secrecy level of the first byte
of the page-table entry, which contains the accessed bit, and it updates the secrecy levels of all addressesv′that map to this byte. The secrecy level of the byte, which contains the accessed bit,
is increased by the context secrecy levellip. Because only a single bit is modified, the updates
must be weak to not overlook information flows through the remaining bits of this byte. In comparison to this, the standard rule for memory reads is:
lres= M(v(a)) M ⊢ read(a) : lres
(4.4)
The complexity of the typing rule in Equation 4.3is immense, although the above typing rule is only for a simple size-aligned read. Reads that are not size aligned may span multiple pages. Hence, the accessed bits in two sets of page-table entries must be updated. In addition, a security typing rule for a write must propagate the written value to all virtual aliases. The rules for reads and writes to memory-mapped device registers are even more complex. A projection of this complexity to the complexity of a security type system, which is prepared to check all the low- level operating-system code of a microkernel-based system, shows the scalability limits of this approach.
A method to automatically derive sound security typing rules from a description of operating- system and hardware functionalities is therefore inevitable. Given an implementation of hard- ware side effects as Toy subprograms, the security type system for Toy is such a method.
4.4. Assumptions
Before I introduce the syntax and the semantics of Toy, let me clarify the assumptions on which the proposed information-flow analysis is based:
1. Because the individual system calls and server functionalities are checked separately, only small amounts of low-level operating-system code are checked at a time. As a result, more complex methods remain feasible for such an analysis;
2. The individual system calls and server functions terminate after a small number of atomic steps. As a consequence, all loops of the checked code terminate and the number of non-deterministic choices is bounded;
3. Due to the limited stack size, the function call depth is limited and, in particular, function calls are not recursive. When translating C++ to Toy, it is therefore feasible to inline all function calls;
4. As already mentioned in Section2.4.7on page33, I assume a correct points-to analysis and a correct loop bound analysis;
5. The to-be-checked operating-system code must not contain null-pointer dereferences (see e.g., Tlili et al. [TD08] for a static analysis of C memory-safety properties);
6. I assume jumps in the inline assembler statements of low-level operating-system code to be trivial. By this, I mean that it is trivial to replace the jumps with appropriate if- statements; and
7. In this thesis, I focus on lock-similar programs only. That is, at any given point in time, the to-be-checked programp will hold the same locks (see Section4.5.4.2on page144).