Skip to content

Commit 2dcdc71

Browse files
authored
Merge pull request github#13142 from MathiasVP/precompute-states-in-overrun-write
C++: Restrict flow-state space of `cpp/overrun-write`
2 parents f5b04ab + a5632a2 commit 2dcdc71

File tree

2 files changed

+148
-255
lines changed

2 files changed

+148
-255
lines changed

cpp/ql/src/experimental/Likely Bugs/OverrunWriteProductFlow.ql

Lines changed: 148 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,149 @@ predicate isSinkPairImpl(
7878
)
7979
}
8080

81+
module ValidState {
82+
/**
83+
* In the `StringSizeConfig` configuration we use an integer as the flow state for the second
84+
* projection of the dataflow graph. The integer represents an offset that is added to the
85+
* size of the allocation. For example, given:
86+
* ```cpp
87+
* char* p = new char[size + 1];
88+
* size += 1;
89+
* memset(p, 0, size);
90+
* ```
91+
* the initial flow state is `1`. This represents the fact that `size + 1` is a valid bound
92+
* for the size of the allocation pointed to by `p`. After updating the size using `+=`, the
93+
* flow state changes to `0`, which represents the fact that `size + 0` is a valid bound for
94+
* the allocation.
95+
*
96+
* So we need to compute a set of valid integers that represent the offset applied to the
97+
* size. We do this in two steps:
98+
* 1. We first perform the dataflow traversal that the second projection of the product-flow
99+
* library will perform, and visit all the places where the size argument is modified.
100+
* 2. Once that dataflow traversal is done, we accumulate the offsets added at each places
101+
* where the offset is modified (see `validStateImpl`).
102+
*
103+
* Because we want to guarantee that each place where we modify the offset has a `PathNode`
104+
* we "flip" a boolean flow state in each `isAdditionalFlowStep`. This ensures that the node
105+
* has a corresponding `PathNode`.
106+
*/
107+
private module ValidStateConfig implements DataFlow::StateConfigSig {
108+
class FlowState = boolean;
109+
110+
predicate isSource(DataFlow::Node source, FlowState state) {
111+
hasSize(_, source, _) and
112+
state = false
113+
}
114+
115+
predicate isSink(DataFlow::Node sink, FlowState state) {
116+
isSinkPairImpl(_, _, sink, _, _) and
117+
state = [false, true]
118+
}
119+
120+
predicate isBarrier(DataFlow::Node node, FlowState state) { none() }
121+
122+
predicate isAdditionalFlowStep(
123+
DataFlow::Node node1, FlowState state1, DataFlow::Node node2, FlowState state2
124+
) {
125+
exists(AddInstruction add, Operand op, int delta |
126+
add.hasOperands(node1.asOperand(), op) and
127+
semBounded(getSemanticExpr(op.getDef()), any(SemZeroBound zero), delta, true, _) and
128+
node2.asInstruction() = add and
129+
state1 = [false, true] and
130+
state2 = state1.booleanNot()
131+
)
132+
}
133+
134+
predicate includeHiddenNodes() { any() }
135+
}
136+
137+
private import DataFlow::GlobalWithState<ValidStateConfig>
138+
139+
private predicate inLoop(PathNode n) { n.getASuccessor+() = n }
140+
141+
/**
142+
* Holds if `value` is a possible offset for `n`.
143+
*
144+
* To ensure termination, we limit `value` to be in the
145+
* range `[-2, 2]` if the node is part of a loop. Without
146+
* this restriction we wouldn't terminate on an example like:
147+
* ```cpp
148+
* while(unknown()) { size++; }
149+
* ```
150+
*/
151+
private predicate validStateImpl(PathNode n, int value) {
152+
// If the dataflow node depends recursively on itself we restrict the range.
153+
(inLoop(n) implies value = [-2 .. 2]) and
154+
(
155+
// For the dataflow source we have an allocation such as `malloc(size + k)`,
156+
// and the value of the flow-state is then `k`.
157+
hasSize(_, n.getNode(), value)
158+
or
159+
// For a dataflow sink any `value` that is strictly smaller than the delta
160+
// needs to be a valid flow-state. That is, for a snippet like:
161+
// ```
162+
// p = b ? new char[size] : new char[size + 1];
163+
// memset(p, 0, size + 2);
164+
// ```
165+
// the valid flow-states at the `memset` must include the set `{0, 1}` since the
166+
// flow-state at `new char[size]` is `0`, and the flow-state at `new char[size + 1]`
167+
// is `1`.
168+
//
169+
// So we find a valid flow-state at the sink's predecessor, and use the definition
170+
// of our sink predicate to compute the valid flow-states at the sink.
171+
exists(int delta, PathNode n0 |
172+
n0.getASuccessor() = n and
173+
validStateImpl(n0, value) and
174+
isSinkPairImpl(_, _, n.getNode(), delta, _) and
175+
delta > value
176+
)
177+
or
178+
// For a non-source and non-sink node there is two cases to consider.
179+
// 1. A node where we have to update the flow-state, or
180+
// 2. A node that doesn't update the flow-state.
181+
//
182+
// For case 1, we compute the new flow-state by adding the constant operand of the
183+
// `AddInstruction` to the flow-state of any predecessor node.
184+
// For case 2 we simply propagate the valid flow-states from the predecessor node to
185+
// the next one.
186+
exists(PathNode n0, DataFlow::Node node0, DataFlow::Node node, int value0 |
187+
n0.getASuccessor() = n and
188+
validStateImpl(n0, value0) and
189+
node = n.getNode() and
190+
node0 = n0.getNode()
191+
|
192+
exists(int delta |
193+
isAdditionalFlowStep2(node0, node, delta) and
194+
value0 = value + delta
195+
)
196+
or
197+
not isAdditionalFlowStep2(node0, node, _) and
198+
value = value0
199+
)
200+
)
201+
}
202+
203+
predicate validState(DataFlow::Node n, int value) {
204+
validStateImpl(any(PathNode pn | pn.getNode() = n), value)
205+
}
206+
}
207+
208+
import ValidState
209+
210+
/**
211+
* Holds if `node2` is a dataflow node that represents an addition of two operands `op1`
212+
* and `op2` such that:
213+
* 1. `node1` is the dataflow node that represents `op1`, and
214+
* 2. the value of `op2` can be upper bounded by `delta.`
215+
*/
216+
predicate isAdditionalFlowStep2(DataFlow::Node node1, DataFlow::Node node2, int delta) {
217+
exists(AddInstruction add, Operand op |
218+
add.hasOperands(node1.asOperand(), op) and
219+
semBounded(getSemanticExpr(op.getDef()), any(SemZeroBound zero), delta, true, _) and
220+
node2.asInstruction() = add
221+
)
222+
}
223+
81224
module StringSizeConfig implements ProductFlow::StateConfigSig {
82225
class FlowState1 = Unit;
83226

@@ -100,7 +243,7 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
100243
DataFlow::Node bufSink, FlowState1 state1, DataFlow::Node sizeSink, FlowState2 state2
101244
) {
102245
exists(state1) and
103-
state2 = [-32 .. 32] and // An arbitrary bound because we need to bound `state2`
246+
validState(sizeSink, state2) and
104247
exists(int delta |
105248
isSinkPairImpl(_, bufSink, sizeSink, delta, _) and
106249
delta > state2
@@ -124,14 +267,10 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
124267
predicate isAdditionalFlowStep2(
125268
DataFlow::Node node1, FlowState2 state1, DataFlow::Node node2, FlowState2 state2
126269
) {
127-
exists(AddInstruction add, Operand op, int delta, int s1, int s2 |
128-
s1 = [-32 .. 32] and // An arbitrary bound because we need to bound `state`
129-
state1 = s1 and
130-
state2 = s2 and
131-
add.hasOperands(node1.asOperand(), op) and
132-
semBounded(getSemanticExpr(op.getDef()), any(SemZeroBound zero), delta, true, _) and
133-
node2.asInstruction() = add and
134-
s1 = s2 + delta
270+
validState(node2, state2) and
271+
exists(int delta |
272+
isAdditionalFlowStep2(node1, node2, delta) and
273+
state1 = state2 + delta
135274
)
136275
}
137276
}

0 commit comments

Comments
 (0)