Chapter 12 Formal Specification
12.2 PROPERTIES OF FORMAL SPECIFICATIONS
At first glance a formal specification looks much like a computer program, with its logical and arithmetic statements, but the notation is quite different. Figure 12-3 is an example of a formal specification. The language of this example specification is not an existing language, but it resembles the style of Ina Jo (the language used in FDM) and Special (the language used in old HDM). The notation of a formal specification language is much richer than that of a programming
language and allows you to express logical operations and relations not possible in a computer program—especially those involving set theory. The following will help clarify some of the notation used in the example:
type1: SET_OF type2 type type1 is a set with elements of type type2 var:typename identifier var is of type typename
'var value of var in new state
{var1,var2} set of elements
var1 ∪ var2 set union
var1 IN var2 TRUE if var1 is an element of set var2 exp1 | exp2 boolean OR of two expressions
Figure 12-3. Formal Specification of Security Model. The variables proc_class and file_class are arrays indexed by parameters identifying processes and files,
respectively. Each element of the two-dimensional access matrix is a set that contains
zero or more of the values "r" or "w". While all data types used in this specification are
listed, most do not need to be elaborated.
The functions in the specification are equivalent to function or procedure calls in a system; but unlike a computer program, the body or effect of the function is a nonprocedural description of the function and not an algorithm. The effect asserts what is true after the function completes, without saying how the function is implemented.
TYPES
process Process name
file File name
class Access class
mode: "r" | "w Possible modes modes: SET_OF mode A set of modes CONSTANTS
init_procs (p:process): boolean Arbitrary constants init_files (f:file): boolean used for initial state init_class: class
VARIABLES
proc_class (p:process): class Access class of process p file_class (f:file): class Access class of file f access (p:process, f:file): modes Access modes for p to f file_exists (f:file): boolean TRUE if file f exists proc_exists (p:process): boolean TRUE if process p exists cur_proc: process Current process
AXIOM Partial ordering of class
FOR_ALL (c1:class, c2:class, c3:class) (cl >= c1)
& (IF c1 >= c2 & c2 >= c1 THEN cl = c2) & (IF cl >= c2 & c2 >= c3 THEN cl >= c3) INITIAL
proc_exists = init_procs & file_exists = init_files & (FOR_ALL (p:process, f:file)
SUCH-THAT (proc_exists(p) & file_exists(f)) (proc_class (p) = init_class) &
(file_class (f) = init_class) & (access (p, f) = {"r","w"}))
Figure 12-3. Formal Specification of Security Model (continued). Shown are
functions to create a file, give a single access mode, and rescind an access mode.
The statements in the functions are mathematical expressions and should not be read as if they were assignment statements in a computer program. For example, the two statements
'var = var + var2 var2 = 'var - var
are equivalent expressions, stating a relationship between the old and new values of var and the
old value of var2. The two statements
('var2 = 'var + 5) & ('var = 3) ('var = 3) & ('var2 = 8)
are also equivalent. No order of evaluation is implied by an ordering of expressions.
/* Create file f with access class c */ FUNCTION create_file (f:file, c:class)
IF NOT file_exists (f) File must not already exist
THEN 'file_exists (f) Make it exist
& 'file_class (f) = c Set its access class
& FOR_ALL p:process SUCH_THAT proc_exists(p)
'access (p, f) = NULL Give nobody access
/* Give process p access mode m to file f */ FUNCTION give_access (p:process, f:file, m:mode)
IF (proc_exists (p) & file_exists (f)) Process and file must exist & ( (m = "r" & Mode requested is r and
proc_class (p) >= file_class (f)) file is readable or.. |(m = "w" & Mode requested is w and
file_class(f) >= proc_class(p) file is writable
THEN 'access (p, f) = access (p, f) U {m} Add mode to access rights
/* Rescind process p access mode m to file f */ FUNCTION rescind_access (p:process, f:file, m:mode)
IF (proc_exists (p) & file_exists (f)) Process and file must exist & (m IN access (p, f)
It is easy to write an expression that cannot be true:
'var = 3 & 'var = 4
If this is the sole expression in the effect of a function, the function is attempting to force the new value of var to two different values, rendering the function inconsistent and any proof of the
specification in valid. Nonetheless, although it is useless to do so, there is no harm in writing a false expression as a condition, as in:
IF 'var = 3 & 'var = 4 THEN . . .
Effects of functions state what must be true after a function is invoked; consequently, if the effect of a function can never be true, the function is inconsistent. For example, assume a function has the following statement as its sole effect:
IF a = b THEN var = 6 ELSE 'var = 7
This says that old value of var is 6 when a = b . If a is not equal to b, the new value of var is set
to 7. Because an effect of a function must always be true, this function can be inconsistent if it can be called when a = b and var does not equal 6. Placing undue constraints on the old values
of variables is dangerous unless the specification shows that the function is not called under circumstances where the effect cannot be true. Some languages allow you to specify preconditions that state when the function can be called.
This last example shows that determining the inconsistency of a function depends on other functions of the specification. In general, it is meaningless to write an effect that constrains the old value of a variable to a specific value or values unless you can guarantee that the constraint will always be true. In general there must be some way to force an effect to evaluate to true through assignments of values to variables in a new state.
A specification may be nondeterministic in several ways:
'var > 3
'var = 3 | 'var = 4 'var1 = 'var2
The first statement says that the new value of var is greater than 3. A function with such a
statement is nondeterministic unless another statement in the function further constrains var .
The second statement allows var to have one of two possible values. The last statement says that
the new values of two variables are equal. It is nondeterministic if no other statement in the same function specifies a value for one of them, but it is inconsistent if the function constrains the new values to be different.
The ability to make nondeterministic statements is of great benefit when you are writing formal specifications, because it allows you to say what is allowed without constraining the implementation and without forcing you to include unnecessary detail. One of the common forms for a function in a secure system is as follows:
if security checks fail
then return “security error”
else (perform function or return “other error”)
This effect prevents the function from being performed if the security checks fail, but it does not specify under what other conditions it may not be performed. The nondeterministic else clause
allows for optional completion of the function under conditions not specified. Because a function that has no effect when it is invoked is as secure as it would be if it never were invoked, this function is just as secure as if it had been written without the or return "other error" clause or if
it had been written with a detailed description of the conditions that cause "other error". Since detailed descriptions just add clutter to a specification and do nothing to help prove the security of the functions, the detail can be omitted.
One final important convention applies to our specification: if a variable or array element is not specifically shown to change in a function or in one branch of a conditional (if statement), it
is assumed to remain unchanged; and when we specify a new value for one element of an array, the other elements must not change. Though this no-change convention may seem intuitively obvious (it is clearly the convention used in programming languages), most verification systems must be told explicitly when variables do not change. This is because verification systems take the mathematical view that a function specification is like a theorem that must be proved true under the assumption that variables not specifically constrained can take on any possible values. This mathematical view of specifications, which conflicts with the programming view, is a source of some frustration and requires the user to insert numerous no-change statements throughout the specification. These no-change statements may increase the size of a specification by as much as 50 percent. In general, developing a tool that views a specification as resembling a program (and so figures out when variables mentioned in one part of a function do not change in another part) is a difficult theoretical problem.