Skip to content

CONTRACTS: Dynamic frame condition checking #6887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

remi-delmas-3000
Copy link
Collaborator

@remi-delmas-3000 remi-delmas-3000 commented May 26, 2022

Dependencies

The first 6 commits are from these other PRs (do not read)

The build fails with ubuntu 18.04 and I am not sure why, since no error is visible on the output.

Implement the functionality described below.

Dynamic frame condition checking (DFCC) for function contracts

Motivation

This new implementation proposes front-end extensions and a new dynamic method for checking assigns and frees clauses addressing all of the above:

  • we allow parametric assigns clauses, frees clauses in the contracts language,
  • we allow function calls in pre and post conditions contracts but check them for side effects when they are used
  • the checking new method is dynamic and does not require inlining the program or unwinding loops a priori,
  • the checking new method is compatible with deferred resolution of function pointers calls during symex,
  • the instrumentation hooks are written as C library,
  • code duplication for contract checking and contract replacement has been reduced,
  • all CPROVER library functions are supported,
  • checking contracts on recursive functions is now supported (by assuming preconditions and asserting post conditions on the top level call and asserting pre conditions and assuming post conditions on the recursive call)
  • the user documentation for contracts was moved to the contracts/doc folder (doxygen)
  • the developer documentation/architecture documentat is found contracts/doc folder (doxygen)

Current limitations:

  • loop contracts are not yet ported to the new framework
  • the deferred function pointer resolution is not yet implemented, user-specified function pointer restriction is still required
  • havocing during contract replacement still relies on pointer snapshots known to affect performance
  • more work is performed by symbolic execution which can be felt on runtime
  • source locations for the instrumented code are still very partial and messy

Frame condition checking

Let’s call f_top​ the top-level function against which the contract is being checked.

We assume f_top​ is itself called from a harness function, which may call other functions besides f_top​.

Let’s consider a function f that is either f_top​ or one of the functions called (directly or indirectly) by f_top​. The goal is to verify that f writes only to memory locations that are part of the frame of the call to f_top but allowed by the contract or that were allocated after entering f_top​ and are hence not part of the frame of the call. To do that, we add ghost code to the program to build representations the following sets of memory locations as the program runs:

  • (contract_assigns, contract_frees, allocated, deallocated)
    where:
  • contract_assigns is the set of memory locations specified in the assigns clause of the contract being checked
  • contract_frees is the set of pointers specified in the frees clause of the contract being checked
  • allocated is the set objects identifiers allocated on the stack or the heap since entering f_top
  • deallocated is the set of heap objects identifiers deallocated since entering f_top​ (allows verification that an object was freed as a post-condition of the contract)

The instrumentation transforms all user-defined goto functions and library functions:

ret_t f(<parameters>);

Into functions accepting an extra parameter that is a pointer to a dynamic representation of the sets introduced above:

ret_t f(<parameters>,  __CPROVER_contracts_write_set_ptr_t write_set);

Function bodies is instrumented so that

  • when the write_set pointer is NULL no checks are performed,
  • when it is not NULL the following checks are performed
    • Assignments LHS are checked for inclusion in contract_assigns or allocated,
    • DECL objects are recorded in allocated
    • Dynamically allocated objects are recorded in allocated
    • DEAD objects or objects assigned to __CPROVER_dead_object are removed from allocated
    • dynamic objects deallocated with __CPROVER_deallocate are checked for inclusion in contract_frees or allocated, and recorded in deallocated (so that if needed we can check in post conditions that the function indeed deallocated the expected memory).
    • The current write set is propagated to all functions calls or function pointer calls.

Since the write set is dynamically updated, a same function called at different call sites can see a different write set.

Write sets are initialised from contracts descriptions and injected in function calls that are checked against contracts, or used to perform inclusion checks against write sets obtained from callers for functions that are replaced by a contract.


@codecov
Copy link

codecov bot commented May 26, 2022

Codecov Report

Base: 77.87% // Head: 77.90% // Increases project coverage by +0.03% 🎉

