3 The Specification Language
3.1 Tool usage
The specification language is processed by a command-line tool that takes as input a specification and a list of C source files. A single instrumented C source file is created that combines the input sources and ensures that they satisfy the properties described in the specification. An instrumented.pred file containing hint predicates for BLAST is also generated. For example, running
spec.opt myspec.spc myfile.c
will produce instrumented.c and instrumented.pred in the current directory. You can then feed this file into BLAST for verification. There is no need to tell BLAST the error label, since the generated file uses the default label. For example, you could check the output by running
pblast.opt -bddcov -nofp -predH 6 -block -pred instrumented.pred instrumented.c
There are other ways to invoke spec.opt. Running
spec.opt myspec.spc myfile1.c myfile2.c myfile3.c
merges all of the specified C sources into a checkable instrumented.c.
spec.opt myspec.spc
merges all C sources in the current directory (except instrumented.c, if it exists) into a checkable instrumented.c.
3.2 Example specification files
3.2.1 A global lock
#include <locking_functions.h>
global int locked = 0;
event {
pattern { $? = init(); }
action { locked = 0; }
}
event {
pattern { $? = lock(); }
guard { locked == 0 }
action { locked = 1; }
}
event {
pattern { $? = unlock(); }
guard { locked == 1 }
action { locked = 0; }
}
This specification models correct usage of abstract global locking functions. A global variable is created to track the status of the lock. Simple events match calls to the relevant functions. The event for init initializes the global variable. The other two events ensure that the lock is in the right state before making a function call. When these checks succeed, the global variable is updated and execution proceeds. When they fail, an error is signalled.
Pattern matching is performed in an intermediate language where code is broken down into sequences of function calls and assignments. The $?'s above match either a variable to which the result of a function call is assigned or the absence of such an assignment, thus making the patterns cover all possible calls to the functions.
3.2.2 Simplified seteuid and system
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <stdlib.h>
global int __E__ = 0;
event {
pattern { $? = seteuid($1); }
action { __E__ = $1; }
}
event {
pattern { $? = system($?); }
guard { __E__ != 0 }
}
This specification models the requirement that a setuid program should not call the system function until it has changed the effective uid to a nonzero value. The $1 in the seteuid patterns will match any parameter, including the result of a complicated series of function calls. Here $? is used as a function parameter to match all remaining actual parameters.
3.2.3 X11 parameter consistency checking
For the sake of this example, we consider types and functions similar to those found in an X11 windowing system API:
typedef struct context *Context;
typedef struct image *Image;
typedef struct display *Display;
Display newDisplay(void);
Context genContext(Display);
Image genImage(Display, int);
void putText(Display, Context, Image);
We now define a specification file to verify the property that the Context and Image passed to putText both belong to the Display that is passed.
#include "x11.h"
shadow Image {
Display display = 0;
}
shadow Context {
Display display = 0;
}
event {
after
pattern { $1 = genContext($2); }
action { $1->display = $2; }
}
event {
after
pattern { $1 = genImage($2, $3); }
action { $1->display = $2; }
}
event {
pattern { $? = putText($1, $2, $3); }
guard { $2->display == $3->display && $2->display == $1 }
}
3.3 Informal description of syntax
A specification (.spc) file consists of a sequence of the following kinds of directives.
These are verbatim C-style #include directives. You should include the necessary header files to support all of the code contained in the specification. For example, functions used should be prototyped in some header file that is included.
3.3.2 Global variables
These are C-style definitions of single variables with initializers, prefaced by the keyword global. For example, global int flag = 10;. Each directive creates a global variable to which the other parts of the specification may refer.
3.3.3 Shadowed types
It is possible to replace ``abstract types'' with structures storing information pertinent to properties to be checked. Here an abstract type is a type used in the code to be checked in such a way that it could be replaced by any other type without creating type errors. For example, a type that has values used as parameters to arithmetic operators or that have struct members projected from them is not abstract. Abstract types will generally arise when dealing with libraries whose source is not available or that you choose to treat as ``black boxes.''
A type is shadowed by a directive consisting of the keyword shadow followed by the name of the type to be shadowed and then a C-style struct definition consisting of a set of field definitions inside braces. The difference from C field definitions is that each field must have a starting value defined in the same manner in which you would define an initial value for a global variable. Note: The initializers are not used in the current implementation.
Events are used to change global state and verify properties based on the execution of a C program. An event directive consists of the keyword event followed by a sequence of sub-directives within braces.
pattern
Patterns specify which possible program statements activate an event. Following the pattern keyword is a sequence of C statements enclosed in braces. These statements may have pattern variables in some positions where expressions belong. A pattern variable is the $ character followed by a positive integer. An event will be activated for any sequence of statements that matches the pattern sequence for that event, with pattern variables matching any expressions in the actual code. Currently, the same pattern variable may only appear multiple times in a single pattern to match the same C variable used in multiple places.
Patterns may also contain an additional special sequence, $?. In most positions, this sequence acts just like a pattern variable, except that matching expressions are not bound in guards, actions, or repairs. It has two additional special functions: A pattern like $? = function_call(some, args); matches a function call matching the given function call pattern, regardless of whether or not the result is saved in a variable, discarding the destination variable if it is present. $? may be given as the last actual parameter in a function call to match all remaining parameters, zero or more.
Patterns are only matched against straight-line code within basic blocks. Both patterns and C source files are compiled to the Cil intermediate language before matching. In this form, the only valid statements are (1) assignments of side effect-free expressions to variables and (2) function calls, optionally saving the return value to variable.
guard, action, and repair
The guard directive is followed by a C expression (possibly with pattern variables) inside braces. action and repair are followed by sequences of C statements (possibly with pattern variables) inside braces.
These directives specify the checks to be made and actions to be taken at certain points during execution, relative to a match of a given pattern. If the guard expression is true with the matching expressions substituted for corresponding pattern variables, then the specified action code is run with the same pattern variable substitutions. If the guard expression is false and a repair has been specified, then those instructions are run with substitutions. If the guard is false and no repair is specified, then an error is signalled by calling the __error__ function. Actions and repairs may also call the __error__ function manually.
These directives are all optional. The default guard is an always-true expression. The default action is empty, and omitting repair causes an error to be signalled when the guard is false. When an event is meant to update global state without verifying a program invariant, it is helpful to specify an empty repair to avoid signalling an error based on conditions used to determine how to change the state.
before and after
These directives take no additional parameters and specify whether to check the guard and perform the appropriate action, repair, or __error__ call before or after the execution of a matching sequence of statements, respectively. If neither directive is given, then before is taken to be present implicitly.