Skip to content

Commit f1d787b

Browse files
authored
Merge pull request diffblue#2166 from tautschnig/out-of-bounds
Handle type-implied (but spurious) out-of-bounds in back-end
2 parents 5e6a3af + d3d888b commit f1d787b

File tree

8 files changed

+222
-12
lines changed

8 files changed

+222
-12
lines changed

regression/cbmc/Pointer_byte_extract5/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ typedef struct
2222

2323
int main()
2424
{
25-
Struct3 *p = malloc (sizeof (int) + 2 * sizeof(Union));
25+
Struct3 *p = malloc(sizeof(Struct3) + sizeof(Union));
2626
p->Count = 3;
2727
int po=0;
2828

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--bounds-check --32 --no-simplify
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
array\.List dynamic object upper bound in p->List\[2\]: FAILURE
7+
\*\* 1 of 14 failed
8+
--
9+
^warning: ignoring
10+
--
11+
Test is built from SV-COMP's memsafety/20051113-1.c_false-valid-memtrack.c.
12+
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.

regression/cbmc/Pointer_byte_extract5/test.desc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ array\.List dynamic object upper bound in p->List\[2\]: FAILURE
77
\*\* 1 of 11 failed
88
--
99
^warning: ignoring
10+
--
11+
Test is built from SV-COMP's memsafety/20051113-1.c_false-valid-memtrack.c.
12+
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.

regression/cbmc/bounds_check1/main.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include <stdint.h>
2+
#include <stdlib.h>
3+
4+
typedef struct _eth_frame_header
5+
{
6+
uint8_t dest[6];
7+
uint8_t src[6];
8+
uint16_t length;
9+
uint8_t payload[0];
10+
} eth_frame_header;
11+
12+
typedef struct _eth_frame_header_with_tag
13+
{
14+
uint8_t dest[6];
15+
uint8_t src[6];
16+
uint32_t tag;
17+
uint16_t length;
18+
uint8_t payload[0];
19+
} eth_frame_header_with_tag;
20+
21+
typedef struct _eth_frame_footer
22+
{
23+
uint32_t crc;
24+
} eth_frame_footer;
25+
26+
#define FRAME_LENGTH \
27+
sizeof(eth_frame_header_with_tag) + 1500 + sizeof(eth_frame_footer)
28+
29+
typedef union _eth_frame {
30+
uint8_t raw[FRAME_LENGTH];
31+
eth_frame_header header;
32+
eth_frame_header_with_tag header_with_tag;
33+
} eth_frame;
34+
35+
typedef struct _eth_frame_with_control
36+
{
37+
eth_frame frame;
38+
uint32_t control; // Routing, filtering, inspection, etc.
39+
} eth_frame_with_control;
40+
41+
void stack()
42+
{
43+
eth_frame_with_control f;
44+
unsigned i, i2, j, j2, k, k2, l, l2;
45+
46+
// Safe if 0 <= i < FRAME_LENGTH, viable attack over FRAME_LENGTH
47+
__CPROVER_assume(i < FRAME_LENGTH);
48+
// within array bounds
49+
f.frame.raw[i] = 42;
50+
__CPROVER_assume(i2 < FRAME_LENGTH + 4);
51+
// possibly out-of-bounds, even though still within the object
52+
f.frame.raw[i2] = 42;
53+
54+
// Safe if 0 <= j < 6, likely unsafe if over 6
55+
__CPROVER_assume(j < 6);
56+
// within array bounds
57+
f.frame.header.dest[j] = 42;
58+
// possibly out-of-bounds
59+
f.frame.header.dest[j2] = 42;
60+
61+
// Safe if 0 <= k < 1500, could corrupt crc if k < 1508, viable attack over 1508
62+
__CPROVER_assume(k < FRAME_LENGTH - 14);
63+
// within array bounds
64+
f.frame.header.payload[k] = 42;
65+
// possibly out-of-bounds
66+
f.frame.header.payload[k2] = 42;
67+
68+
// Safe if 0 <= l < 1504, wrong but probably harmless if l < 1508, viable attack over 1508
69+
__CPROVER_assume(l < FRAME_LENGTH - 14);
70+
// within array bounds
71+
((eth_frame_footer *)(&(f.frame.header.payload[l])))->crc = 42;
72+
// possibly out-of-bounds
73+
((eth_frame_footer *)(&(f.frame.header.payload[l2])))->crc = 42;
74+
}
75+
76+
void heap()
77+
{
78+
eth_frame_with_control *f_heap = malloc(sizeof(eth_frame_with_control));
79+
unsigned i, i2, j, j2, k, k2, l, l2;
80+
81+
// Safe if 0 <= i < FRAME_LENGTH, viable attack over FRAME_LENGTH
82+
__CPROVER_assume(i < FRAME_LENGTH);
83+
// within array bounds
84+
f_heap->frame.raw[i] = 42;
85+
__CPROVER_assume(i2 < FRAME_LENGTH + 4);
86+
// possibly out-of-bounds, even though still within the object
87+
f_heap->frame.raw[i2] = 42;
88+
89+
// Safe if 0 <= j < 6, likely unsafe if over 6
90+
__CPROVER_assume(j < 6);
91+
// within array bounds
92+
f_heap->frame.header.dest[j] = 42;
93+
// possibly out-of-bounds
94+
f_heap->frame.header.dest[j2] = 42;
95+
96+
// Safe if 0 <= k < 1500, could corrupt crc if k < 1508, viable attack over 1508
97+
__CPROVER_assume(k < FRAME_LENGTH - 14);
98+
// within array bounds
99+
f_heap->frame.header.payload[k] = 42;
100+
// possibly out-of-bounds
101+
f_heap->frame.header.payload[k2] = 42;
102+
103+
// Safe if 0 <= l < 1504, wrong but probably harmless if l < 1508, viable attack over 1508
104+
__CPROVER_assume(l < FRAME_LENGTH - 14);
105+
// within array bounds
106+
((eth_frame_footer *)(&(f_heap->frame.header.payload[l])))->crc = 42;
107+
// possibly out-of-bounds
108+
((eth_frame_footer *)(&(f_heap->frame.header.payload[l2])))->crc = 42;
109+
}
110+
111+
int main()
112+
{
113+
stack();
114+
heap();
115+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
main.c
3+
--bounds-check --pointer-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
\[\(signed long( long)? int\)i2\]: FAILURE
7+
dest\[\(signed long( long)? int\)j2\]: FAILURE
8+
payload\[\(signed long( long)? int\)[kl]2\]: FAILURE
9+
\*\* 10 of 72 failed
10+
--
11+
^warning: ignoring
12+
\[\(signed long( long)? int\)i\]: FAILURE
13+
dest\[\(signed long( long)? int\)j\]: FAILURE
14+
payload\[\(signed long( long)? int\)[kl]\]: FAILURE

src/analyses/goto_check.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,27 @@ void goto_checkt::bounds_check(
12131213
expr.array().id()==ID_member)
12141214
{
12151215
// a variable sized struct member
1216+
//
1217+
// Excerpt from the C standard on flexible array members:
1218+
// However, when a . (or ->) operator has a left operand that is (a pointer
1219+
// to) a structure with a flexible array member and the right operand names
1220+
// that member, it behaves as if that member were replaced with the longest
1221+
// array (with the same element type) that would not make the structure
1222+
// larger than the object being accessed; [...]
1223+
const exprt type_size = size_of_expr(ode.root_object().type(), ns);
1224+
1225+
binary_relation_exprt inequality(
1226+
typecast_exprt::conditional_cast(ode.offset(), type_size.type()),
1227+
ID_lt,
1228+
type_size);
1229+
1230+
add_guarded_claim(
1231+
implies_exprt(type_matches_size, inequality),
1232+
name + " upper bound",
1233+
"array bounds",
1234+
expr.find_source_location(),
1235+
expr,
1236+
guard);
12161237
}
12171238
else
12181239
{

src/solvers/flattening/boolbv_byte_extract.cpp

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Author: Daniel Kroening, [email protected]
1212

1313
#include <util/arith_tools.h>
1414
#include <util/byte_operators.h>
15+
#include <util/pointer_offset_size.h>
1516
#include <util/std_expr.h>
1617
#include <util/throw_with_nested.h>
1718

@@ -78,6 +79,30 @@ bvt boolbvt::convert_byte_extract(const byte_extract_exprt &expr)
7879
if(width==0)
7980
return conversion_failed(expr);
8081

82+
// see if the byte number is constant and within bounds, else work from the
83+
// root object
84+
const mp_integer op_bytes = pointer_offset_size(expr.op().type(), ns);
85+
auto index = numeric_cast<mp_integer>(expr.offset());
86+
if(
87+
(!index.has_value() || (*index < 0 || *index >= op_bytes)) &&
88+
(expr.op().id() == ID_member || expr.op().id() == ID_index ||
89+
expr.op().id() == ID_byte_extract_big_endian ||
90+
expr.op().id() == ID_byte_extract_little_endian))
91+
{
92+
object_descriptor_exprt o;
93+
o.build(expr.op(), ns);
94+
CHECK_RETURN(o.offset().id() != ID_unknown);
95+
if(o.offset().type() != expr.offset().type())
96+
o.offset().make_typecast(expr.offset().type());
97+
byte_extract_exprt be(
98+
expr.id(),
99+
o.root_object(),
100+
plus_exprt(o.offset(), expr.offset()),
101+
expr.type());
102+
103+
return convert_bv(be);
104+
}
105+
81106
const exprt &op=expr.op();
82107
const exprt &offset=expr.offset();
83108

@@ -106,22 +131,16 @@ bvt boolbvt::convert_byte_extract(const byte_extract_exprt &expr)
106131
// see if the byte number is constant
107132
unsigned byte_width=8;
108133

109-
mp_integer index;
110-
if(!to_integer(offset, index))
134+
if(index.has_value())
111135
{
112-
if(index<0)
113-
throw "byte_extract flatting with negative offset: "+expr.pretty();
114-
115-
mp_integer offset=index*byte_width;
116-
117-
std::size_t offset_i=integer2unsigned(offset);
136+
const mp_integer offset = *index * byte_width;
118137

119138
for(std::size_t i=0; i<width; i++)
120139
// out of bounds?
121-
if(offset<0 || offset_i+i>=op_bv.size())
140+
if(offset + i < 0 || offset + i >= op_bv.size())
122141
bv[i]=prop.new_variable();
123142
else
124-
bv[i]=op_bv[offset_i+i];
143+
bv[i] = op_bv[numeric_cast_v<std::size_t>(offset) + i];
125144
}
126145
else
127146
{

src/solvers/flattening/boolbv_index.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ Author: Daniel Kroening, [email protected]
1111
#include <cassert>
1212

1313
#include <util/arith_tools.h>
14-
#include <util/std_expr.h>
14+
#include <util/pointer_offset_size.h>
1515
#include <util/simplify_expr.h>
16+
#include <util/std_expr.h>
1617

1718
bvt boolbvt::convert_index(const index_exprt &expr)
1819
{
@@ -333,6 +334,27 @@ bvt boolbvt::convert_index(
333334
for(std::size_t i=0; i<width; i++)
334335
bv[i]=tmp[integer2size_t(offset+i)];
335336
}
337+
else if(
338+
array.id() == ID_member || array.id() == ID_index ||
339+
array.id() == ID_byte_extract_big_endian ||
340+
array.id() == ID_byte_extract_little_endian)
341+
{
342+
object_descriptor_exprt o;
343+
o.build(array, ns);
344+
CHECK_RETURN(o.offset().id() != ID_unknown);
345+
346+
const mp_integer subtype_bytes =
347+
pointer_offset_size(array_type.subtype(), ns);
348+
exprt new_offset = simplify_expr(
349+
plus_exprt(
350+
o.offset(), from_integer(index * subtype_bytes, o.offset().type())),
351+
ns);
352+
353+
byte_extract_exprt be(
354+
byte_extract_id(), o.root_object(), new_offset, array_type.subtype());
355+
356+
return convert_bv(be);
357+
}
336358
else
337359
{
338360
// out of bounds

0 commit comments

Comments
 (0)