Function contracts and roles¶
Visao knows what a library function does from its contract. A contract is an ACSL block that is attached to a function by its symbol name. The analyzer does not know any function by its name. It reads the contract and the roles in the contract.
Contract files¶
Contract files have the .contract extension. The format is:
# comment
@symbol read(fd, buf, count)
/*@
@ requires count == 0 || \valid(buf + (0 .. count - 1));
@ tagged "role:input", "role:writes:1";
*/
The @symbol line gives the symbol and the names of its parameters.
Visao binds the names to the parameters of the real declaration by
position, so the host header can use other names.
Visao reads contracts from these directories:
<stdlib>/c/contracts/Contracts of C and C++ library functions: the C standard library, POSIX, Linux, macOS and Windows. C, C++, Objective-C, RuC and assembly use these contracts.
<stdlib>/LANGUAGE/contracts/Contracts of the library functions of one language, for example
<stdlib>/python/contracts/or<stdlib>/java/contracts/. The directory name is the language name of the--languageoption.<stdlib>/roles/Roles of names that the frontends make for every language, for example
!throw. Visao reads only the roles from these files.
A contract applies only to the analysis of its language. The Python
open and the C open are two different functions with two
different contracts.
Visao finds the contract of a call by the name of the called function:
When the program or a library model declares the function, Visao attaches the contract to the declaration.
When the program calls a function that it does not declare, Visao attaches the contract to the called function at the first call.
The name and the arguments of a call are the ones the frontend gives the call:
A method call
x.m(a)callsmwith the argumentsx, a. The receiver is argument 0, so the contract is@symbol m(self, a).A call through an imported module keeps the module name: the Python
os.system(c)callsos.systemwith the argumentc.A Rust path keeps the last part:
File::open(p)callsopen.
Rules for the ACSL block of an entry:
Write the
requiresclauses before theensuresclauses. The ACSL grammar sets this order.Do not give a parameter the name of an ACSL keyword, for example
default.Use
p != \nullfor “not null”. In a script language, write it only for an argument that is never a number: the value\nullis 0.
An entry with only tagged clauses gives roles. Visao does not attach
it, and the call stays unknown to the value analysis.
One symbol can have entries in more than one file. Visao adds the roles
of all entries together. An entry that has ACSL clauses other than
tagged replaces the ACSL block of an earlier entry.
A function in the analyzed source can have roles too. Write the roles in the annotation of the function:
/*@ tagged "role:input"; */
int read_packet(char *buf, int len);
System state¶
A contract can read and change the state of the system the program runs on: the operating system, the CPU, the hardware and the threads. The state is a set of state variables. A state variable is a number that exists only in the analysis.
Declare a state variable in a contract file:
@state os_irq_off = 0
@balanced os_irq_off
@state os_context = 0
@entry hardirq os_context = 2, os_irq_off = 1
@state NAME = VALUEDeclares the state variable
NAME. Each function starts with the variable atVALUE.@balanced NAMEEach function must return with
NAMEat the value it had when the function started. Visao checks this at eachreturnand at the end of the function.@entry PROFILE NAME = VALUE, ...Gives the values a function starts with when the system runs it in the way
PROFILEnames, for example as an interrupt handler.
A contract uses a state variable by its name. In an ensures clause,
\old(NAME) is the value before the call:
@symbol local_irq_disable()
/*@ ensures os_irq_off == \old(os_irq_off) + 1; */
@symbol msleep(ms)
/*@ requires \report(os_irq_off == 0 && os_context == 0,
"WrongExecutionContext"); */
These roles give a function its entry profile:
role:handler:N:PROFILEThe function passed in argument
Nruns with the entry profilePROFILE. Use it on the function that registers a handler, for examplerequest_irqorsignal.role:entry:PROFILEThe function runs with the entry profile
PROFILE. Write it on an interrupt handler in the analyzed source.
A function called in a function of the program shares the state of its
caller. The program can call a function it defines. Then Visao checks
the state requirements of the library calls that this function makes
before its first branch or return.
The findings of this section:
WrongExecutionContextA call needs another state of the system.
StateNotRestoredA function returns with a balanced state variable at a value other than the one it started with.
The models of the target systems are in these directories. They apply to the C family:
<stdlib>/os/OS/contracts/The model of the target OS:
linux,macos,windowsorbaremetal.<stdlib>/os/posix/contracts/applies to Linux and macOS.<stdlib>/cpu/CPU/contracts/The model of the target CPU, for example
armorx86_64.
An entry of a system model adds its clauses to the library contract of
the same symbol. The target comes from the build. The environment
variables VISAO_MODEL_OS and VISAO_MODEL_CPU select another
model, for example to analyze kernel code with the headers of the host.
Function summaries¶
Visao analyzes each function of the program on its own. A call of a function of the program uses the contract of that function. A function without a contract gets a summary from the analysis of its body:
the range of its result, for example
\result >= 1 && \result <= 3;a result that is never null;
the change the function makes to a state variable, for example
os_irq_off == \old(os_irq_off) + 1.
Visao analyzes the called functions before their callers, so the caller
gets the summary. A function with no branch and no early return that
changes a balanced state variable is a wrapper, for example a lock
function. Visao does not report StateNotRestored for it. Its callers
get the change through the summary.
The caller uses the range of a result in the same way as the range of a
variable. This is also true for a call in a condition, for example
if (pick(a) > 10). When the range of each operand makes the condition
always true or always false, Visao reports ConditionAlwaysTrue or
ConditionAlwaysFalse. An outer condition also gives a range, for
example x < 1 is always false in the body of if (x >= 1).
Visao uses the relations between variables too: in the body of
if (x < y && y < 10) the condition x > 20 is always false. A loop
stops when its condition is false or at a break. After the loop, a
variable has one of the values it has at these points. After
for (i = 0; i < 10; i++) the condition i < 5 is always false.
After while (i < 100) { if (c) break; i++; } the condition
i > 100 is always false. A continue gives its values to the
next iteration.
In the body of a loop the ranges hold for all iterations: in the same
loop i > 20 is always false, and i == 5 gives no finding.
Calls between languages¶
A function of the program with a contract or a summary is available to
the other languages of the run under its name. A call from another
language uses that contract: Visao checks the requires clauses at the
call and uses the ensures clauses after it.
These calls get the contract:
a call through Python
ctypesorcffi, for examplelib.foo(x);a function in a Rust
extern "C"block;a Go call through cgo, for example
C.foo(x);a C# method with
DllImport;a Java
nativemethod. The C functionJava_pkg_Class_methodgives its contract to the methodmethod, without the two JNI parameters.
When the analysis of the function finds a violated ensures clause,
the contract is refuted. The callers then check its requires clauses
and do not use its ensures clauses.
Visao analyzes C, C++, Rust and Go files before the other languages of
the run, also with -j. The functions these languages export are then
known when a Python, Java or C# file calls them.
Roles¶
A role is a string in a tagged clause. It starts with role:.
Some roles have parameters. A parameter is separated by :.
Argument numbers start at 0. In a method call, argument 0 is the
receiver.
Resources¶
role:acquire:CLASSThe result is a new resource of the class
CLASS.role:acquire_on_success:CLASSThe result is a new resource of the class
CLASSonly when the call succeeds.role:release:CLASS:NThe function releases the resource in argument
N. The class tells what the release does:memory,malloc,new,new[]andgcfree memory.mutex,rwlock,spinandlockunlock.The other classes close a handle.
role:lock:CLASS:NThe function locks the lock in argument
N.role:alloc_size:NThe size of the new block is argument
N.role:alloc_size:N*Mgives the size as the product of two arguments.role:reallocates:NThe function releases the block in argument
Nand returns a new block (realloc). The facts about the old block become false.role:nullable_resultThe result can be null. A use of the result before a check is a defect.
The rules use these resource classes:
Memory:
malloc,malloc0(zeroed memory),new,new[],gc,stack,heapmem(HeapAlloc),local,global,cotask,bstr,aligned,malloca,vm, and the reference-countedcf,objcanddispatch. A release frees the memory.Locks:
mutex,rwlock,spin,lock,srw. A release unlocks.Handles: every other class, for example
FILE,fd,DIR,pipe,db,handle(a Windows handle),heap(a heap fromHeapCreate),find,module(a loaded library),map(a mapped view),key(a registry key). A release closes the handle. A handle that is not closed is a handle leak.
The class sets are in XResourceTracker::isMemoryClass,
isLockClass and isHandleClass.
Control flow¶
role:abortThe function does not return.
role:assertThe function is an assertion handler. It stops the program because an assertion failed.
role:branch_hintThe function returns its argument 0 and tells the compiler which branch is likely (
__builtin_expect).
Data from outside¶
role:inputThe function reads data from outside the program. Its result and the buffers it writes are tainted.
role:input:functionThe same, but only when the call is a function call, not a method call (Go
fmt.Scanagainstrows.Scan).role:taint_passesThe result is tainted when an argument is tainted (
atoi).role:sink:KINDThe function interprets its argument.
KINDiscommand(runs a program),command_shell(only withshell=True) orcode(evaluates code). A tainted argument is an injection.role:secret_sourceThe result is a secret (a password hash, a key).
role:secret_passesThe result is a secret when an argument is a secret.
role:copies:D:SThe function copies argument
Sinto argumentD.
Buffers and memory¶
role:writes:NThe function writes into the buffer in argument
N.role:bounded_copy:NThe function copies a bounded string into argument
Nand can leave it without a terminating null (strncpy).role:mem_copy:D:S:LThe function copies
Lbytes from argumentSto argumentD. The regions must not overlap.role:mem_move:D:S:LThe same, but the regions can overlap (
memmove).role:mem_fill:D:LThe function writes
Lbytes into argumentD.role:derefs:NThe function reads through the pointer in argument
N. A null pointer there is a defect.role:borrows:NThe function uses the block in argument
Nonly while it runs. It does not free the block or keep a pointer to it, so the caller still owns the block (strcpy,memcpy).role:size_arg:NArgument
Nis a size.role:var_param:NArgument
Nis a Pascalvaroroutparameter. The call passes the variable, and the function can write into it (TryStrToInt).role:returns_into:NThe result points into the buffer in argument
N.role:format:NThe format string of a
printf-like function is argumentN.role:format_scan:NThe format string of a
scanf-like function is argumentN.role:unbounded_format_write:NThe function writes formatted text into argument
Nwithout a size (sprintf).
Paths¶
role:path_check:NThe function checks the file in argument
N(access,stat).role:path_use:NThe function acts on the file in argument
N(open,unlink). A check before a use of the same path is a race.
Security¶
role:compares:A:BThe function compares argument
Awith argumentB.role:variable_time_compareThe comparison stops at the first difference. It leaks the position of that difference through time.
role:credentials:L:PThe function takes a login in argument
Land a password in argumentP. Use-1when the function has no login argument.role:security:KINDThe function is part of the security surface.
KINDispassword,authorization,authentication,crypto,tokenorprivilege.role:dangerousThe function cannot be used safely (
gets,tmpnam).role:banned:STANDARD:RULEA coding standard bans the function, for example
role:banned:misra-c-2012:21.6.
Findings on a call¶
The frontend rules of a language use these roles. A rule reports only the finding types that it owns, so the same name in two languages does not mix the rules.
role:report_call:TYPEA call of the function is a finding of the type
TYPE.TYPEis a report type name, for exampleEvalCall.role:report_string_arg:TYPE:NA call with a string literal in argument
Nis a finding of the typeTYPE. The function runs that string as code (setTimeout("code", n)).role:returns_minus_one_if_missingThe function returns
-1when it does not find the value (indexOf).role:deletes_filesThe command deletes files (
rm).role:blocking_waitThe method blocks the thread until the task in the receiver completes (C#
Task.Wait).role:builtinThe name is a builtin of the language. An assignment to the name hides the builtin (Python
list = []).role:existence_checkThe function tests whether a value exists. It does not use the value (PHP
isset).role:glob_mode:NArgument
Nselects the mode of the command. The modesGLOBandGLOB_RECURSEfind the files one time, at configure time (CMakefile).role:minimum_versionThe command sets the minimum version of the tool and the policies of that version (CMake
cmake_minimum_required).
Signals¶
role:async_signal_safeThe function can be called from a signal handler.
role:signal_handler:NThe function installs the signal handler in argument
N.
Containers¶
role:container:OPandrole:container:OP:NA container operation.
OPiscreate,sized(the size is argumentN),empty,grow,shrink,clear,lenorread.role:iteratorThe result is an iterator into the receiver.
role:invalidates_iteratorsThe function changes the storage of the receiver. Iterators into the receiver become invalid.
role:indexed:NArgument
Nis an index into the receiver.role:consumes_receiverThe function moves the receiver. A later use of the receiver is a defect.
Values¶
role:math:FUNCTIONThe function computes a known mathematical function, so the analyzer can compute the result of a call with known arguments.
FUNCTIONisabs,fabsorsqrt.abscomputes the absolute value of an integer argument.fabsalso computes it for a floating argument.