Skip to content

Commit e1060ec

Browse files
committed
Bounds check for flexible array members
Example crafted by Martin, which now clarifies which bounds we check.
1 parent 1a24a44 commit e1060ec

File tree

3 files changed

+151
-0
lines changed

3 files changed

+151
-0
lines changed

regression/cbmc/bounds_check1/main.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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+
{
31+
uint8_t raw[FRAME_LENGTH];
32+
eth_frame_header header;
33+
eth_frame_header_with_tag header_with_tag;
34+
} eth_frame;
35+
36+
typedef struct _eth_frame_with_control
37+
{
38+
eth_frame frame;
39+
uint32_t control; // Routing, filtering, inspection, etc.
40+
} eth_frame_with_control;
41+
42+
void stack()
43+
{
44+
eth_frame_with_control f;
45+
unsigned i, i2, j, j2, k, k2, l, l2;
46+
47+
// Safe if 0 <= i < FRAME_LENGTH, viable attack over FRAME_LENGTH
48+
__CPROVER_assume(i < FRAME_LENGTH);
49+
// within array bounds
50+
f.frame.raw[i] = 42;
51+
__CPROVER_assume(i2 < FRAME_LENGTH + 4);
52+
// possibly out-of-bounds, even though still within the object
53+
f.frame.raw[i2] = 42;
54+
55+
// Safe if 0 <= j < 6, likely unsafe if over 6
56+
__CPROVER_assume(j < 6);
57+
// within array bounds
58+
f.frame.header.dest[j] = 42;
59+
// possibly out-of-bounds
60+
f.frame.header.dest[j2] = 42;
61+
62+
// Safe if 0 <= k < 1500, could corrupt crc if k < 1508, viable attack over 1508
63+
__CPROVER_assume(k < FRAME_LENGTH - 14);
64+
// within array bounds
65+
f.frame.header.payload[k] = 42;
66+
// possibly out-of-bounds
67+
f.frame.header.payload[k2] = 42;
68+
69+
// Safe if 0 <= l < 1504, wrong but probably harmless if l < 1508, viable attack over 1508
70+
__CPROVER_assume(l < FRAME_LENGTH - 14);
71+
// within array bounds
72+
((eth_frame_footer*)(&(f.frame.header.payload[l])))->crc = 42;
73+
// possibly out-of-bounds
74+
((eth_frame_footer*)(&(f.frame.header.payload[l2])))->crc = 42;
75+
}
76+
77+
void heap()
78+
{
79+
eth_frame_with_control *f_heap = malloc(sizeof(eth_frame_with_control));
80+
unsigned i, i2, j, j2, k, k2, l, l2;
81+
82+
// Safe if 0 <= i < FRAME_LENGTH, viable attack over FRAME_LENGTH
83+
__CPROVER_assume(i < FRAME_LENGTH);
84+
// within array bounds
85+
f_heap->frame.raw[i] = 42;
86+
__CPROVER_assume(i2 < FRAME_LENGTH + 4);
87+
// possibly out-of-bounds, even though still within the object
88+
f_heap->frame.raw[i2] = 42;
89+
90+
// Safe if 0 <= j < 6, likely unsafe if over 6
91+
__CPROVER_assume(j < 6);
92+
// within array bounds
93+
f_heap->frame.header.dest[j] = 42;
94+
// possibly out-of-bounds
95+
f_heap->frame.header.dest[j2] = 42;
96+
97+
// Safe if 0 <= k < 1500, could corrupt crc if k < 1508, viable attack over 1508
98+
__CPROVER_assume(k < FRAME_LENGTH - 14);
99+
// within array bounds
100+
f_heap->frame.header.payload[k] = 42;
101+
// possibly out-of-bounds
102+
f_heap->frame.header.payload[k2] = 42;
103+
104+
// Safe if 0 <= l < 1504, wrong but probably harmless if l < 1508, viable attack over 1508
105+
__CPROVER_assume(l < FRAME_LENGTH - 14);
106+
// within array bounds
107+
((eth_frame_footer*)(&(f_heap->frame.header.payload[l])))->crc = 42;
108+
// possibly out-of-bounds
109+
((eth_frame_footer*)(&(f_heap->frame.header.payload[l2])))->crc = 42;
110+
}
111+
112+
int main()
113+
{
114+
stack();
115+
heap();
116+
}
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 int\)i2\]: FAILURE
7+
dest\[\(signed long int\)j2\]: FAILURE
8+
payload\[\(signed long int\)[kl]2\]: FAILURE
9+
\*\* 10 of 72 failed
10+
--
11+
^warning: ignoring
12+
\[\(signed long int\)i\]: FAILURE
13+
dest\[\(signed long int\)j\]: FAILURE
14+
payload\[\(signed 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+
inequality,
1232+
name+" upper bound",
1233+
"array bounds",
1234+
expr.find_source_location(),
1235+
expr,
1236+
guard);
12161237
}
12171238
else
12181239
{

0 commit comments

Comments
 (0)