@@ -78,6 +78,149 @@ predicate isSinkPairImpl(
78
78
)
79
79
}
80
80
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
+
81
224
module StringSizeConfig implements ProductFlow:: StateConfigSig {
82
225
class FlowState1 = Unit ;
83
226
@@ -100,7 +243,7 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
100
243
DataFlow:: Node bufSink , FlowState1 state1 , DataFlow:: Node sizeSink , FlowState2 state2
101
244
) {
102
245
exists ( state1 ) and
103
- state2 = [ - 32 .. 32 ] and // An arbitrary bound because we need to bound `state2`
246
+ validState ( sizeSink , state2 ) and
104
247
exists ( int delta |
105
248
isSinkPairImpl ( _, bufSink , sizeSink , delta , _) and
106
249
delta > state2
@@ -124,14 +267,10 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
124
267
predicate isAdditionalFlowStep2 (
125
268
DataFlow:: Node node1 , FlowState2 state1 , DataFlow:: Node node2 , FlowState2 state2
126
269
) {
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
135
274
)
136
275
}
137
276
}
0 commit comments