Skip to content

Commit bd59ad3

Browse files
authored
Merge pull request #6385 from padhi-aws-forks/free-dead-fix
Fix invalidation of CARs on free and DEAD instructions
2 parents d3ee1ab + 998d141 commit bd59ad3

File tree

6 files changed

+237
-21
lines changed

6 files changed

+237
-21
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
int foo(int *x, int **p) __CPROVER_assigns(*x, *p, **p)
5+
{
6+
if(p && *p)
7+
**p = 0;
8+
9+
{
10+
int y;
11+
y = 1;
12+
*x = 0;
13+
14+
if(nondet_bool())
15+
return 0;
16+
17+
if(p)
18+
*p = &y;
19+
20+
// y goes DEAD here, unconditionally
21+
}
22+
23+
if(p && *p)
24+
**p = 0;
25+
26+
int a = 1;
27+
28+
if(x == NULL)
29+
{
30+
return 1;
31+
// a goes DEAD here, conditionally
32+
}
33+
34+
int *z = malloc(100 * sizeof(int));
35+
int *w = NULL;
36+
37+
if(z)
38+
{
39+
w = &(z[10]);
40+
*w = 0;
41+
42+
int *q = &(z[20]);
43+
*q = 1;
44+
// q goes DEAD here, unconditionally
45+
}
46+
47+
free(z);
48+
49+
z = malloc(sizeof(int));
50+
if(nondet_bool())
51+
{
52+
free(z);
53+
// here z is deallocated, conditionally
54+
}
55+
56+
*x = 1;
57+
x = 0;
58+
}
59+
60+
int main()
61+
{
62+
int z;
63+
int *x = malloc(sizeof(int));
64+
foo(&z, &x);
65+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo _ --malloc-may-fail --malloc-fail-null --pointer-primitive-check
4+
^\[foo.\d+\] line \d+ Check that \*\(\*p\) is assignable: SUCCESS$
5+
^\[foo.\d+\] line \d+ Check that \*\(\*p\) is assignable: FAILURE$
6+
^\[foo.\d+\] line \d+ Check that \*p is assignable: SUCCESS$
7+
^\[foo.\d+\] line \d+ Check that \*q is assignable: SUCCESS$
8+
^\[foo.\d+\] line \d+ Check that \*w is assignable: SUCCESS$
9+
^\[foo.\d+\] line \d+ Check that \*x is assignable: SUCCESS$
10+
^\[foo.\d+\] line \d+ Check that a is assignable: SUCCESS$
11+
^\[foo.\d+\] line \d+ Check that q is assignable: SUCCESS$
12+
^\[foo.\d+\] line \d+ Check that w is assignable: SUCCESS$
13+
^\[foo.\d+\] line \d+ Check that x is assignable: SUCCESS$
14+
^\[foo.\d+\] line \d+ Check that y is assignable: SUCCESS$
15+
^\[foo.\d+\] line \d+ Check that z is assignable: SUCCESS$
16+
^\[foo.\d+\] line \d+ Check that POINTER_OBJECT\(\(void \*\)z\) is assignable: SUCCESS$
17+
^EXIT=10$
18+
^SIGNAL=0$
19+
^VERIFICATION FAILED$
20+
--
21+
^\[foo.\d+\] line \d+ Check that \*p is assignable: FAILURE$
22+
^\[foo.\d+\] line \d+ Check that \*q is assignable: FAILURE$
23+
^\[foo.\d+\] line \d+ Check that \*w is assignable: FAILURE$
24+
^\[foo.\d+\] line \d+ Check that \*x is assignable: FAILURE$
25+
^\[foo.\d+\] line \d+ Check that a is assignable: FAILURE$
26+
^\[foo.\d+\] line \d+ Check that q is assignable: FAILURE$
27+
^\[foo.\d+\] line \d+ Check that w is assignable: FAILURE$
28+
^\[foo.\d+\] line \d+ Check that x is assignable: FAILURE$
29+
^\[foo.\d+\] line \d+ Check that y is assignable: FAILURE$
30+
^\[foo.\d+\] line \d+ Check that z is assignable: FAILURE$
31+
^\[foo.\d+\] line \d+ Check that POINTER_OBJECT\(\(void \*\)z\) is assignable: FAILURE$
32+
^.*tmp_cc\$\d+.*: FAILURE$
33+
--
34+
Checks that invalidated CARs are correctly tracked on `free` and `DEAD`.
35+
36+
Since several variables are assigned multiple times,
37+
we rule out all failures using the negative regex matches above.
38+
39+
We also check (using positive regex matches above) that
40+
`**p` should be assignable in one case and should not be assignable in the other.
41+
42+
Finally, we check that there should be no "internal" assertion failures
43+
on `tmp_cc` variables used to track CARs.

regression/contracts/test_aliasing_ensure_indirect/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
main.c
3-
--enforce-contract foo --enforce-contract bar
3+
--enforce-contract foo
44
^EXIT=0$
55
^SIGNAL=0$
66
\[postcondition.\d+\] file main.c line \d+ Check ensures clause: SUCCESS

src/goto-instrument/contracts/assigns.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ assigns_clauset::conditional_address_ranget::generate_snapshot_instructions()
8686
instructions.add(goto_programt::make_decl(lower_bound_address_var, location));
8787
instructions.add(goto_programt::make_decl(upper_bound_address_var, location));
8888

89+
instructions.add(goto_programt::make_assignment(
90+
lower_bound_address_var,
91+
null_pointer_exprt{to_pointer_type(slice.first.type())},
92+
location));
93+
instructions.add(goto_programt::make_assignment(
94+
upper_bound_address_var,
95+
null_pointer_exprt{to_pointer_type(slice.first.type())},
96+
location));
97+
8998
goto_programt skip_program;
9099
const auto skip_target = skip_program.add(goto_programt::make_skip(location));
91100

src/goto-instrument/contracts/contracts.cpp

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Date: February 2016
3737
#include <util/pointer_predicates.h>
3838
#include <util/replace_symbol.h>
3939

40-
#include "assigns.h"
4140
#include "memory_predicates.h"
4241
#include "utils.h"
4342

@@ -637,7 +636,7 @@ void code_contractst::instrument_assign_statement(
637636
"The first instruction of instrument_assign_statement should always be"
638637
" an assignment");
639638

640-
add_containment_check(
639+
add_inclusion_check(
641640
program, assigns_clause, instruction_it, instruction_it->assign_lhs());
642641
}
643642

@@ -669,21 +668,98 @@ void code_contractst::instrument_call_statement(
669668
auto snapshot_instructions = car->generate_snapshot_instructions();
670669
insert_before_swap_and_advance(
671670
body, instruction_it, snapshot_instructions);
672-
// since CAR was inserted *after* the malloc,
671+
// since CAR was inserted *after* the malloc call,
673672
// move the instruction pointer backward,
674673
// because the caller increments it in a `for` loop
675674
instruction_it--;
676675
}
677-
return;
678676
}
679677
else if(callee_name == "free")
680678
{
681-
add_containment_check(
679+
const auto free_car = add_inclusion_check(
682680
body,
683681
assigns,
684682
instruction_it,
685683
pointer_object(instruction_it->call_arguments().front()));
686-
return;
684+
685+
// skip all invalidation business if we're freeing invalid memory
686+
goto_programt alias_checking_instructions, skip_program;
687+
alias_checking_instructions.add(goto_programt::make_goto(
688+
skip_program.add(
689+
goto_programt::make_skip(instruction_it->source_location)),
690+
not_exprt{free_car.validity_condition_var},
691+
instruction_it->source_location));
692+
693+
// Since the argument to free may be an "alias" (but not identical)
694+
// to existing CARs' source_expr, structural equality wouldn't work.
695+
// Moreover, multiple CARs in the writeset might be aliased to the
696+
// same underlying object.
697+
// So, we first find the corresponding CAR using `same_object` checks.
698+
std::unordered_set<exprt, irep_hash> write_set_validity_addrs;
699+
for(const auto &w_car : assigns.get_write_set())
700+
{
701+
const auto object_validity_var_addr =
702+
new_tmp_symbol(
703+
pointer_type(bool_typet{}),
704+
instruction_it->source_location,
705+
symbol_table.lookup_ref(function).mode,
706+
symbol_table)
707+
.symbol_expr();
708+
write_set_validity_addrs.insert(object_validity_var_addr);
709+
710+
alias_checking_instructions.add(goto_programt::make_decl(
711+
object_validity_var_addr, instruction_it->source_location));
712+
// if the CAR was defined on the same_object as the one being `free`d,
713+
// record its validity variable's address, otherwise record NULL
714+
alias_checking_instructions.add(goto_programt::make_assignment(
715+
object_validity_var_addr,
716+
if_exprt{
717+
and_exprt{
718+
w_car.validity_condition_var,
719+
same_object(
720+
free_car.lower_bound_address_var, w_car.lower_bound_address_var)},
721+
address_of_exprt{w_car.validity_condition_var},
722+
null_pointer_exprt{to_pointer_type(object_validity_var_addr.type())}},
723+
instruction_it->source_location));
724+
}
725+
726+
alias_checking_instructions.destructive_append(skip_program);
727+
insert_before_swap_and_advance(
728+
body, instruction_it, alias_checking_instructions);
729+
730+
// move past the call and then insert the invalidation instructions
731+
instruction_it++;
732+
733+
// skip all invalidation business if we're freeing invalid memory
734+
goto_programt invalidation_instructions;
735+
skip_program.clear();
736+
invalidation_instructions.add(goto_programt::make_goto(
737+
skip_program.add(
738+
goto_programt::make_skip(instruction_it->source_location)),
739+
not_exprt{free_car.validity_condition_var},
740+
instruction_it->source_location));
741+
742+
// invalidate all recorded CAR validity variables
743+
for(const auto &w_car_validity_addr : write_set_validity_addrs)
744+
{
745+
goto_programt w_car_skip_program;
746+
invalidation_instructions.add(goto_programt::make_goto(
747+
w_car_skip_program.add(
748+
goto_programt::make_skip(instruction_it->source_location)),
749+
null_pointer(w_car_validity_addr),
750+
instruction_it->source_location));
751+
invalidation_instructions.add(goto_programt::make_assignment(
752+
dereference_exprt{w_car_validity_addr},
753+
false_exprt{},
754+
instruction_it->source_location));
755+
invalidation_instructions.destructive_append(w_car_skip_program);
756+
}
757+
758+
invalidation_instructions.destructive_append(skip_program);
759+
insert_before_swap_and_advance(
760+
body, instruction_it, invalidation_instructions);
761+
762+
instruction_it--;
687763
}
688764
}
689765