Coverage data is based on head (7bf890c) compared to base (3034749).
Patch coverage: 80.50% of modified lines in pull request are covered.

❗ Current head 7bf890c differs from pull request most recent head 29ef0b0. Consider uploading reports for the commit 29ef0b0 to get more accurate results

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #6887      +/-   ##
===========================================
+ Coverage    77.87%   77.90%   +0.03%     
===========================================
  Files         1576     1593      +17     
  Lines       181824   184380    +2556     
===========================================
+ Hits        141598   143649    +2051     
- Misses       40226    40731     +505     
Impacted Files Coverage Δ
src/ansi-c/ansi_c_convert_type.h 100.00% <ø> (ø)
src/ansi-c/ansi_c_internal_additions.cpp 90.12% <ø> (ø)
src/ansi-c/c_typecheck_base.h 100.00% <ø> (ø)
...ntracts/dynamic-frames/dfcc_dsl_contract_handler.h 0.00% <0.00%> (ø)
...trument/contracts/dynamic-frames/dfcc_instrument.h 0.00% <0.00%> (ø)
...ent/contracts/dynamic-frames/dfcc_spec_functions.h 0.00% <0.00%> (ø)
...o-instrument/contracts/dynamic-frames/dfcc_utils.h 0.00% <0.00%> (ø)
...o-instrument/contracts/instrument_spec_assigns.cpp 90.98% <0.00%> (-8.38%) ⬇️
...rc/goto-instrument/goto_instrument_parse_options.h 100.00% <ø> (ø)
src/util/replace_symbol.cpp 76.89% <0.00%> (-1.32%) ⬇️
... and 53 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@remi-delmas-3000 remi-delmas-3000 force-pushed the contracts-dfcc branch 4 times, most recently from 3183180 to f6e22e9 Compare July 20, 2022 21:06
@remi-delmas-3000 remi-delmas-3000 force-pushed the contracts-dfcc branch 5 times, most recently from 0190a5a to 102d2b2 Compare July 27, 2022 05:35
@remi-delmas-3000 remi-delmas-3000 force-pushed the contracts-dfcc branch 3 times, most recently from 4f373d7 to 7d98112 Compare August 12, 2022 09:06
Remi Delmas added 18 commits September 22, 2022 11:36
`__CPROVER_is_freshr` works like
`__CPROVER_is_fresh` but  takes pointers by reference
instead of by value. This willallow using the predicate
from within user-defined functions.
Only type-checking in the front-end is implemented,
support in the back-end will come with dynamic frames.
The `dfcc_utilst` class provides methods to manipulate
function symbols in the symbol table:
- create fresh symbols
- clone and rename function parameter lists,
- clone and rename function symbols,
- add parameters to functions symbols and goto functions
These transformations will help us implement dynamic frames.
`cprover_contracts.c` defines C types representing
dynamic frames:
- `__CPROVER_contract_car_t`
- `__CPROVER_contract_car_set_t`
- `__CPROVER_contract_obj_set_t`
- `__CPROVER_contract_write_set_t`

It also defines functions that build and check
frame conditions in various situations:
 - allocations/deallocations,
 - assignments,
 - array operations,
 - havoc operations,
 - assigns clause inclusion checks for
   contract replacement,
 - frees clause inclusion checks for
   contract replacement,
 - etc.

It also provides C implementations for built-in predicates:
- `__CPROVER_contracts_is_freeable` for `__CPROVER_is_freeable`
- `__CPROVER_contracts_is_freed`  for `__CPROVER_is_freed`
- `__CPROVER_contracts_is_freshr` for `__CPROVER_is_fresh` and
  `__CPROVER_is_freshr`

The class `dfcc_libraryt` provides a programmatic
interface to load and use the library types and
functions and perform some optimisations like
inlining or unwinding loops in library functions.

dfcc_libraryt fixes
- `dfcc_contract_modet` is an enum representing
  the CHECK and REPLACE modes for contracts,
- `dfcc_contract_handlert` is an abstract interface
  for generating GOTO instructions from contracts
  for CHECK or REPLACE modes.
