Skip to content

Commit 7e644d8

Browse files
authored
Merge pull request #6098 from atorralba/atorralba/entrypoint-field-steps
Java: Preserve taint on field-read-steps on entrypoint types
2 parents 9363d64 + c1e4c05 commit 7e644d8

File tree

5 files changed

+130
-0
lines changed

5 files changed

+130
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: majorAnalysis
3+
---
4+
* Data flow now propagates taint from remote source `Parameter` types to read steps of their fields (e.g. `tainted.publicField` or `tainted.getField()`). This also applies to their subtypes and the types of their fields, recursively.

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ private import semmle.code.java.frameworks.spring.SpringController
1111
private import semmle.code.java.frameworks.spring.SpringHttp
1212
private import semmle.code.java.frameworks.Networking
1313
private import semmle.code.java.dataflow.ExternalFlow
14+
private import semmle.code.java.dataflow.FlowSources
1415
private import semmle.code.java.dataflow.internal.DataFlowPrivate
1516
import semmle.code.java.dataflow.FlowSteps
1617
private import FlowSummaryImpl as FlowSummaryImpl
@@ -91,6 +92,8 @@ private module Cached {
9192
)
9293
or
9394
FlowSummaryImpl::Private::Steps::summaryLocalStep(src, sink, false)
95+
or
96+
entrypointFieldStep(src, sink)
9497
}
9598

9699
/**
@@ -591,3 +594,19 @@ private MethodAccess callReturningSameType(Expr ref) {
591594
ref = result.getQualifier() and
592595
result.getMethod().getReturnType() = ref.getType()
593596
}
597+
598+
private SrcRefType entrypointType() {
599+
exists(RemoteFlowSource s, RefType t |
600+
s instanceof DataFlow::ExplicitParameterNode and
601+
t = pragma[only_bind_out](s).getType() and
602+
not t instanceof TypeObject and
603+
result = t.getASubtype*().getSourceDeclaration()
604+
)
605+
or
606+
result = entrypointType().getAField().getType().(RefType).getSourceDeclaration()
607+
}
608+
609+
private predicate entrypointFieldStep(DataFlow::Node src, DataFlow::Node sink) {
610+
src = DataFlow::getFieldQualifier(sink.asExpr().(FieldRead)) and
611+
src.getType().(RefType).getSourceDeclaration() = entrypointType()
612+
}

java/ql/test/library-tests/dataflow/entrypoint-types/EntryPointTypesTest.expected

Whitespace-only changes.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
public class EntryPointTypesTest {
2+
3+
static class TestObject {
4+
public String field1;
5+
private String field2;
6+
private AnotherTestObject field3;
7+
8+
public String getField2() {
9+
return field2;
10+
}
11+
12+
public AnotherTestObject getField3() {
13+
return field3;
14+
}
15+
}
16+
17+
static class AnotherTestObject {
18+
public String field4;
19+
private String field5;
20+
21+
public String getField5() {
22+
return field5;
23+
}
24+
}
25+
26+
static class ParameterizedTestObject<T, K> {
27+
public String field6;
28+
public T field7;
29+
private K field8;
30+
31+
public K getField8() {
32+
return field8;
33+
}
34+
}
35+
36+
static class ChildObject extends ParameterizedTestObject<TestObject, Object> {
37+
public Object field9;
38+
}
39+
40+
class UnrelatedObject {
41+
public String safeField;
42+
}
43+
44+
private static void sink(String sink) {}
45+
46+
public static void test(TestObject source) {
47+
sink(source.field1); // $hasTaintFlow
48+
sink(source.getField2()); // $hasTaintFlow
49+
sink(source.getField3().field4); // $hasTaintFlow
50+
sink(source.getField3().getField5()); // $hasTaintFlow
51+
}
52+
53+
public static void testParameterized(
54+
ParameterizedTestObject<TestObject, AnotherTestObject> source) {
55+
sink(source.field6); // $hasTaintFlow
56+
sink(source.field7.field1); // $hasTaintFlow
57+
sink(source.field7.getField2()); // $hasTaintFlow
58+
sink(source.getField8().field4); // $hasTaintFlow
59+
sink(source.getField8().getField5()); // $hasTaintFlow
60+
}
61+
62+
public static void testSubtype(ParameterizedTestObject<?, ?> source) {
63+
ChildObject subtypeSource = (ChildObject) source;
64+
sink(subtypeSource.field6); // $hasTaintFlow
65+
sink(subtypeSource.field7.field1); // $hasTaintFlow
66+
sink(subtypeSource.field7.getField2()); // $hasTaintFlow
67+
sink((String) subtypeSource.getField8()); // $hasTaintFlow
68+
sink((String) subtypeSource.field9); // $hasTaintFlow
69+
// Ensure that we are not tainting every subclass of Object
70+
UnrelatedObject unrelated = (UnrelatedObject) subtypeSource.getField8();
71+
sink(unrelated.safeField); // Safe
72+
}
73+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
import TestUtilities.InlineExpectationsTest
4+
5+
class TestRemoteFlowSource extends RemoteFlowSource {
6+
TestRemoteFlowSource() { this.asParameter().hasName("source") }
7+
8+
override string getSourceType() { result = "test" }
9+
}
10+
11+
class TaintFlowConf extends TaintTracking::Configuration {
12+
TaintFlowConf() { this = "qltest:dataflow:entrypoint-types-taint" }
13+
14+
override predicate isSource(DataFlow::Node n) { n instanceof RemoteFlowSource }
15+
16+
override predicate isSink(DataFlow::Node n) {
17+
exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
18+
}
19+
}
20+
21+
class HasFlowTest extends InlineExpectationsTest {
22+
HasFlowTest() { this = "HasFlowTest" }
23+
24+
override string getARelevantTag() { result = ["hasTaintFlow"] }
25+
26+
override predicate hasActualResult(Location location, string element, string tag, string value) {
27+
tag = "hasTaintFlow" and
28+
exists(DataFlow::Node src, DataFlow::Node sink, TaintFlowConf conf | conf.hasFlow(src, sink) |
29+
sink.getLocation() = location and
30+
element = sink.toString() and
31+
value = ""
32+
)
33+
}
34+
}

0 commit comments

Comments
 (0)