Skip to content

Commit cd2c65b

Browse files
authored
Merge pull request #5210 from xbauch/fix/malloc-size-too-large
Malloc should fail on too large allocations
2 parents 5568f64 + 195da86 commit cd2c65b

File tree

27 files changed

+179
-38
lines changed

27 files changed

+179
-38
lines changed

doc/cprover-manual/properties.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,19 @@ example, replacing functions or setting global variables with the `__CPROVER`
319319
prefix might make analysis impossible. To avoid doing this by accident, negative
320320
lookahead can be used. For example, `(?!__).*` matches all names not starting
321321
with `__`.
322+
323+
### Malloc failure mode
324+
325+
|Flag | Check |
326+
|------------------------|-------------------------------------------------|
327+
| `--malloc-fail-null` | in case malloc fails return NULL |
328+
| `--malloc-fail-assert` | in case malloc fails report as failed property |
329+
330+
Calling `malloc` may fail for a number of reasons and the function may return a
331+
NULL pointer. The users can choose if and how they want the `malloc`-related
332+
failures to occur. The option `--malloc-fail-null` results in `malloc` returning
333+
the NULL pointer when failing. The option `--malloc-fail-assert` places
334+
additional properties inside `malloc` that are checked and if failing the
335+
verification is terminated (by `assume(false)`). One such property is that the
336+
allocated size is not too large, i.e. internally representable. When neither of
337+
those two options are used, CBMC will assume that `malloc` does not fail.

regression/cbmc/Pointer_byte_extract5/main.i

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ typedef union
1111
typedef struct
1212
{
1313
int Count;
14-
Union List[1];
14+
// flexible array member -- note that smt conversion does not yet support
15+
// 0-sized arrays
16+
Union List[0];
1517
} Struct3;
1618
#pragma pack(pop)
1719

