@@ -17,11 +17,12 @@ float getMaxIntValue(int bitSize, boolean isSigned) {
1717 * Get the size of `int` or `uint` in `file`, or 0 if it is
1818 * architecture-specific.
1919 */
20- int getIntTypeBitSize ( File file ) {
20+ bindingset [ architectureSpecificBitSize]
21+ int getIntTypeBitSize ( File file , int architectureSpecificBitSize ) {
2122 file .constrainsIntBitSize ( result )
2223 or
2324 not file .constrainsIntBitSize ( _) and
24- result = 0
25+ result = architectureSpecificBitSize
2526}
2627
2728/**
@@ -90,7 +91,7 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
9091 ) and
9192 (
9293 if apparentBitSize = 0
93- then effectiveBitSize = getIntTypeBitSize ( source .getFile ( ) )
94+ then effectiveBitSize = getIntTypeBitSize ( source .getFile ( ) , 0 )
9495 else effectiveBitSize = apparentBitSize
9596 ) and
9697 // `effectiveBitSize` could be any value between 0 and 64, but we
@@ -113,7 +114,7 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
113114 bitSize = integerType .getSize ( )
114115 or
115116 not exists ( integerType .getSize ( ) ) and
116- bitSize = getIntTypeBitSize ( sink .getFile ( ) )
117+ bitSize = getIntTypeBitSize ( sink .getFile ( ) , 0 )
117118 ) and
118119 if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false
119120 ) and
@@ -152,50 +153,73 @@ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking::Confi
152153
153154/** Flow state for ConversionWithoutBoundsCheckConfig. */
154155newtype IntegerConversionFlowState =
155- /** Keep track of info about the source and potential sinks. */
156- TFlowstate ( boolean sinkIsSigned , int sourceBitSize , int sinkBitSize ) {
157- sinkIsSigned in [ true , false ] and
158- isIncorrectIntegerConversion ( sourceBitSize , sinkBitSize )
156+ TFlowstate ( int bitSize , boolean isSigned ) {
157+ bitSize = [ 0 , 8 , 16 , 32 , 64 ] and
158+ isSigned = [ true , false ]
159159 }
160160
161- /** Gets whether the sink is signed. */
162- boolean getSinkIsSigned ( IntegerConversionFlowState state ) { state = TFlowstate ( result , _, _) }
161+ /**
162+ * The integer type with bit size `bitSize` and signedness `isSigned` has
163+ * maximum value `2^result - 1`.
164+ */
165+ bindingset [ bitSize, bitSizeForZero]
166+ private int f ( int bitSize , boolean isSigned , int bitSizeForZero ) {
167+ exists ( int effectiveBitSize | effectiveBitSize = replaceZeroWith ( bitSize , bitSizeForZero ) |
168+ isSigned = true and result = effectiveBitSize - 1
169+ or
170+ isSigned = false and result = effectiveBitSize
171+ )
172+ }
163173
164- /** Gets the bit size of the source. */
165- int getSourceBitSize ( IntegerConversionFlowState state ) { state = TFlowstate ( _, result , _) }
174+ private module ConversionWithoutBoundsCheckConfig implements DataFlow:: StateConfigSig {
175+ class FlowState extends IntegerConversionFlowState {
176+ /** Gets the signedness of the flow state. */
177+ boolean getIsSigned ( ) { this = TFlowstate ( _, result ) }
166178
167- /** Gets the bit size of the sink . */
168- int getSinkBitSize ( IntegerConversionFlowState state ) { state = TFlowstate ( _ , _, result ) }
179+ /** Gets the bit size of the flow state . */
180+ int getBitSize ( ) { this = TFlowstate ( result , _) }
169181
170- private module ConversionWithoutBoundsCheckConfig implements DataFlow :: StateConfigSig {
171- class FlowState = IntegerConversionFlowState ;
182+ string toString ( ) { result = "FlowState(" + this . getBitSize ( ) + "," + this . getIsSigned ( ) + ")" }
183+ }
172184
173185 predicate isSource ( DataFlow:: Node source , FlowState state ) {
174186 exists (
175187 DataFlow:: CallNode c , IntegerParser:: Range ip , int apparentBitSize , int effectiveBitSize
176188 |
177- c .getTarget ( ) = ip and source = c . getResult ( 0 )
178- |
189+ c .getTarget ( ) = ip and
190+ source = c . getResult ( 0 ) and
179191 (
180192 apparentBitSize = ip .getTargetBitSize ( )
181193 or
182194 // If we are reading a variable, check if it is
183195 // `strconv.IntSize`, and use 0 if it is.
184- exists ( DataFlow:: Node rawBitSize | rawBitSize = ip .getTargetBitSizeInput ( ) .getNode ( c ) |
196+ exists ( DataFlow:: Node rawBitSize |
197+ rawBitSize = ip .getTargetBitSizeInput ( ) .getNode ( c ) and
185198 if rawBitSize = any ( Strconv:: IntSize intSize ) .getARead ( )
186199 then apparentBitSize = 0
187200 else apparentBitSize = rawBitSize .getIntValue ( )
188201 )
189202 ) and
190- (
191- if apparentBitSize = 0
192- then effectiveBitSize = getIntTypeBitSize ( source .getFile ( ) )
193- else effectiveBitSize = apparentBitSize
194- ) and
195- // `effectiveBitSize` could be any value between 0 and 64, but we
196- // can round it up to the nearest size of an integer type without
197- // changing behavior.
198- getSourceBitSize ( state ) = min ( int b | b in [ 0 , 8 , 16 , 32 , 64 ] and b >= effectiveBitSize )
203+ // Note that `getIntTypeBitSize` can return 0, so `effectiveBitSize`
204+ // can be 0. Also `effectiveBitSize` is not necessarily the bit-size
205+ // of an integer type - it can be any integer between 0 and 64.
206+ effectiveBitSize = replaceZeroWith ( apparentBitSize , getIntTypeBitSize ( source .getFile ( ) , 0 ) ) and
207+ // `state` represents an integer type for the sink which will overflow
208+ // if assigned the maximum value that can be parsed by `ip`. If both the
209+ // source and the sink are `int` or `uint` then overflow can only happen
210+ // if we are converting from `uint` to `int`.
211+ if state .getBitSize ( ) = 0 and effectiveBitSize = 0
212+ then ip .isSigned ( ) = false and state .getIsSigned ( ) = true
213+ else
214+ // The maximum value for a signed type with n bits is 2^(n - 1) - 1.
215+ // The maximum value for an unsigned type with n bits is 2^n - 1. To
216+ // deal with `int` and `uint` we treat them as 64-bit if they are at
217+ // the source (so we catch that flow from `int` to `int32` is incorrect
218+ // on a 64-bit architecture) and 32-bit if they are at the sink (so we
219+ // catch that flow from `int64` to `int` is incorrect on a 32-bit
220+ // architecture), and we have already dealt with the case that both
221+ // source and sink are `int` or `uint`.
222+ f ( state .getBitSize ( ) , state .getIsSigned ( ) , 32 ) < f ( effectiveBitSize , ip .isSigned ( ) , 64 )
199223 )
200224 }
201225
@@ -205,18 +229,18 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
205229 * not also in a right-shift expression. We allow this case because it is
206230 * a common pattern to serialise `byte(v)`, `byte(v >> 8)`, and so on.
207231 */
208- additional predicate isSinkWithBitSize (
209- DataFlow:: TypeCastNode sink , boolean sinkIsSigned , int bitSize
210- ) {
232+ additional predicate isSink2 ( DataFlow:: TypeCastNode sink , FlowState state ) {
211233 sink .asExpr ( ) instanceof ConversionExpr and
212234 exists ( IntegerType integerType | sink .getResultType ( ) .getUnderlyingType ( ) = integerType |
213235 (
214- bitSize = integerType .getSize ( )
236+ state . getBitSize ( ) = integerType .getSize ( )
215237 or
216238 not exists ( integerType .getSize ( ) ) and
217- bitSize = getIntTypeBitSize ( sink .getFile ( ) )
239+ state . getBitSize ( ) = getIntTypeBitSize ( sink .getFile ( ) , 0 )
218240 ) and
219- if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false
241+ if integerType instanceof SignedIntegerType
242+ then state .getIsSigned ( ) = true
243+ else state .getIsSigned ( ) = false
220244 ) and
221245 not exists ( ShrExpr shrExpr |
222246 shrExpr .getLeftOperand ( ) .getGlobalValueNumber ( ) =
@@ -231,28 +255,20 @@ private module ConversionWithoutBoundsCheckConfig implements DataFlow::StateConf
231255 // can sanitize the result of the conversion to prevent flow on to further sinks
232256 // without needing to use `isSanitizerOut`, which doesn't work with flow states
233257 // (and therefore the legacy `TaintTracking::Configuration` class).
234- isSinkWithBitSize ( sink .getASuccessor ( ) , getSinkIsSigned ( state ) , getSinkBitSize ( state ) )
258+ isSink2 ( sink .getASuccessor ( ) , state )
235259 }
236260
237261 predicate isBarrier ( DataFlow:: Node node , FlowState state ) {
238- exists ( boolean sinkIsSigned , int sinkBitSize |
239- sinkIsSigned = getSinkIsSigned ( state ) and
240- sinkBitSize = getSinkBitSize ( state )
262+ // To catch flows that only happen on 32-bit architectures we consider an
263+ // architecture-dependent sink bit size to be 32.
264+ exists ( UpperBoundCheckGuard g , int effectiveSinkBitSize |
265+ effectiveSinkBitSize = replaceZeroWith ( state .getBitSize ( ) , 32 )
241266 |
242- // To catch flows that only happen on 32-bit architectures we
243- // consider an architecture-dependent sink bit size to be 32.
244- exists ( UpperBoundCheckGuard g , int effectiveSinkBitSize |
245- if sinkBitSize != 0 then effectiveSinkBitSize = sinkBitSize else effectiveSinkBitSize = 32
246- |
247- node = DataFlow:: BarrierGuard< upperBoundCheckGuard / 3 > :: getABarrierNodeForGuard ( g ) and
248- g .isBoundFor ( effectiveSinkBitSize , sinkIsSigned )
249- )
250- or
251- exists ( int bitSize |
252- isIncorrectIntegerConversion ( getSourceBitSize ( state ) , bitSize ) and
253- isSinkWithBitSize ( node , sinkIsSigned , bitSize )
254- )
267+ node = DataFlow:: BarrierGuard< upperBoundCheckGuard / 3 > :: getABarrierNodeForGuard ( g ) and
268+ g .isBoundFor ( effectiveSinkBitSize , state .getIsSigned ( ) )
255269 )
270+ or
271+ isSink2 ( node , state )
256272 }
257273}
258274
@@ -324,3 +340,8 @@ string describeBitSize(int bitSize, int intTypeBitSize) {
324340 "a number with architecture-dependent bit-width, which is constrained to be " +
325341 intTypeBitSize + "-bit by build constraints,"
326342}
343+
344+ bindingset [ inputBitSize, replacementForZero]
345+ private int replaceZeroWith ( int inputBitSize , int replacementForZero ) {
346+ if inputBitSize = 0 then result = replacementForZero else result = inputBitSize
347+ }
0 commit comments