Skip to content

Commit 3024404

Browse files
authored
Merge pull request #5318 from danpoe/feature/pointer-primitive-check
Verify that pointers used in pointer primitives are either null or valid
2 parents b47f267 + d025257 commit 3024404

File tree

10 files changed

+217
-4
lines changed

10 files changed

+217
-4
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
void main()
3+
{
4+
// we need a new variable for each check, as goto_checkt removes redundant
5+
// assertions, and if we would use the same variable all pointer primitive
6+
// assertions would look the same
7+
8+
char *p1;
9+
__CPROVER_same_object(p1, p1);
10+
11+
char *p2;
12+
__CPROVER_POINTER_OFFSET(p2);
13+
14+
char *p3;
15+
__CPROVER_POINTER_OBJECT(p3);
16+
17+
char *p4;
18+
__CPROVER_OBJECT_SIZE(p4);
19+
20+
char *p5;
21+
__CPROVER_r_ok(p5, 1);
22+
23+
char *p6;
24+
__CPROVER_w_ok(p6, 1);
25+
26+
char *p7;
27+
__CPROVER_DYNAMIC_OBJECT(p7);
28+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE
2+
main.c
3+
--pointer-primitive-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
\[main.pointer_primitives.1\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
7+
\[main.pointer_primitives.2\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OFFSET.*: FAILURE
8+
\[main.pointer_primitives.3\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
9+
\[main.pointer_primitives.4\] line \d+ pointer in pointer primitive is neither null nor valid in OBJECT_SIZE.*: FAILURE
10+
\[main.pointer_primitives.5\] line \d+ pointer in pointer primitive is neither null nor valid in R_OK.*: FAILURE
11+
\[main.pointer_primitives.6\] line \d+ pointer in pointer primitive is neither null nor valid in W_OK.*: FAILURE
12+
\*\* 7 of \d+ failed
13+
--
14+
^warning: ignoring
15+
--
16+
Verifies that checks are added for all pointer primitives
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#include <stdlib.h>
2+
3+
char *p1;
4+
5+
void main()
6+
{
7+
__CPROVER_POINTER_OBJECT(p1);
8+
9+
char *p2 = NULL;
10+
__CPROVER_POINTER_OBJECT(p2);
11+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
--pointer-primitive-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
^warning: ignoring
8+
--
9+
Verifies that a null pointer does not fail the pointer primitives check
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#include <stdlib.h>
2+
3+
void main()
4+
{
5+
// uninitialized pointer
6+
char *p1;
7+
__CPROVER_POINTER_OBJECT(p1);
8+
9+
// special value of invalid pointer
10+
char *p2 = (size_t)1 << (sizeof(char *) * 8 - 8);
11+
__CPROVER_POINTER_OBJECT(p2);
12+
13+
// pointer object 123, offset 123, not pointing to valid memory
14+
char *p3 = ((size_t)123 << (sizeof(char *) * 8 - 8)) | 123;
15+
__CPROVER_POINTER_OBJECT(p3);
16+
17+
// negative offset
18+
char *p4 = malloc(1);
19+
p4 -= 1;
20+
__CPROVER_POINTER_OBJECT(p4);
21+
22+
// offset out of bounds
23+
char *p5 = malloc(10);
24+
p5 += 10;
25+
__CPROVER_POINTER_OBJECT(p5);
26+
27+
// dead
28+
char *p6;
29+
{
30+
char c;
31+
p6 = &c;
32+
}
33+
__CPROVER_POINTER_OBJECT(p6);
34+
*p6;
35+
36+
// deallocated
37+
char *p7 = malloc(1);
38+
free(p7);
39+
__CPROVER_POINTER_OBJECT(p7);
40+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
main.c
3+
--pointer-primitive-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
\[main.pointer_primitives.1\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
7+
\[main.pointer_primitives.2\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
8+
\[main.pointer_primitives.3\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
9+
\[main.pointer_primitives.4\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
10+
\[main.pointer_primitives.5\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
11+
\[main.pointer_primitives.6\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
12+
\[main.pointer_primitives.7\] line \d+ pointer in pointer primitive is neither null nor valid in POINTER_OBJECT.*: FAILURE
13+
\*\* 7 of \d+ failed
14+
--
15+
^warning: ignoring
16+
--
17+
Verifies that the pointer primitives check fails for the various forms of
18+
invalid pointers
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
void main()
3+
{
4+
char *p;
5+
__CPROVER_assume(__CPROVER_r_ok(p, 1));
6+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--pointer-primitive-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
\[main.pointer_primitives.1\] line \d+ pointer in pointer primitive is neither null nor valid in R_OK.*: FAILURE
7+
\*\* 1 of \d+ failed
8+
--
9+
^warning: ignoring
10+
--
11+
Verifies that the check fails for a primitive in an assume that uses an
12+
uninitialized pointer

src/analyses/goto_check.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ class goto_checkt
7272
enable_built_in_assertions=_options.get_bool_option("built-in-assertions");
7373
enable_assumptions=_options.get_bool_option("assumptions");
7474
error_labels=_options.get_list_option("error-label");
75+
enable_pointer_primitive_check =
76+
_options.get_bool_option("pointer-primitive-check");
7577
}
7678

7779
typedef goto_functionst::goto_functiont goto_functiont;
@@ -185,6 +187,20 @@ class goto_checkt
185187
const exprt &src_expr,
186188
const guardt &guard);
187189

190+
/// Generates VCCs to check that pointers passed to pointer primitives are
191+
/// either null or valid
192+
///
193+
/// \param expr: the pointer primitive expression
194+
/// \param guard: the condition under which the operation happens
195+
void pointer_primitive_check(const exprt &expr, const guardt &guard);
196+
197+
/// Returns true if the given expression is a pointer primitive such as
198+
/// __CPROVER_r_ok()
199+
///
200+
/// \param expr expression
201+
/// \return true if the given expression is a pointer primitive
202+
bool is_pointer_primitive(const exprt &expr);
203+
188204
conditionst address_check(const exprt &address, const exprt &size);
189205
void integer_overflow_check(const exprt &, const guardt &);
190206
void conversion_check(const exprt &, const guardt &);
@@ -238,6 +254,7 @@ class goto_checkt
238254
bool enable_assertions;
239255
bool enable_built_in_assertions;
240256
bool enable_assumptions;
257+
bool enable_pointer_primitive_check;
241258

242259
typedef optionst::value_listt error_labelst;
243260
error_labelst error_labels;
@@ -1163,6 +1180,53 @@ void goto_checkt::pointer_validity_check(
11631180
}
11641181
}
11651182

1183+
void goto_checkt::pointer_primitive_check(
1184+
const exprt &expr,
1185+
const guardt &guard)
1186+
{
1187+
if(!enable_pointer_primitive_check)
1188+
return;
1189+
1190+
const exprt pointer = (expr.id() == ID_r_ok || expr.id() == ID_w_ok)
1191+
? to_binary_expr(expr).lhs()
1192+
: to_unary_expr(expr).op();
1193+
1194+
CHECK_RETURN(pointer.type().id() == ID_pointer);
1195+
1196+
const auto size_of_expr_opt = size_of_expr(pointer.type().subtype(), ns);
1197+
1198+
const exprt size = !size_of_expr_opt.has_value()
1199+
? from_integer(1, size_type())
1200+
: size_of_expr_opt.value();
1201+
1202+
const conditionst &conditions = address_check(pointer, size);
1203+
1204+
exprt::operandst conjuncts;
1205+
1206+
for(const auto &c : conditions)
1207+
conjuncts.push_back(c.assertion);
1208+
1209+
const or_exprt or_expr(null_object(pointer), conjunction(conjuncts));
1210+
1211+
add_guarded_property(
1212+
or_expr,
1213+
"pointer in pointer primitive is neither null nor valid",
1214+
"pointer primitives",
1215+
expr.source_location(),
1216+
expr,
1217+
guard);
1218+
}
1219+
1220+
bool goto_checkt::is_pointer_primitive(const exprt &expr)
1221+
{
1222+
// we don't need to include the __CPROVER_same_object primitive here as it
1223+
// is replaced by lower level primitives in the special function handling
1224+
// during typechecking (see c_typecheck_expr.cpp)
1225+
return expr.id() == ID_pointer_object || expr.id() == ID_pointer_offset ||
1226+
expr.id() == ID_object_size || expr.id() == ID_r_ok ||
1227+
expr.id() == ID_w_ok || expr.id() == ID_is_dynamic_object;
1228+
}
1229+
11661230
goto_checkt::conditionst
11671231
goto_checkt::address_check(const exprt &address, const exprt &size)
11681232
{
@@ -1740,6 +1804,10 @@ void goto_checkt::check_rec(const exprt &expr, guardt &guard)
17401804
{
17411805
pointer_validity_check(to_dereference_expr(expr), expr, guard);
17421806
}
1807+
else if(is_pointer_primitive(expr))
1808+
{
1809+
pointer_primitive_check(expr, guard);
1810+
}
17431811
}
17441812

17451813
void goto_checkt::check(const exprt &expr)
@@ -1860,6 +1928,8 @@ void goto_checkt::goto_check(
18601928
flag_resetter.set_flag(enable_undefined_shift_check, false);
18611929
else if(d.first == "disable:nan-check")
18621930
flag_resetter.set_flag(enable_nan_check, false);
1931+
else if(d.first == "disable:pointer-primitive-check")
1932+
flag_resetter.set_flag(enable_pointer_primitive_check, false);
18631933
}
18641934

18651935
new_code.clear();
@@ -2049,7 +2119,7 @@ void goto_checkt::goto_check(
20492119
}
20502120
else if(i.is_dead())
20512121
{
2052-
if(enable_pointer_check)
2122+
if(enable_pointer_check || enable_pointer_primitive_check)
20532123
{
20542124
assert(i.code.operands().size()==1);
20552125
const symbol_exprt &variable=to_symbol_expr(i.code.op0());

src/analyses/goto_check.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ void goto_check(
3838
"(div-by-zero-check)(enum-range-check)(signed-overflow-check)(unsigned-" \
3939
"overflow-check)" \
4040
"(pointer-overflow-check)(conversion-check)(undefined-shift-check)" \
41-
"(float-overflow-check)(nan-check)(no-built-in-assertions)"
41+
"(float-overflow-check)(nan-check)(no-built-in-assertions)" \
42+
"(pointer-primitive-check)"
4243

4344
// clang-format off
4445
#define HELP_GOTO_CHECK \
@@ -55,7 +56,7 @@ void goto_check(
5556
" --nan-check check floating-point for NaN\n" \
5657
" --no-built-in-assertions ignore assertions in built-in library\n" \
5758
" --enum-range-check checks that all enum type expressions have values in the enum range\n" /* NOLINT(whitespace/line_length) */ \
58-
// clang-format on
59+
" --pointer-primitive-check checks that all pointers in pointer primitives are valid or null\n" /* NOLINT(whitespace/line_length) */
5960

6061
#define PARSE_OPTIONS_GOTO_CHECK(cmdline, options) \
6162
options.set_option("bounds-check", cmdline.isset("bounds-check")); \
@@ -70,6 +71,8 @@ void goto_check(
7071
options.set_option("undefined-shift-check", cmdline.isset("undefined-shift-check")); /* NOLINT(whitespace/line_length) */ \
7172
options.set_option("float-overflow-check", cmdline.isset("float-overflow-check")); /* NOLINT(whitespace/line_length) */ \
7273
options.set_option("nan-check", cmdline.isset("nan-check")); \
73-
options.set_option("built-in-assertions", !cmdline.isset("no-built-in-assertions")) /* NOLINT(whitespace/line_length) */
74+
options.set_option("built-in-assertions", !cmdline.isset("no-built-in-assertions")); /* NOLINT(whitespace/line_length) */ \
75+
options.set_option("pointer-primitive-check", cmdline.isset("pointer-primitive-check")) /* NOLINT(whitespace/line_length) */
76+
// clang-format on
7477

7578
#endif // CPROVER_ANALYSES_GOTO_CHECK_H

0 commit comments

Comments
 (0)