@@ -823,9 +899,9 @@ void code_contractst::check_frame_conditions(
823899
auto snapshot_instructions = car->generate_snapshot_instructions();
824900
insert_before_swap_and_advance(
825901
body, instruction_it, snapshot_instructions);
826-
// since CAR was inserted *after* the DECL,
902+
// since CAR was inserted *after* the DECL instruction,
827903
// move the instruction pointer backward,
828-
// because the caller increments it in a `for` loop
904+
// because the `for` loop takes care of the increment
829905
instruction_it--;
830906
}
831907
else if(instruction_it->is_assign())
@@ -838,20 +914,42 @@ void code_contractst::check_frame_conditions(
838914
}
839915
else if(instruction_it->is_dead())
840916
{
841-
assigns.remove_from_write_set(instruction_it->dead_symbol());
917+
const auto &symbol = instruction_it->dead_symbol();
918+
// CAR equality and hash are defined on source_expr alone,
919+
// therefore this temporary CAR should be "found"
920+
const auto &symbol_car = assigns.get_write_set().find(
921+
assigns_clauset::conditional_address_ranget{assigns, symbol});
922+
if(symbol_car != assigns.get_write_set().end())
923+
{
924+
instruction_it++;
925+
auto invalidation_assignment = goto_programt::make_assignment(
926+
symbol_car->validity_condition_var,
927+
false_exprt{},
928+
instruction_it->source_location);
929+
// note that instruction_it is not advanced by this call,
930+
// so no need to move it backwards
931+
body.insert_before_swap(instruction_it, invalidation_assignment);
932+
}
933+
else
934+
{
935+
throw incorrect_goto_program_exceptiont(
936+
"Found a `DEAD` variable without corresponding `DECL`!",
937+
instruction_it->source_location);
938+
}
842939
}
843940
else if(
844941
instruction_it->is_other() &&
845942
instruction_it->get_other().get_statement() == ID_havoc_object)
846943
{
847944
const exprt &havoc_argument = dereference_exprt(
848945
to_typecast_expr(instruction_it->get_other().operands().front()).op());
849-
add_containment_check(body, assigns, instruction_it, havoc_argument);
946+
add_inclusion_check(body, assigns, instruction_it, havoc_argument);
850947
}
851948
}
852949
}
853950

