Skip to content

Commit ccbbb57

Browse files
committed
C++: Use range analysis in 'cpp/overrun-write' and accept test changes.
1 parent 51758aa commit ccbbb57

File tree

3 files changed

+121
-140
lines changed

3 files changed

+121
-140
lines changed
Lines changed: 112 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,139 @@
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+
/**
43+
* Holds if the combination of `n` and `state` represents an appropriate
44+
* source for the expression `e` suitable for use-use flow.
45+
*/
46+
private predicate hasSizeImpl(Expr e, DataFlow::Node n, string state) {
47+
// The simple case: If the size is a variable access with no qualifier we can just use the
48+
// dataflow node for that expression and no state.
49+
exists(VariableAccess va |
50+
va = e and
51+
not va instanceof FieldAccess and
52+
n.asConvertedExpr() = va.getFullyConverted() and
53+
state = "0"
54+
)
55+
or
56+
// If the size is a choice between two expressions we allow both to be nodes representing the size.
57+
exists(ConditionalExpr cond | cond = e | hasSizeImpl([cond.getThen(), cond.getElse()], n, state))
58+
or
59+
// If the size is an expression plus a constant, we pick the dataflow node of the expression and
60+
// remember the constant in the state.
61+
exists(Expr const, Expr nonconst |
62+
e.(AddExpr).hasOperands(const, nonconst) and
63+
state = const.getValue() and
64+
hasSizeImpl(nonconst, n, _)
65+
)
66+
or
67+
exists(Expr const, Expr nonconst |
68+
e.(SubExpr).hasOperands(const, nonconst) and
69+
state = "-" + const.getValue() and
70+
hasSizeImpl(nonconst, n, _)
71+
)
72+
}
73+
74+
/**
75+
* Holds if `(n, state)` pair represents the source of flow for the size
76+
* expression associated with `alloc`.
77+
*/
78+
predicate hasSize(AllocationExpr alloc, DataFlow::Node n, string state) {
79+
hasSizeImpl(alloc.getSizeExpr(), n, state)
80+
}
81+
82+
predicate isSinkPairImpl(
83+
CallInstruction c, DataFlow::Node bufSink, DataFlow::Node sizeSink, int delta, Expr eBuf,
84+
Expr eSize
85+
) {
86+
exists(int bufIndex, int sizeIndex, Instruction sizeInstr, Instruction bufInstr |
87+
bufInstr = bufSink.asInstruction() and
88+
c.getArgument(bufIndex) = bufInstr and
89+
sizeInstr = sizeSink.asInstruction() and
90+
c.getStaticCallTarget().(ArrayFunction).hasArrayWithVariableSize(bufIndex, sizeIndex) and
91+
bounded(c.getArgument(sizeIndex), sizeInstr, delta) and
92+
eSize = sizeInstr.getUnconvertedResultExpression() and
93+
eBuf = bufInstr.getUnconvertedResultExpression() and
94+
delta >= 1
95+
)
96+
}
97+
1998
class StringSizeConfiguration extends ProductFlow::Configuration {
2099
StringSizeConfiguration() { this = "StringSizeConfiguration" }
21100

22-
override predicate isSourcePair(DataFlow::Node bufSource, DataFlow::Node sizeSource) {
23-
bufSource.asConvertedExpr().(AllocationExpr).getSizeExpr() = sizeSource.asConvertedExpr()
101+
override predicate isSourcePair(
102+
DataFlow::Node bufSource, DataFlow::FlowState state1, DataFlow::Node sizeSource,
103+
DataFlow::FlowState state2
104+
) {
105+
// In the case of an allocation like
106+
// ```cpp
107+
// malloc(size + 1);
108+
// ```
109+
// we use `state2` to remember that there was an offset (in this case an offset of `1`) added
110+
// to the size of the allocation. This state is then checked in `isSinkPair`.
111+
state1 instanceof DataFlow::FlowStateEmpty and
112+
hasSize(bufSource.asConvertedExpr(), sizeSource, state2)
24113
}
25114

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()
115+
override predicate isSinkPair(
116+
DataFlow::Node bufSink, DataFlow::FlowState state1, DataFlow::Node sizeSink,
117+
DataFlow::FlowState state2
118+
) {
119+
state1 instanceof DataFlow::FlowStateEmpty and
120+
state2 = [0 .. 32].toString() and // An arbitrary bound because we need to bound `state2`
121+
exists(int delta |
122+
isSinkPairImpl(_, bufSink, sizeSink, delta, _, _) and
123+
delta > state2.toInt()
31124
)
32125
}
33126
}
34127

35-
// we don't actually check correctness yet. Right now the query just finds relevant source/sink pairs.
36128
from
37129
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()
130+
DataFlow::PathNode sink1, DataFlow2::PathNode sink2, int k, CallInstruction c,
131+
DataFlow::Node sourceNode, Expr bound, Expr buffer, string element
132+
where
133+
conf.hasFlowPath(source1, source2, sink1, sink2) and
134+
k > sink2.getState().toInt() and
135+
isSinkPairImpl(c, sink1.getNode(), sink2.getNode(), k, buffer, bound) and
136+
sourceNode = source1.getNode() and
137+
if k = 1 then element = " element." else element = " elements."
138+
select c.getUnconvertedResultExpression(), source1, sink1,
139+
"This write may overflow $@ by " + k + element, buffer, buffer.toString()

0 commit comments

Comments
 (0)