|
| 1 | +\ingroup module_hidden |
| 2 | +\page memory-bounds-checking Memory Bounds Checking |
| 3 | + |
| 4 | +cbmc provides means to verify that pointers are within the bounds of (statically |
| 5 | +or dynamically) allocated memory blocks. When the option `--pointer-check` is |
| 6 | +used, cbmc checks the safety of each pointer dereference in the program. |
| 7 | +Similarly, the primitive `__CPROVER_r_ok(pointer, size)` returns true when it is |
| 8 | +safe to read from the memory segment starting at `pointer` and extending for |
| 9 | +`size` bytes. `__CPROVER_w_ok(pointer, size)` indicates if writing to the given |
| 10 | +segment is safe. At present, both primitives behave the same. |
| 11 | + |
| 12 | +Each memory segment is referred to internally in cbmc via an object id. Pointers |
| 13 | +are represented as bitvectors (typically of length 32 or 64). The topmost `n` |
| 14 | +bits encode the id of the memory segment the pointer is pointing to, and the |
| 15 | +remaining bits encode the offset within the segment. The number of bits used to |
| 16 | +represent the object id can be specified via `--object-bits n`. Object offsets |
| 17 | +are signed. This allows to distinguish the case of when a pointer has been |
| 18 | +incremented too much from the case of when a pointer has been decremented too |
| 19 | +much. In the latter case, a negative value would be visible for the offset |
| 20 | +portion of a pointer in an error trace. |
| 21 | + |
| 22 | +In the following, we describe how the memory bounds checks are implemented in |
| 23 | +cbmc for dynamically allocated memory. Dynamic memory is allocated in C via the |
| 24 | +`malloc()` library function. Its model in cbmc looks as follows (see |
| 25 | +`src/ansi-c/library/stdlib.c`, details not related to memory bounds checking are |
| 26 | +left off): |
| 27 | + |
| 28 | +```C |
| 29 | +inline void *malloc(__CPROVER_size_t malloc_size) |
| 30 | +{ |
| 31 | + void *malloc_res; |
| 32 | + malloc_res = __CPROVER_allocate(malloc_size, 0); |
| 33 | + |
| 34 | + __CPROVER_bool record_malloc = __VERIFIER_nondet___CPROVER_bool(); |
| 35 | + __CPROVER_malloc_object = record_malloc ? malloc_res : __CPROVER_malloc_object; |
| 36 | + __CPROVER_malloc_size = record_malloc ? malloc_size : __CPROVER_malloc_size; |
| 37 | + |
| 38 | + return malloc_res; |
| 39 | +} |
| 40 | +``` |
| 41 | +
|
| 42 | +Both internal variables `__CPROVER_malloc_object` and `__CPROVER_malloc_size` |
| 43 | +are initialized to 0 in the `__CPROVER_initialize()` function of a goto program. |
| 44 | +The nondeterministic switch controls whether the address and size of the memory |
| 45 | +block allocated in this particular invocation of `malloc()` are recorded. |
| 46 | +
|
| 47 | +When the option `--pointer-check` is used, cbmc generates the following |
| 48 | +verification condition for each pointer dereference expression (e.g., |
| 49 | +`*pointer`): |
| 50 | +
|
| 51 | +```C |
| 52 | +__CPROVER_POINTER_OBJECT(pointer) == __CPROVER_POINTER_OBJECT(__CPROVER_malloc_object) ==> |
| 53 | +__CPROVER_POINTER_OFFSET(pointer) >= 0 && __CPROVER_POINTER_OFFSET(pointer) < __CPROVER_malloc_size |
| 54 | +``` |
| 55 | + |
| 56 | +The primitives `__CPROVER_POINTER_OBJECT()` and `__CPROVER_POINTER_OFFSET()` extract |
| 57 | +the object id, and pointer offset, respectively. Similar conditions are |
| 58 | +generated for `assert(__CPROVER_r_ok(pointer, size))` and |
| 59 | +`assert(__CPROVER_w_ok(pointer, size))`. |
| 60 | + |
| 61 | +To illustrate how the bounds checking works, consider the following example |
| 62 | +program which is unsafe: |
| 63 | + |
| 64 | + |
| 65 | +```C |
| 66 | +int main() |
| 67 | +{ |
| 68 | + char *p1 = malloc(1); |
| 69 | + p1++; |
| 70 | + char *p2 = malloc(2); |
| 71 | + |
| 72 | + *p1 = 1; |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +Here the verification condition generated for the pointer dereference should |
| 77 | +fail. In the approach outlined above it indeed can, as one can choose true for |
| 78 | +`__VERIFIER_nondet___CPROVER_bool()` in the first call |
| 79 | +to `malloc()`, and false for `__VERIFIER_nondet___CPROVER_bool()` in the second |
| 80 | +call to `malloc()`. Thus, the object address and size of the first call to |
| 81 | +`malloc()` are recorded in `__CPROVER_malloc_object` and `__CPROVER_malloc_size` |
| 82 | +respectively. Thus, the premise of the implication in the verification condition |
| 83 | +above is true, while the conclusion is false, hence the overall condition is |
| 84 | +false. |
| 85 | + |
0 commit comments