854-
void code_contractst::add_containment_check(
951+
const assigns_clauset::conditional_address_ranget
952+
code_contractst::add_inclusion_check(
855953
goto_programt &program,
856954
const assigns_clauset &assigns,
857955
goto_programt::instructionst::iterator &instruction_it,
@@ -868,6 +966,8 @@ void code_contractst::add_containment_check(
868966
assertion.instructions.back().source_location.set_comment(
869967
"Check that " + from_expr(ns, expr.id(), expr) + " is assignable");
870968
insert_before_swap_and_advance(program, instruction_it, assertion);
969+
970+
return car;
871971
}
872972

873973
bool code_contractst::enforce_contract(const irep_idt &function)

src/goto-instrument/contracts/contracts.h

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Date: February 2016
3030
#include <util/namespace.h>
3131
#include <util/pointer_expr.h>
3232

33+
#include "assigns.h"
34+
3335
#define FLAG_LOOP_CONTRACTS "apply-loop-contracts"
3436
#define HELP_LOOP_CONTRACTS \
3537
" --apply-loop-contracts\n" \
@@ -44,7 +46,6 @@ Date: February 2016
4446
#define HELP_ENFORCE_CONTRACT \
4547
" --enforce-contract <fun> wrap fun with an assertion of its contract\n"
4648

47-
class assigns_clauset;
4849
class local_may_aliast;
4950
class replace_symbolt;
5051

@@ -134,7 +135,7 @@ class code_contractst
134135

135136
/// Inserts an assertion into the goto program to ensure that
136137
/// an expression is within the assignable memory frame.
137-
void add_containment_check(
138+
const assigns_clauset::conditional_address_ranget add_inclusion_check(
138139
goto_programt &,
139140
const assigns_clauset &,
140141
goto_programt::instructionst::iterator &,
@@ -144,19 +145,17 @@ class code_contractst
144145
/// a goto statement that jumps back.
145146
bool check_for_looped_mallocs(const goto_programt &program);
146147

147-
/// Inserts an assertion statement into program before the assignment
148+
/// Inserts an assertion into program immediately before the assignment
148149
/// instruction_it, to ensure that the left-hand-side of the assignment
149-
/// aliases some expression in original_references, unless it is contained
150-
/// in freely assignable set.
150+
/// is "included" in the (conditional address ranges in the) write set.
151151
void instrument_assign_statement(
152152
goto_programt::instructionst::iterator &,
153153
goto_programt &,
154154
assigns_clauset &);
155155

156-
/// Inserts an assertion statement into program before the function call at
157-
/// ins_it, to ensure that any memory which may be written by the call is
158-
/// aliased by some expression in assigns_references, unless it is contained
159-
/// in freely assignable set.
156+
/// Inserts an assertion into program immediately before the function call at
157+
/// instruction_it, to ensure that all memory locations written to by the
158+
// callee are "included" in the (conditional address ranges in the) write set.
160159
void instrument_call_statement(
161160
goto_programt::instructionst::iterator &,
162161
const irep_idt &,

0 commit comments

Comments
 (0)