@@ -51,11 +51,13 @@ private predicate isIncorrectIntegerConversion(int sourceBitSize, int sinkBitSiz
5151}
5252
5353/**
54+ * DEPRECATED: use `Flow` instead.
55+ *
5456 * A taint-tracking configuration for reasoning about when an integer
5557 * obtained from parsing a string flows to a type conversion to a smaller
5658 * integer types, which could cause unexpected values.
5759 */
58- class ConversionWithoutBoundsCheckConfig extends TaintTracking:: Configuration {
60+ deprecated class ConversionWithoutBoundsCheckConfig extends TaintTracking:: Configuration {
5961 boolean sinkIsSigned ;
6062 int sourceBitSize ;
6163 int sinkBitSize ;
@@ -148,6 +150,115 @@ class ConversionWithoutBoundsCheckConfig extends TaintTracking::Configuration {
148150 }
149151}
150152
153+ /** Flow state for ConversionWithoutBoundsCheckConfig. */
154+ newtype MyFlowState =
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 )
159+ }
160+
161+ /** Gets the bit size of the source. */
162+ int getSourceBitSize ( MyFlowState state ) { state = TFlowstate ( _, result , _) }
163+
164+ private module ConversionWithoutBoundsCheckConfig implements DataFlow:: StateConfigSig {
165+ class FlowState = MyFlowState ;
166+
167+ predicate isSource ( DataFlow:: Node source , FlowState state ) {
168+ exists (
169+ DataFlow:: CallNode c , IntegerParser:: Range ip , int apparentBitSize , int effectiveBitSize
170+ |
171+ c .getTarget ( ) = ip and source = c .getResult ( 0 )
172+ |
173+ (
174+ apparentBitSize = ip .getTargetBitSize ( )
175+ or
176+ // If we are reading a variable, check if it is
177+ // `strconv.IntSize`, and use 0 if it is.
178+ exists ( DataFlow:: Node rawBitSize | rawBitSize = ip .getTargetBitSizeInput ( ) .getNode ( c ) |
179+ if rawBitSize = any ( Strconv:: IntSize intSize ) .getARead ( )
180+ then apparentBitSize = 0
181+ else apparentBitSize = rawBitSize .getIntValue ( )
182+ )
183+ ) and
184+ (
185+ if apparentBitSize = 0
186+ then effectiveBitSize = getIntTypeBitSize ( source .getFile ( ) )
187+ else effectiveBitSize = apparentBitSize
188+ ) and
189+ // `effectiveBitSize` could be any value between 0 and 64, but we
190+ // can round it up to the nearest size of an integer type without
191+ // changing behavior.
192+ exists ( int sourceBitSize |
193+ sourceBitSize = min ( int b | b in [ 0 , 8 , 16 , 32 , 64 ] and b >= effectiveBitSize )
194+ |
195+ state = TFlowstate ( _, sourceBitSize , _)
196+ )
197+ )
198+ }
199+
200+ /**
201+ * Holds if `sink` is a typecast to an integer type with size `bitSize` (where
202+ * 0 represents architecture-dependent) and the expression being typecast is
203+ * not also in a right-shift expression. We allow this case because it is
204+ * a common pattern to serialise `byte(v)`, `byte(v >> 8)`, and so on.
205+ */
206+ additional predicate isSinkWithBitSize (
207+ DataFlow:: TypeCastNode sink , boolean sinkIsSigned , int bitSize
208+ ) {
209+ sink .asExpr ( ) instanceof ConversionExpr and
210+ exists ( IntegerType integerType | sink .getResultType ( ) .getUnderlyingType ( ) = integerType |
211+ (
212+ bitSize = integerType .getSize ( )
213+ or
214+ not exists ( integerType .getSize ( ) ) and
215+ bitSize = getIntTypeBitSize ( sink .getFile ( ) )
216+ ) and
217+ if integerType instanceof SignedIntegerType then sinkIsSigned = true else sinkIsSigned = false
218+ ) and
219+ not exists ( ShrExpr shrExpr |
220+ shrExpr .getLeftOperand ( ) .getGlobalValueNumber ( ) =
221+ sink .getOperand ( ) .asExpr ( ) .getGlobalValueNumber ( ) or
222+ shrExpr .getLeftOperand ( ) .( AndExpr ) .getAnOperand ( ) .getGlobalValueNumber ( ) =
223+ sink .getOperand ( ) .asExpr ( ) .getGlobalValueNumber ( )
224+ )
225+ }
226+
227+ predicate isSink ( DataFlow:: Node sink , FlowState state ) {
228+ // We use the argument of the type conversion as the configuration sink so that we
229+ // can sanitize the result of the conversion to prevent flow on to further sinks
230+ // without needing to use `isSanitizerOut`, which doesn't work with flow states
231+ // (and therefore the legacy `TaintTracking::Configuration` class).
232+ exists ( boolean sinkIsSigned , int sinkBitSize |
233+ state = TFlowstate ( sinkIsSigned , _, sinkBitSize )
234+ |
235+ isSinkWithBitSize ( sink .getASuccessor ( ) , sinkIsSigned , sinkBitSize )
236+ )
237+ }
238+
239+ predicate isBarrier ( DataFlow:: Node node , FlowState state ) {
240+ exists ( boolean sinkIsSigned , int sourceBitSize , int sinkBitSize |
241+ state = TFlowstate ( sinkIsSigned , sourceBitSize , sinkBitSize )
242+ |
243+ // To catch flows that only happen on 32-bit architectures we
244+ // consider an architecture-dependent sink bit size to be 32.
245+ exists ( UpperBoundCheckGuard g , int bitSize |
246+ if sinkBitSize != 0 then bitSize = sinkBitSize else bitSize = 32
247+ |
248+ node = DataFlow:: BarrierGuard< upperBoundCheckGuard / 3 > :: getABarrierNodeForGuard ( g ) and
249+ g .isBoundFor ( bitSize , sinkIsSigned )
250+ )
251+ or
252+ exists ( int bitSize |
253+ isIncorrectIntegerConversion ( sourceBitSize , bitSize ) and
254+ isSinkWithBitSize ( node , sinkIsSigned , bitSize )
255+ )
256+ )
257+ }
258+ }
259+
260+ module Flow = TaintTracking:: GlobalWithState< ConversionWithoutBoundsCheckConfig > ;
261+
151262private predicate upperBoundCheckGuard ( DataFlow:: Node g , Expr e , boolean branch ) {
152263 g .( UpperBoundCheckGuard ) .checks ( e , branch )
153264}
0 commit comments