Skip to content

Commit 9b03e1c

Browse files
authored
Merge pull request #10609 from MathiasVP/overrun-write-only-flag-overrunning-write
C++: Make `OverrunWriteProductFlow` raise alerts on overflows
2 parents 1fcd22b + 4e3b445 commit 9b03e1c

File tree

6 files changed

+705
-35
lines changed

6 files changed

+705
-35
lines changed

cpp/ql/lib/experimental/semmle/code/cpp/dataflow/ProductFlow.qll

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ module ProductFlow {
2020
* `source1` and `source2` must belong to the same callable.
2121
*/
2222
predicate isSourcePair(
23-
DataFlow::Node source1, string state1, DataFlow::Node source2, string state2
23+
DataFlow::Node source1, DataFlow::FlowState state1, DataFlow::Node source2,
24+
DataFlow::FlowState state2
2425
) {
2526
state1 = "" and
2627
state2 = "" and
@@ -89,6 +90,49 @@ module ProductFlow {
8990
*/
9091
predicate isBarrierOut2(DataFlow::Node node) { none() }
9192

93+
/*
94+
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps in
95+
* the first projection of the product dataflow graph.
96+
*/
97+
98+
predicate isAdditionalFlowStep1(DataFlow::Node node1, DataFlow::Node node2) { none() }
99+
100+
/**
101+
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps in
102+
* the first projection of the product dataflow graph.
103+
*
104+
* This step is only applicable in `state1` and updates the flow state to `state2`.
105+
*/
106+
predicate isAdditionalFlowStep1(
107+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
108+
DataFlow::FlowState state2
109+
) {
110+
state1 instanceof DataFlow::FlowStateEmpty and
111+
state2 instanceof DataFlow::FlowStateEmpty and
112+
this.isAdditionalFlowStep1(node1, node2)
113+
}
114+
115+
/**
116+
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps in
117+
* the second projection of the product dataflow graph.
118+
*/
119+
predicate isAdditionalFlowStep2(DataFlow::Node node1, DataFlow::Node node2) { none() }
120+
121+
/**
122+
* Holds if data may flow from `node1` to `node2` in addition to the normal data-flow steps in
123+
* the second projection of the product dataflow graph.
124+
*
125+
* This step is only applicable in `state1` and updates the flow state to `state2`.
126+
*/
127+
predicate isAdditionalFlowStep2(
128+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
129+
DataFlow::FlowState state2
130+
) {
131+
state1 instanceof DataFlow::FlowStateEmpty and
132+
state2 instanceof DataFlow::FlowStateEmpty and
133+
this.isAdditionalFlowStep2(node1, node2)
134+
}
135+
92136
predicate hasFlowPath(
93137
DataFlow::PathNode source1, DataFlow2::PathNode source2, DataFlow::PathNode sink1,
94138
DataFlow2::PathNode sink2
@@ -103,54 +147,69 @@ module ProductFlow {
103147
class Conf1 extends DataFlow::Configuration {
104148
Conf1() { this = "Conf1" }
105149

106-
override predicate isSource(DataFlow::Node source, string state) {
150+
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
107151
exists(Configuration conf | conf.isSourcePair(source, state, _, _))
108152
}
109153

110-
override predicate isSink(DataFlow::Node sink, string state) {
154+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
111155
exists(Configuration conf | conf.isSinkPair(sink, state, _, _))
112156
}
113157

114-
override predicate isBarrier(DataFlow::Node node, string state) {
158+
override predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
115159
exists(Configuration conf | conf.isBarrier1(node, state))
116160
}
117161

118162
override predicate isBarrierOut(DataFlow::Node node) {
119163
exists(Configuration conf | conf.isBarrierOut1(node))
120164
}
165+
166+
override predicate isAdditionalFlowStep(
167+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
168+
DataFlow::FlowState state2
169+
) {
170+
exists(Configuration conf | conf.isAdditionalFlowStep1(node1, state1, node2, state2))
171+
}
121172
}
122173

123174
class Conf2 extends DataFlow2::Configuration {
124175
Conf2() { this = "Conf2" }
125176

126-
override predicate isSource(DataFlow::Node source, string state) {
127-
exists(Configuration conf, DataFlow::Node source1 |
128-
conf.isSourcePair(source1, _, source, state) and
129-
any(Conf1 c).hasFlow(source1, _)
177+
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
178+
exists(Configuration conf, DataFlow::PathNode source1 |
179+
conf.isSourcePair(source1.getNode(), source1.getState(), source, state) and
180+
any(Conf1 c).hasFlowPath(source1, _)
130181
)
131182
}
132183

133-
override predicate isSink(DataFlow::Node sink, string state) {
134-
exists(Configuration conf, DataFlow::Node sink1 |
135-
conf.isSinkPair(sink1, _, sink, state) and any(Conf1 c).hasFlow(_, sink1)
184+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
185+
exists(Configuration conf, DataFlow::PathNode sink1 |
186+
conf.isSinkPair(sink1.getNode(), sink1.getState(), sink, state) and
187+
any(Conf1 c).hasFlowPath(_, sink1)
136188
)
137189
}
138190

139-
override predicate isBarrier(DataFlow::Node node, string state) {
191+
override predicate isBarrier(DataFlow::Node node, DataFlow::FlowState state) {
140192
exists(Configuration conf | conf.isBarrier2(node, state))
141193
}
142194

143195
override predicate isBarrierOut(DataFlow::Node node) {
144196
exists(Configuration conf | conf.isBarrierOut2(node))
145197
}
198+
199+
override predicate isAdditionalFlowStep(
200+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
201+
DataFlow::FlowState state2
202+
) {
203+
exists(Configuration conf | conf.isAdditionalFlowStep2(node1, state1, node2, state2))
204+
}
146205
}
147206
}
148207

149208
private predicate reachableInterprocEntry(
150209
Configuration conf, DataFlow::PathNode source1, DataFlow2::PathNode source2,
151210
DataFlow::PathNode node1, DataFlow2::PathNode node2
152211
) {
153-
conf.isSourcePair(node1.getNode(), _, node2.getNode(), _) and
212+
conf.isSourcePair(node1.getNode(), node1.getState(), node2.getNode(), node2.getState()) and
154213
node1 = source1 and
155214
node2 = source2
156215
or
@@ -213,7 +272,7 @@ module ProductFlow {
213272
) {
214273
exists(DataFlow::PathNode mid1, DataFlow2::PathNode mid2 |
215274
reachableInterprocEntry(conf, source1, source2, mid1, mid2) and
216-
conf.isSinkPair(sink1.getNode(), _, sink2.getNode(), _) and
275+
conf.isSinkPair(sink1.getNode(), sink1.getState(), sink2.getNode(), sink2.getState()) and
217276
localPathStep1*(mid1, sink1) and
218277
localPathStep2*(mid2, sink2)
219278
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
int f(char * s, unsigned size) {
2+
char* buf = (char*)malloc(size);
3+
4+
strncpy(buf, s, size + 1); // wrong: copy may exceed size of buf
5+
6+
for (int i = 0; i <= size; i++) { // wrong: upper limit that is higher than size of buf
7+
cout << buf[i];
8+
}
9+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>You must ensure that you do not exceed the size of an allocation during write and read operations.
7+
If an operation attempts to write to or access an element that is outside the range of the allocation then this results in a buffer overflow.
8+
Buffer overflows can lead to anything from a segmentation fault to a security vulnerability.
9+
</p>
10+
11+
</overview>
12+
<recommendation>
13+
<p>
14+
Check the offsets and sizes used in the highlighted operations to ensure that a buffer overflow will not occur.
15+
</p>
16+
17+
</recommendation>
18+
<example><sample src="OverrunWriteProductFlow.cpp" />
19+
20+
21+
22+
</example>
23+
<references>
24+
25+
<li>I. Gerg. <em>An Overview and Example of the Buffer-Overflow Exploit</em>. IANewsletter vol 7 no 4. 2005.</li>
26+
<li>M. Donaldson. <em>Inside the Buffer Overflow Attack: Mechanism, Method &amp; Prevention</em>. SANS Institute InfoSec Reading Room. 2002.</li>
27+
28+
</references>
29+
</qhelp>
Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,131 @@
11
/**
22
* @name Overrunning write
3-
* @description TODO
3+
* @description Exceeding the size of a static array during write or access operations
4+
* may result in a buffer overflow.
45
* @kind path-problem
56
* @problem.severity error
67
* @id cpp/overrun-write
78
* @tags reliability
89
* security
10+
* external/cwe/cwe-119
11+
* external/cwe/cwe-131
912
*/
1013

1114
import cpp
1215
import experimental.semmle.code.cpp.dataflow.ProductFlow
1316
import semmle.code.cpp.ir.IR
14-
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
1517
import semmle.code.cpp.models.interfaces.Allocation
1618
import semmle.code.cpp.models.interfaces.ArrayFunction
19+
import experimental.semmle.code.cpp.semantic.analysis.RangeAnalysis
20+
import experimental.semmle.code.cpp.semantic.SemanticBound
21+
import experimental.semmle.code.cpp.semantic.SemanticExprSpecific
1722
import DataFlow::PathGraph
1823

24+
pragma[nomagic]
25+
Instruction getABoundIn(SemBound b, IRFunction func) {
26+
result = b.getExpr(0) and
27+
result.getEnclosingIRFunction() = func
28+
}
29+
30+
/**
31+
* Holds if `i <= b + delta`.
32+
*/
33+
pragma[nomagic]
34+
predicate bounded(Instruction i, Instruction b, int delta) {
35+
exists(SemBound bound, IRFunction func |
36+
semBounded(getSemanticExpr(i), bound, delta, true, _) and
37+
b = getABoundIn(bound, func) and
38+
i.getEnclosingIRFunction() = func
39+
)
40+
}
41+
42+
VariableAccess getAVariableAccess(Expr e) { e.getAChild*() = result }
43+
44+
/**
45+
* Holds if `(n, state)` pair represents the source of flow for the size
46+
* expression associated with `alloc`.
47+
*/
48+
predicate hasSize(AllocationExpr alloc, DataFlow::Node n, string state) {
49+
exists(VariableAccess va, Expr size, int delta |
50+
size = alloc.getSizeExpr() and
51+
// Get the unique variable in a size expression like `x` in `malloc(x + 1)`.
52+
va = unique( | | getAVariableAccess(size)) and
53+
// Compute `delta` as the constant difference between `x` and `x + 1`.
54+
bounded(any(Instruction instr | instr.getUnconvertedResultExpression() = size),
55+
any(LoadInstruction load | load.getUnconvertedResultExpression() = va), delta) and
56+
n.asConvertedExpr() = va.getFullyConverted() and
57+
state = delta.toString()
58+
)
59+
}
60+
61+
predicate isSinkPairImpl(
62+
CallInstruction c, DataFlow::Node bufSink, DataFlow::Node sizeSink, int delta, Expr eBuf
63+
) {
64+
exists(int bufIndex, int sizeIndex, Instruction sizeInstr, Instruction bufInstr |
65+
bufInstr = bufSink.asInstruction() and
66+
c.getArgument(bufIndex) = bufInstr and
67+
sizeInstr = sizeSink.asInstruction() and
68+
c.getStaticCallTarget().(ArrayFunction).hasArrayWithVariableSize(bufIndex, sizeIndex) and
69+
bounded(c.getArgument(sizeIndex), sizeInstr, delta) and
70+
eBuf = bufInstr.getUnconvertedResultExpression()
71+
)
72+
}
73+
1974
class StringSizeConfiguration extends ProductFlow::Configuration {
2075
StringSizeConfiguration() { this = "StringSizeConfiguration" }
2176

22-
override predicate isSourcePair(DataFlow::Node bufSource, DataFlow::Node sizeSource) {
23-
bufSource.asConvertedExpr().(AllocationExpr).getSizeExpr() = sizeSource.asConvertedExpr()
77+
override predicate isSourcePair(
78+
DataFlow::Node bufSource, DataFlow::FlowState state1, DataFlow::Node sizeSource,
79+
DataFlow::FlowState state2
80+
) {
81+
// In the case of an allocation like
82+
// ```cpp
83+
// malloc(size + 1);
84+
// ```
85+
// we use `state2` to remember that there was an offset (in this case an offset of `1`) added
86+
// to the size of the allocation. This state is then checked in `isSinkPair`.
87+
state1 instanceof DataFlow::FlowStateEmpty and
88+
hasSize(bufSource.asConvertedExpr(), sizeSource, state2)
89+
}
90+
91+
override predicate isSinkPair(
92+
DataFlow::Node bufSink, DataFlow::FlowState state1, DataFlow::Node sizeSink,
93+
DataFlow::FlowState state2
94+
) {
95+
state1 instanceof DataFlow::FlowStateEmpty and
96+
state2 = [-32 .. 32].toString() and // An arbitrary bound because we need to bound `state2`
97+
exists(int delta |
98+
isSinkPairImpl(_, bufSink, sizeSink, delta, _) and
99+
delta > state2.toInt()
100+
)
24101
}
25102

26-
override predicate isSinkPair(DataFlow::Node bufSink, DataFlow::Node sizeSink) {
27-
exists(CallInstruction c, int bufIndex, int sizeIndex |
28-
c.getStaticCallTarget().(ArrayFunction).hasArrayWithVariableSize(bufIndex, sizeIndex) and
29-
c.getArgument(bufIndex) = bufSink.asInstruction() and
30-
c.getArgument(sizeIndex) = sizeSink.asInstruction()
103+
override predicate isAdditionalFlowStep2(
104+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
105+
DataFlow::FlowState state2
106+
) {
107+
exists(AddInstruction add, Operand op, int delta, int s1, int s2 |
108+
s1 = [-32 .. 32] and // An arbitrary bound because we need to bound `state`
109+
state1 = s1.toString() and
110+
state2 = s2.toString() and
111+
add.hasOperands(node1.asOperand(), op) and
112+
semBounded(op.getDef(), any(SemZeroBound zero), delta, true, _) and
113+
node2.asInstruction() = add and
114+
s1 = s2 + delta
31115
)
32116
}
33117
}
34118

35-
// we don't actually check correctness yet. Right now the query just finds relevant source/sink pairs.
36119
from
37120
StringSizeConfiguration conf, DataFlow::PathNode source1, DataFlow2::PathNode source2,
38-
DataFlow::PathNode sink1, DataFlow2::PathNode sink2
39-
where conf.hasFlowPath(source1, source2, sink1, sink2)
40-
// TODO: pull delta out and display it
41-
select sink1.getNode(), source1, sink1, "Overrunning write allocated at $@ bounded by $@.", source1,
42-
source1.toString(), sink2, sink2.toString()
121+
DataFlow::PathNode sink1, DataFlow2::PathNode sink2, int overflow, int sinkState,
122+
CallInstruction c, DataFlow::Node sourceNode, Expr buffer, string element
123+
where
124+
conf.hasFlowPath(source1, source2, sink1, sink2) and
125+
sinkState = sink2.getState().toInt() and
126+
isSinkPairImpl(c, sink1.getNode(), sink2.getNode(), overflow + sinkState, buffer) and
127+
overflow > 0 and
128+
sourceNode = source1.getNode() and
129+
if overflow = 1 then element = " element." else element = " elements."
130+
select c.getUnconvertedResultExpression(), source1, sink1,
131+
"This write may overflow $@ by " + overflow + element, buffer, buffer.toString()

0 commit comments

Comments
 (0)