Skip to content

Commit 75e36e8

Browse files
committed
C++: Precompute the set of necessary states.
1 parent f7924bd commit 75e36e8

File tree

2 files changed

+110
-249
lines changed

2 files changed

+110
-249
lines changed

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

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,113 @@ 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+
(inLoop(n) implies value = [-2 .. 2]) and
153+
(
154+
hasSize(_, n.getNode(), value)
155+
or
156+
exists(int delta, PathNode n0 |
157+
n0.getASuccessor() = n and
158+
validStateImpl(n0, value) and
159+
isSinkPairImpl(_, _, n.getNode(), delta, _) and
160+
delta > value
161+
)
162+
or
163+
exists(PathNode n0, DataFlow::Node node, int value0 |
164+
n0.getASuccessor() = n and
165+
validStateImpl(n0, value0) and
166+
node = n.getNode()
167+
|
168+
exists(AddInstruction add, Operand op1, Operand op2, int delta |
169+
add = node.asInstruction() and
170+
add.hasOperands(op1, op2) and
171+
value0 = value + delta and
172+
semBounded(getSemanticExpr([op1, op2].getDef()), any(SemZeroBound zero), delta, true, _)
173+
)
174+
or
175+
not node.asInstruction() instanceof AddInstruction and
176+
value = value0
177+
)
178+
)
179+
}
180+
181+
predicate validState(DataFlow::Node n, int value) {
182+
validStateImpl(any(PathNode pn | pn.getNode() = n), value)
183+
}
184+
}
185+
186+
import ValidState
187+
81188
module StringSizeConfig implements ProductFlow::StateConfigSig {
82189
class FlowState1 = Unit;
83190

@@ -100,7 +207,7 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
100207
DataFlow::Node bufSink, FlowState1 state1, DataFlow::Node sizeSink, FlowState2 state2
101208
) {
102209
exists(state1) and
103-
state2 = [-32 .. 32] and // An arbitrary bound because we need to bound `state2`
210+
validState(sizeSink, state2) and
104211
exists(int delta |
105212
isSinkPairImpl(_, bufSink, sizeSink, delta, _) and
106213
delta > state2
@@ -120,8 +227,8 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
120227
predicate isAdditionalFlowStep2(
121228
DataFlow::Node node1, FlowState2 state1, DataFlow::Node node2, FlowState2 state2
122229
) {
230+
validState(node2, state2) and
123231
exists(AddInstruction add, Operand op, int delta, int s1, int s2 |
124-
s1 = [-32 .. 32] and // An arbitrary bound because we need to bound `state`
125232
state1 = s1 and
126233
state2 = s2 and
127234
add.hasOperands(node1.asOperand(), op) and

0 commit comments

Comments
 (0)