1820
int main()
1921
{
20-
Struct3 *p = malloc(sizeof(Struct3) + sizeof(Union));
22+
Struct3 *p = malloc(sizeof(Struct3) + 2 * sizeof(Union));
2123
p->Count = 3;
2224
int po=0;
2325

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
CORE
1+
CORE broken-smt-backend
22
main.i
33
--bounds-check --32 --no-simplify
44
^EXIT=10$
55
^SIGNAL=0$
66
array\.List dynamic object upper bound in p->List\[2\]: FAILURE
7-
\*\* 1 of 14 failed
7+
\*\* 1 of 15 failed
88
--
99
^warning: ignoring
1010
--
1111
Test is built from SV-COMP's memsafety/20051113-1.c_false-valid-memtrack.c.
1212
C90 did not have flexible arrays, and using arrays of size 1 was common
13-
practice: http://c-faq.com/struct/structhack.html. We need to treat those as if
14-
it were a zero-sized array.
13+
practice: http://c-faq.com/struct/structhack.html. But past C90 using
14+
non-flexible members for struct-hack is undefined, hence we changed the test to
15+
use flexible member instead.

regression/cbmc/Pointer_byte_extract5/test.desc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ main.i
44
^EXIT=10$
55
^SIGNAL=0$
66
array\.List dynamic object upper bound in p->List\[2\]: FAILURE
7-
\*\* 1 of 11 failed
7+
\*\* 1 of 13 failed
88
--
99
^warning: ignoring
1010
--
1111
Test is built from SV-COMP's memsafety/20051113-1.c_false-valid-memtrack.c.
1212
C90 did not have flexible arrays, and using arrays of size 1 was common
13-
practice: http://c-faq.com/struct/structhack.html. We need to treat those as if
14-
it were a zero-sized array.
13+
practice: http://c-faq.com/struct/structhack.html. But past C90 using
14+
non-flexible members for struct-hack is undefined, hence we changed the test to
15+
use flexible member instead.

regression/cbmc/array_constraints1/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ main.c
44
^EXIT=10$
55
^SIGNAL=0$
66
^VERIFICATION FAILED$
7-
^\*\* 2 of 14
7+
^\*\* 2 of 15
88
--
99
^warning: ignoring
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
5+
int main()
6+
{
7+
int *p = malloc(__CPROVER_max_malloc_size); // try to allocate exactly the max
8+
9+
return 0;
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
largest_representable.c
3+
--malloc-fail-assert
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^\[malloc.assertion.\d+\] line \d+ max allocation size exceeded: SUCCESS$
7+
^VERIFICATION SUCCESSFUL$
8+
--
9+
^warning: ignoring
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
5+
int main()
6+
{
7+
int *p = malloc(SIZE_MAX);
8+
9+
return 0;
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
max_size.c
3+
--malloc-fail-assert
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^\[malloc.assertion.\d+\] line \d+ max allocation size exceeded: FAILURE$
7+
^VERIFICATION FAILED$
8+
--
9+
^warning: ignoring
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <assert.h>
2+
#include <stdint.h>
3+
#include <stdlib.h>
4+
5+
int main()
6+
{
7+
// try to allocate the smallest violating amount
8+
int *p = malloc(__CPROVER_max_malloc_size + 1);
9+
assert(p);
10+
11+
return 0;
12+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
one_byte_too_large.c
3+
--malloc-fail-null
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^\[main.assertion.\d+\] line \d+ assertion p: FAILURE$
7+
^VERIFICATION FAILED$
8+
--
9+
^warning: ignoring

regression/cbmc/pointer-overflow1/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ main.c
55
^SIGNAL=0$
66
^\[main\.overflow\.\d+\] line \d+ (pointer )?arithmetic overflow on .*sizeof\(signed int\) .* : SUCCESS
77
^VERIFICATION FAILED$
8-
^\*\* 8 of 11 failed
8+
^\*\* 8 of 12 failed
99
--
1010
^\[main\.overflow\.\d+\] line \d+ (pointer )?arithmetic overflow on .*sizeof\(signed int\) .* : FAILURE
1111
^warning: ignoring

regression/cbmc/r_w_ok1/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ CORE
22
main.c
33

44
__CPROVER_[rw]_ok\(arbitrary_size, n \+ 1\): FAILURE$
5-
^\*\* 2 of 10 failed
5+
^\*\* 2 of 11 failed
66
^VERIFICATION FAILED$
77
^EXIT=10$
88
^SIGNAL=0$

regression/goto-analyzer/constant_propagation_01/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--constants --simplify out.gb
44
^EXIT=0$
55
^SIGNAL=0$
6-
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 6, function calls: 0$
7-
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 15, function calls: 2$
6+
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 7, function calls: 0$
7+
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 18, function calls: 2$
88
--
99
^warning: ignoring

regression/goto-analyzer/constant_propagation_02/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--constants --simplify out.gb
44
^EXIT=0$
55
^SIGNAL=0$
6-
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 7, function calls: 0$
7-
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 14, function calls: 2$
6+
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 8, function calls: 0$
7+
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 17, function calls: 2$
88
--
99
^warning: ignoring

regression/goto-analyzer/constant_propagation_03/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--constants --simplify out.gb
44
^EXIT=0$
55
^SIGNAL=0$
6-
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 7, function calls: 0$
7-
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 14, function calls: 2$
6+
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 8, function calls: 0$
7+
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 17, function calls: 2$
88
--
99
^warning: ignoring

regression/goto-analyzer/constant_propagation_04/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--constants --simplify out.gb
44
^EXIT=0$
55
^SIGNAL=0$
6-
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 7, function calls: 0$
7-
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 14, function calls: 2$
6+
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 8, function calls: 0$
7+
^Unmodified: assert: 0, assume: 0, goto: 0, assigns: 17, function calls: 2$
88
--
99
^warning: ignoring

regression/goto-analyzer/constant_propagation_07/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--constants --simplify out.gb
44
^EXIT=0$
55
^SIGNAL=0$
6-
^Simplified: assert: 1, assume: 0, goto: 3, assigns: 12, function calls: 0$
7-
^Unmodified: assert: 0, assume: 0, goto: 1, assigns: 12, function calls: 2$
6+
^Simplified: assert: 1, assume: 0, goto: 3, assigns: 13, function calls: 0$
7+
^Unmodified: assert: 0, assume: 0, goto: 1, assigns: 15, function calls: 2$
88
--
99
^warning: ignoring

regression/goto-analyzer/constant_propagation_12/test.desc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ main.c
33
--constants --simplify out.gb
44
^EXIT=0$
55
^SIGNAL=0$
6-
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 6, function calls: 0$
7-
^Unmodified: assert: 0, assume: 0, goto: 1, assigns: 13, function calls: 2$
6+
^Simplified: assert: 1, assume: 0, goto: 1, assigns: 7, function calls: 0$
7+
^Unmodified: assert: 0, assume: 0, goto: 1, assigns: 16, function calls: 2$
88
--
99
^warning: ignoring

regression/snapshot-harness/pointer-to-array-function-parameters-multi-arg-right/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-
first,second,string_size,prefix,prefix_size --harness-type initialise-with-memory-snapshot --initial-goto-location main:4 --havoc-variables prefix --size-of-array prefix:prefix_size --max-array-size 5
3+
first,second,string_size,prefix --harness-type initialise-with-memory-snapshot --initial-goto-location main:4 --havoc-variables prefix --size-of-array prefix:prefix_size --max-array-size 5
44
^SIGNAL=0$
55
^EXIT=0$
66
VERIFICATION SUCCESSFUL

src/ansi-c/ansi_c_internal_additions.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,15 @@ void ansi_c_internal_additions(std::string &code)
155155
"void *" CPROVER_PREFIX "allocate("
156156
CPROVER_PREFIX "size_t size, " CPROVER_PREFIX "bool zero);\n"
157157
"const void *" CPROVER_PREFIX "alloca_object = 0;\n"
158+
"int " CPROVER_PREFIX "malloc_failure_mode="+
159+
std::to_string(config.ansi_c.malloc_failure_mode)+";\n"
160+
"int " CPROVER_PREFIX "malloc_failure_mode_return_null="+
161+
std::to_string(config.ansi_c.malloc_failure_mode_return_null)+";\n"
162+
"int " CPROVER_PREFIX "malloc_failure_mode_assert_then_assume="+
163+
std::to_string(config.ansi_c.malloc_failure_mode_assert_then_assume)+";\n"
164+
CPROVER_PREFIX "size_t " CPROVER_PREFIX "max_malloc_size="+
165+
std::to_string(1 << (config.ansi_c.pointer_width -
166+
config.bv_encoding.object_bits - 1))+";\n"
158167

159168
// this is ANSI-C
160169
"extern " CPROVER_PREFIX "thread_local const char __func__["

src/ansi-c/library/cprover.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ extern const void *__CPROVER_malloc_object;
1616
extern __CPROVER_size_t __CPROVER_malloc_size;
1717
extern _Bool __CPROVER_malloc_is_new_array;
1818
extern const void *__CPROVER_memory_leak;
19+
extern int __CPROVER_malloc_failure_mode;
20+
extern __CPROVER_size_t __CPROVER_max_malloc_size;
21+
22+
// malloc failure modes
23+
extern int __CPROVER_malloc_failure_mode_return_null;
24+
extern int __CPROVER_malloc_failure_mode_assert_then_assume;
1925

2026
void __CPROVER_assume(__CPROVER_bool assumption) __attribute__((__noreturn__));
2127
void __CPROVER_assert(__CPROVER_bool assertion, const char *description);

src/ansi-c/library/stdlib.c

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,47 @@ inline void *malloc(__CPROVER_size_t malloc_size)
115115
// realistically, malloc may return NULL,
116116
// and __CPROVER_allocate doesn't, but no one cares
117117
__CPROVER_HIDE:;
118-
void *malloc_res;
119-
malloc_res = __CPROVER_allocate(malloc_size, 0);
120118

121-
// make sure it's not recorded as deallocated
122-
__CPROVER_deallocated=(malloc_res==__CPROVER_deallocated)?0:__CPROVER_deallocated;
119+
if(
120+
__CPROVER_malloc_failure_mode ==
121+
__CPROVER_malloc_failure_mode_return_null)
122+
{
123+
if(malloc_size > __CPROVER_max_malloc_size)
124+
{
125+
return (void *)0;
126+
}
127+
}
128+
else if(
129+
__CPROVER_malloc_failure_mode ==
130+
__CPROVER_malloc_failure_mode_assert_then_assume)
131+
{
132+
__CPROVER_assert(
133+
malloc_size <= __CPROVER_max_malloc_size,
134+
"max allocation size exceeded");
135+
__CPROVER_assume(malloc_size <= __CPROVER_max_malloc_size);
136+
}
123137

124-
// record the object size for non-determistic bounds checking
125-
__CPROVER_bool record_malloc=__VERIFIER_nondet___CPROVER_bool();
126-
__CPROVER_malloc_object=record_malloc?malloc_res:__CPROVER_malloc_object;
127-
__CPROVER_malloc_size=record_malloc?malloc_size:__CPROVER_malloc_size;
128-
__CPROVER_malloc_is_new_array=record_malloc?0:__CPROVER_malloc_is_new_array;
138+
void *malloc_res;
139+
malloc_res = __CPROVER_allocate(malloc_size, 0);
129140

130-
// detect memory leaks
131-
__CPROVER_bool record_may_leak=__VERIFIER_nondet___CPROVER_bool();
132-
__CPROVER_memory_leak=record_may_leak?malloc_res:__CPROVER_memory_leak;
141+
// make sure it's not recorded as deallocated
142+
__CPROVER_deallocated =
143+
(malloc_res == __CPROVER_deallocated) ? 0 : __CPROVER_deallocated;
133144

134-
return malloc_res;
145+
// record the object size for non-determistic bounds checking
146+
__CPROVER_bool record_malloc = __VERIFIER_nondet___CPROVER_bool();
147+
__CPROVER_malloc_object =
148+
record_malloc ? malloc_res : __CPROVER_malloc_object;
149+
__CPROVER_malloc_size = record_malloc ? malloc_size : __CPROVER_malloc_size;
150+
__CPROVER_malloc_is_new_array =
151+
record_malloc ? 0 : __CPROVER_malloc_is_new_array;
152+
153+
// detect memory leaks
154+
__CPROVER_bool record_may_leak = __VERIFIER_nondet___CPROVER_bool();
155+
__CPROVER_memory_leak =
156+
record_may_leak ? malloc_res : __CPROVER_memory_leak;
157+
158+
return malloc_res;
135159
}
136160

137161
/* FUNCTION: __builtin_alloca */

src/cbmc/cbmc_parse_options.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,9 @@ void cbmc_parse_optionst::help()
10671067
" --error-label label check that label is unreachable\n"
10681068
" --cover CC create test-suite with coverage criterion CC\n" // NOLINT(*)
10691069
" --mm MM memory consistency model for concurrent programs\n" // NOLINT(*)
1070+
// NOLINTNEXTLINE(whitespace/line_length)
1071+
" --malloc-fail-assert set malloc failure mode to assert-then-assume\n"
1072+
" --malloc-fail-null set malloc failure mode to return null\n"
10701073
HELP_REACHABILITY_SLICER
10711074
HELP_REACHABILITY_SLICER_FB
10721075
" --full-slice run full slicer (experimental)\n" // NOLINT(*)

src/cbmc/cbmc_parse_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class optionst;
5050
"(object-bits):" \
5151
OPT_GOTO_CHECK \
5252
"(no-assertions)(no-assumptions)" \
53+
"(malloc-fail-assert)(malloc-fail-null)" \
5354
OPT_XML_INTERFACE \
5455
OPT_JSON_INTERFACE \
5556
"(smt1)(smt2)(fpa)(cvc3)(cvc4)(boolector)(yices)(z3)(mathsat)" \

src/util/config.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,16 @@ bool configt::set(const cmdlinet &cmdline)
10931093
bv_encoding.is_object_bits_default = false;
10941094
}
10951095

1096+
if(cmdline.isset("malloc-fail-assert") && cmdline.isset("malloc-fail-null"))
1097+
{
1098+
throw invalid_command_line_argument_exceptiont{
1099+
"at most one malloc failure mode is acceptable", "--malloc-fail-null"};
1100+
}
1101+
if(cmdline.isset("malloc-fail-null"))
1102+
ansi_c.malloc_failure_mode = ansi_c.malloc_failure_mode_return_null;
1103+
if(cmdline.isset("malloc-fail-assert"))
1104+
ansi_c.malloc_failure_mode = ansi_c.malloc_failure_mode_assert_then_assume;
1105+
10961106
return false;
10971107
}
10981108

src/util/config.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ class configt
130130

131131
bool string_abstraction;
132132

133+
enum malloc_failure_modet
134+
{
135+
malloc_failure_mode_none = 0,
136+
malloc_failure_mode_return_null = 1,
137+
malloc_failure_mode_assert_then_assume = 2
138+
};
139+
140+
malloc_failure_modet malloc_failure_mode = malloc_failure_mode_none;
141+
133142
static const std::size_t default_object_bits=8;
134143
} ansi_c;
135144

0 commit comments

Comments
 (0)