Skip to content

Commit d3d888b

Browse files
committed
Bounds check for flexible array members
Example crafted by Martin, which now clarifies which bounds we check: 1) When the object is stack-allocated and does not use flexible array members, the bounds are as specified in the type. 2) When flexible array members are used (zero-sized array), the semantics laid out in the standard are used (the bound is the size of the full object). 3) When the allocation size does not match the type-specified size (necessarily a case of heap allocation), use the size of the allocation.
1 parent 41003e8 commit d3d888b

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

regression/cbmc/bounds_check1/main.c

+115
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+
}
+14
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

+21
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
{

0 commit comments

Comments
 (0)