Rewrite calls to
- `is_fresh` to `__CPROVER_contracts_is_freshr`
- `is_freshr` to `__CPROVER_contracts_is_freshr`
- `is_freeable` to `__CPROVER_contracts_is_freeable`
- `is_freed` to `__CPROVER_contracts_is_freed`
This class extends `cfg_infot` and computes locals
and dirty locals directly from a goto program's
instruction sequence. Contary to `function_cfg_infot`
it does not consider function parameters as locals.
programs with checks against a dynamic frame.
This class rewrites `__CPROVER_assignable_t`
and `__CPROVER_freeable_t` functions into
functions that accept two extra write set parameters.
Targets specified by the function are added to the
first write set.
The second write set is used to check that any other
the side effect performed by the function is allowed.
This second write set will usually be the empty write set,
or a write set that allows only writing to history variables.
The class translates the assigns and frees clauses of
a contract expressed in DSL style into goto functions
returning `__CPROVER_assignable_t` and `__CPROVER_freeable_t`
types and describing the same set of targets as the clause.

Internally it uses the `dfcc_spec_functiontst` class
to translate the functions generated from the clauses
into functions that dynamically build write sets at
runtime.

A havoc function is also generated for the assigns clause,
that havocs the locations described by the clause in
a type-directed way, taking snapshots of start addresses
from a write set instance.
The class generates a sequence of GOTO instruction from
a contract expressed in the DSL syntax.

These instructions are meant to be added to the
GOTO function that serves as a wrapper for the function
under verification or being replaced by the contract.

It first uses `dfcc_dsl_contract_functionst` to generate
goto functions from the contract's assigns and frees clauses.

In contract checking mode, it generates GOTO instructions
that assume preconditions, snapshots history variables,
creates a write set instance, call the function under verification
asserts the postconditions, and returns the call's return value.

In contract replacement mode, it generates GOTO
instructions encoding the nondeterministic abstraction
defined by the contract: assert preconditions,
create and havoc assigns clause targets, create
a nondet return value for the call, nondeterministically free
frees clause targets, assume postconditions, return
a value that satisfies the post conditions.
This class implements the abstract `dfcc_contract_handlert`
interfance for DSL-style contracts using the class
`dfcc_dsl_wrapper_programt`.
Given a `function_id`, a `contract_id`, a contract `mode`,
and a `dfcc_contract_handlert` for the contract,
this class swaps the body of the function to a function
with a mangled name, instruments the mangled function
for dynamic frame condition checking using `dfcc_instrument`.
The body of the original function is then replaced with
instructions generated by the contract handler for the
required contract  mode.
To handle recursive functions in contract checking mode,
the body of the wrapper function is generated in such a way
the first invocation results in checking the contract against the
mangled function and recursive invocations of the wrapper
behave like the contract abstraction (modelling that the contract holds).
function contracts with dynamic frames.

The class receives the proof harness function,
the map of functions and contracts to check
and the map of functions to replace with contracts.
It instruments the proof harness,  performs
swap-and-wrap operations for each of the checked
and replaced functions. It then instruments all other
functions for DFCC.
The switch expects the harness function name as parameter,
and accepts any number of `--enforce-contract` and
`--replace-call-with-contract` switches.
Updated tests
- update assigns-enforce-malloc-zero
- update assigns-local-composite
- update assigns-replace-ignored-return-value
- update assigns-replace-malloc-zero
- update assigns-slice-targets
- update assigns-slice-targets
- update assigns_enforce_01
- update assigns_enforce_02
- update assigns_enforce_03
- update assigns_enforce_04
- update assigns_enforce_05
- update assigns_enforce_06
- update assigns_enforce_07
@feliperodri
Copy link
Collaborator

Fixes #7197

@feliperodri
Copy link
Collaborator

Fixes #7198

@feliperodri
Copy link
Collaborator

Fixes #7206

@remi-delmas-3000
Copy link
Collaborator Author

Superseded by #7242

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aws Bugs or features of importance to AWS CBMC users aws-high Code Contracts Function and loop contracts dependent - do not merge enhancement new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants