@@ -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+
81224module 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