@@ -6,10 +6,14 @@ import semmle.code.java.dataflow.TaintTracking
66import semmle.code.java.security.SensitiveActions
77import semmle.code.java.frameworks.android.Compose
88private import semmle.code.java.security.Sanitizers
9+ import semmle.code.java.Constants
910
1011/** A data flow source node for sensitive logging sources. */
1112abstract class SensitiveLoggerSource extends DataFlow:: Node { }
1213
14+ /** A data flow barrier node for sensitive logging sanitizers. */
15+ abstract class SensitiveLoggerBarrier extends DataFlow:: Node { }
16+
1317/** A variable that may hold sensitive information, judging by its name. */
1418class VariableWithSensitiveName extends Variable {
1519 VariableWithSensitiveName ( ) {
@@ -40,21 +44,119 @@ private class TypeType extends RefType {
4044 }
4145}
4246
47+ /** A sanitizer that may remove sensitive information from a string before logging.
48+ *
49+ * It allows for substring operations taking the first N (or last N, for Kotlin) characters, limited to 7 or fewer.
50+ */
51+ private class SensitiveLoggerSanitizerCalled extends SensitiveLoggerBarrier {
52+ SensitiveLoggerSanitizerCalled ( ) {
53+ exists ( MethodCall mc , Method m , int limit |
54+ limit = 7 and
55+ mc .getMethod ( ) = m and
56+ (
57+ // substring in Java
58+ (
59+ m .hasQualifiedName ( "java.lang" , "String" , "substring" ) or
60+ m .hasQualifiedName ( "java.lang" , "StringBuffer" , "substring" ) or
61+ m .hasQualifiedName ( "java.lang" , "StringBuilder" , "substring" )
62+ ) and
63+ twoArgLimit ( mc , limit , false ) and
64+ this .asExpr ( ) = mc .getQualifier ( )
65+ or
66+ // Kotlin string operations, which use extension methods (so the string is the first argument)
67+ (
68+ m .hasQualifiedName ( "kotlin.text" , "StringsKt" , "substring" ) and twoArgLimit ( mc , limit , true )
69+ or
70+ m .hasQualifiedName ( "kotlin.text" , "StringsKt" , [ "take" , "takeLast" ] ) and
71+ singleArgLimit ( mc , limit , true )
72+ ) and
73+ this .asExpr ( ) = mc .getArgument ( 0 )
74+ )
75+ )
76+ }
77+ }
78+
79+ bindingset [ limit, isKotlin]
80+ predicate singleArgLimit ( MethodCall mc , int limit , boolean isKotlin ) {
81+ exists ( int argIndex , int staticInt |
82+ ( if isKotlin = true then argIndex = 1 else argIndex = 0 ) and
83+ (
84+ staticInt <= limit and
85+ staticInt > 0 and
86+ mc .getArgument ( argIndex ) .getUnderlyingExpr ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) = staticInt
87+ or exists ( CompileTimeConstantExpr cte , DataFlow:: Node source , DataFlow:: Node sink |
88+ source .asExpr ( ) = cte and
89+ cte .getIntValue ( ) = staticInt and
90+ sink .asExpr ( ) = mc .getArgument ( argIndex ) and
91+ IntegerToArgFlow:: flow ( source , sink )
92+ )
93+ )
94+ )
95+ }
96+
97+ bindingset [ limit, isKotlin]
98+ predicate twoArgLimit ( MethodCall mc , int limit , boolean isKotlin ) {
99+ exists ( int firstArgIndex , int secondArgIndex , int staticInt |
100+ staticInt <= limit and
101+ staticInt > 0 and
102+ (
103+ ( isKotlin = true and firstArgIndex = 1 and secondArgIndex = 2 )
104+ or
105+ ( isKotlin = false and firstArgIndex = 0 and secondArgIndex = 1 )
106+ )
107+ and
108+ mc .getArgument ( firstArgIndex ) .getUnderlyingExpr ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) = 0 and
109+ (
110+ mc .getArgument ( secondArgIndex ) .getUnderlyingExpr ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) = staticInt
111+ or exists ( CompileTimeConstantExpr cte , DataFlow:: Node source , DataFlow:: Node sink |
112+ source .asExpr ( ) = cte and
113+ cte .getIntValue ( ) = staticInt and
114+ sink .asExpr ( ) = mc .getArgument ( secondArgIndex ) and
115+ IntegerToArgFlow:: flow ( source , sink )
116+ )
117+ )
118+ )
119+ }
120+
121+ module IntegerToArgConfig implements DataFlow:: ConfigSig {
122+ predicate isSource ( DataFlow:: Node source ) {
123+ source .asExpr ( ) .getUnderlyingExpr ( ) instanceof CompileTimeConstantExpr and
124+ source .asExpr ( ) .getType ( ) instanceof IntegralType and
125+ source .asExpr ( ) .( CompileTimeConstantExpr ) .getIntValue ( ) > 0
126+ }
127+
128+ predicate isSink ( DataFlow:: Node sink ) {
129+ exists ( MethodCall mc |
130+ sink .asExpr ( ) = mc .getAnArgument ( )
131+ and sink .asExpr ( ) .getType ( ) instanceof IntegralType
132+ )
133+ }
134+
135+ predicate isBarrier ( DataFlow:: Node sanitizer ) { none ( ) }
136+
137+ predicate isBarrierIn ( DataFlow:: Node node ) { none ( ) }
138+ }
139+
140+ private class GenericSanitizer extends SensitiveLoggerBarrier {
141+ GenericSanitizer ( ) {
142+ this .asExpr ( ) instanceof LiveLiteral or
143+ this instanceof SimpleTypeSanitizer or
144+ this .getType ( ) instanceof TypeType
145+ }
146+ }
147+
43148/** A data-flow configuration for identifying potentially-sensitive data flowing to a log output. */
44149module SensitiveLoggerConfig implements DataFlow:: ConfigSig {
45150 predicate isSource ( DataFlow:: Node source ) { source instanceof SensitiveLoggerSource }
46151
47152 predicate isSink ( DataFlow:: Node sink ) { sinkNode ( sink , "log-injection" ) }
48153
49- predicate isBarrier ( DataFlow:: Node sanitizer ) {
50- sanitizer .asExpr ( ) instanceof LiveLiteral or
51- sanitizer instanceof SimpleTypeSanitizer or
52- sanitizer .getType ( ) instanceof TypeType
53- }
154+ predicate isBarrier ( DataFlow:: Node sanitizer ) { sanitizer instanceof SensitiveLoggerBarrier }
54155
55156 predicate isBarrierIn ( DataFlow:: Node node ) { isSource ( node ) }
56157
57158 predicate observeDiffInformedIncrementalMode ( ) { any ( ) }
58159}
59160
60161module SensitiveLoggerFlow = TaintTracking:: Global< SensitiveLoggerConfig > ;
162+ module IntegerToArgFlow = TaintTracking:: Global< IntegerToArgConfig > ;
0 commit comments