Skip to content

Commit d062b8b

Browse files
committed
Add list of constants sanitizer for java.util.List
1 parent 04ed09a commit d062b8b

File tree

5 files changed

+490
-0
lines changed

5 files changed

+490
-0
lines changed
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
import java
2+
private import semmle.code.java.controlflow.Guards
3+
private import semmle.code.java.dataflow.DataFlow
4+
private import semmle.code.java.dataflow.TaintTracking
5+
6+
/**
7+
* A comparison against a list of compile-time constants, sanitizing taint by
8+
* restricting to a set of known values.
9+
*/
10+
class ListOfConstantsComparisonSanitizerGuard extends TaintTracking::DefaultTaintSanitizer {
11+
ListOfConstantsComparisonSanitizerGuard() {
12+
this = DataFlow::BarrierGuard<listOfConstantsComparisonSanitizerGuard/3>::getABarrierNode()
13+
}
14+
}
15+
16+
private predicate listOfConstantsComparisonSanitizerGuard(Guard g, Expr e, boolean outcome) {
17+
exists(ListOfConstantsComparison locc |
18+
g = locc and
19+
e = locc.getExpr() and
20+
outcome = locc.getOutcome()
21+
)
22+
}
23+
24+
/** A comparison against a list of compile-time constants. */
25+
abstract class ListOfConstantsComparison extends Guard {
26+
Expr e;
27+
boolean outcome;
28+
29+
ListOfConstantsComparison() {
30+
exists(this) and
31+
outcome = [true, false]
32+
}
33+
34+
/** Gets the expression that is compared to a list of constants. */
35+
Expr getExpr() { result = e }
36+
37+
/** Gets the value of `this` when `e` is in the list of constants. */
38+
boolean getOutcome() { result = outcome }
39+
}
40+
41+
/**
42+
* Holds if the method call `mc` has only compile-time constant arguments (and
43+
* at least one argument). To account for varargs methods, we also include
44+
* a single array argument which is initialized locally with at least one
45+
* compile-time constant.
46+
*/
47+
predicate methodCallHasConstantArguments(MethodCall mc) {
48+
// f("a", "b", "c")
49+
forex(Expr e | e = mc.getAnArgument() |
50+
not e.getType() instanceof Array and e.isCompileTimeConstant()
51+
)
52+
or
53+
// String[] a = {"a", "b", "c"};
54+
// f(a)
55+
mc.getNumArgument() = 1 and
56+
mc.getArgument(0).getType() instanceof Array and
57+
exists(ArrayInit arr | DataFlow::localExprFlow(arr, mc.getArgument(0)) |
58+
forex(Expr e | e = arr.getAnInit() | e.isCompileTimeConstant())
59+
)
60+
}
61+
62+
module JavaUtilList {
63+
private class JavaUtilListContainsCall extends MethodCall {
64+
JavaUtilListContainsCall() {
65+
exists(Method m |
66+
this.getMethod() = m and
67+
m.hasName("contains") and
68+
m.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "List")
69+
)
70+
}
71+
}
72+
73+
private class NonConstantElementAddition extends Expr {
74+
NonConstantElementAddition() {
75+
exists(Method m, RefType t, MethodCall mc |
76+
this = mc.getQualifier() and
77+
mc.getMethod() = m and
78+
t = m.getDeclaringType().getSourceDeclaration().getASourceSupertype*()
79+
|
80+
t.hasQualifiedName("java.util", "List") and
81+
m.getName() = ["add", "addFirst", "addLast"] and
82+
not mc.getArgument(m.getNumberOfParameters() - 1).isCompileTimeConstant()
83+
or
84+
// If a whole collection is added then we don't try to track if it contains
85+
// only compile-time constants, and conservatively assume that it does.
86+
t.hasQualifiedName("java.util", ["Collection", "List"]) and m.getName() = "addAll"
87+
)
88+
}
89+
}
90+
91+
private predicate javaUtilListOfConstantsLocalFlowTo(Expr e) {
92+
exists(JavaUtilListOfConstants loc | DataFlow::localExprFlow(loc, e) |
93+
loc.isImmutable()
94+
or
95+
not DataFlow::localExprFlow(any(NonConstantElementAddition ncea), e)
96+
)
97+
}
98+
99+
private predicate javaUtilListOfConstantsFlowsTo(Expr e) {
100+
javaUtilListOfConstantsLocalFlowTo(e)
101+
or
102+
// Access a static final field to get an immutable list of constants.
103+
exists(Field f |
104+
f.isStatic() and
105+
f.isFinal() and
106+
forall(Expr v | v = f.getInitializer() or v = f.getAnAccess().(FieldWrite).getASource() |
107+
v = any(JavaUtilListOfConstants loc | loc.isImmutable())
108+
)
109+
|
110+
DataFlow::localExprFlow(f.getAnAccess(), e)
111+
)
112+
}
113+
114+
/**
115+
* An invocation of `java.util.List.contains` where the qualifier contains only
116+
* compile-time constants.
117+
*/
118+
private class JavaUtilListOfConstantsContains extends ListOfConstantsComparison {
119+
JavaUtilListOfConstantsContains() {
120+
exists(JavaUtilListContainsCall mc |
121+
this = mc and
122+
e = mc.getArgument(0) and
123+
outcome = true and
124+
javaUtilListOfConstantsFlowsTo(mc.getQualifier())
125+
)
126+
}
127+
}
128+
129+
/**
130+
* An instance of `java.util.List` which contains only compile-time constants.
131+
*/
132+
abstract class JavaUtilListOfConstants extends Call {
133+
abstract predicate isImmutable();
134+
}
135+
136+
/**
137+
* A invocation of a constructor of a type that extends `java.util.List`
138+
* which constructs an empty mutable list.
139+
*/
140+
private class JavaUtilListOfConstantsEmptyConstructor extends ClassInstanceExpr,
141+
JavaUtilListOfConstants
142+
{
143+
JavaUtilListOfConstantsEmptyConstructor() {
144+
this.getConstructedType()
145+
.getSourceDeclaration()
146+
.getASourceSupertype*()
147+
.hasQualifiedName("java.util", "List") and
148+
exists(Constructor c | c = this.getConstructor() |
149+
c.hasNoParameters()
150+
or
151+
c.getNumberOfParameters() = 1 and
152+
c.getParameter(0).getType().(PrimitiveType).hasName("int")
153+
)
154+
}
155+
156+
override predicate isImmutable() { none() }
157+
}
158+
159+
/**
160+
* A invocation of a constructor of a type that extends `java.util.List`
161+
* which constructs an empty mutable list.
162+
*/
163+
private class JavaUtilListOfConstantsNonEmptyConstructor extends ClassInstanceExpr,
164+
JavaUtilListOfConstants
165+
{
166+
JavaUtilListOfConstantsNonEmptyConstructor() {
167+
this.getConstructedType()
168+
.getSourceDeclaration()
169+
.getASourceSupertype*()
170+
.hasQualifiedName("java.util", "List") and
171+
exists(Constructor c | c = this.getConstructor() |
172+
c.getNumberOfParameters() = 1 and
173+
c.getParameter(0)
174+
.getType()
175+
.(RefType)
176+
.getASourceSupertype*()
177+
.hasQualifiedName("java.util", "Collection")
178+
) and
179+
javaUtilListOfConstantsFlowsTo(this.getArgument(0))
180+
}
181+
182+
override predicate isImmutable() { none() }
183+
}
184+
185+
/**
186+
* A invocation of `java.util.Arrays.asList` which constructs a mutable list.
187+
*/
188+
private class JavaUtilArraysAsList extends MethodCall, JavaUtilListOfConstants {
189+
JavaUtilArraysAsList() {
190+
exists(Method m | this.getMethod() = m |
191+
m.hasName("asList") and
192+
m.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Arrays")
193+
) and
194+
methodCallHasConstantArguments(this)
195+
}
196+
197+
override predicate isImmutable() { none() }
198+
}
199+
200+
/**
201+
* An invocation of `java.util.List.of` which constructs an immutable list
202+
* which contains only compile-time constants.
203+
*/
204+
private class JavaUtilListOfConstantsCreatedWithListOf extends MethodCall, JavaUtilListOfConstants
205+
{
206+
JavaUtilListOfConstantsCreatedWithListOf() {
207+
exists(Method m | this.getMethod() = m |
208+
m.hasName("of") and
209+
m.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "List")
210+
) and
211+
methodCallHasConstantArguments(this)
212+
}
213+
214+
override predicate isImmutable() { any() }
215+
}
216+
217+
/**
218+
* An invocation of `java.util.Collections.unmodifiableList` which constructs an immutable list
219+
* which contains only compile-time constants.
220+
*/
221+
private class JavaUtilListOfConstantsCreatedWithCollectionsUnmodifiableList extends MethodCall,
222+
JavaUtilListOfConstants
223+
{
224+
JavaUtilListOfConstantsCreatedWithCollectionsUnmodifiableList() {
225+
exists(Method m |
226+
m.hasName("unmodifiableList") and
227+
m.getDeclaringType().getSourceDeclaration().hasQualifiedName("java.util", "Collections") and
228+
this.getMethod() = m
229+
|
230+
javaUtilListOfConstantsFlowsTo(this.getArgument(0))
231+
)
232+
}
233+
234+
override predicate isImmutable() { any() }
235+
}
236+
}

java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import semmle.code.java.security.SecurityTests
77
private import semmle.code.java.security.Validation
88
private import semmle.code.java.Maps
99
private import semmle.code.java.dataflow.internal.ContainerFlow
10+
private import semmle.code.java.dataflow.ListOfConstantsSanitizer
1011
private import semmle.code.java.frameworks.spring.SpringController
1112
private import semmle.code.java.frameworks.spring.SpringHttp
1213
private import semmle.code.java.frameworks.Networking

0 commit comments

Comments
 (0)