Skip to content

Commit bc6c13b

Browse files
committed
Refactor to actually build the full flows from src to sink
Add more tests for edge cases
1 parent 4dd9e7d commit bc6c13b

File tree

4 files changed

+84
-62
lines changed

4 files changed

+84
-62
lines changed

java/ql/lib/semmle/code/java/security/AndroidIntentRedirectionQuery.qll

Lines changed: 44 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,18 @@
22

33
import java
44
import semmle.code.java.dataflow.FlowSources
5+
import semmle.code.java.dataflow.DataFlow3
56
import semmle.code.java.dataflow.TaintTracking
67
import semmle.code.java.dataflow.TaintTracking2
78
import semmle.code.java.security.AndroidIntentRedirection
89

9-
/**
10-
* Holds if taint may flow from `source` to `sink` for `IntentRedirectionConfiguration`.
11-
*
12-
* It ensures that `ChangeIntentComponent` is an intermediate step in the flow.
13-
*/
14-
predicate hasIntentRedirectionFlowPath(DataFlow::PathNode source, DataFlow::PathNode sink) {
15-
exists(IntentRedirectionConfiguration conf, DataFlow::PathNode intermediate |
16-
intermediate.getNode().(ChangeIntentComponent).hasFlowFrom(source.getNode()) and
17-
conf.hasFlowPath(intermediate, sink)
18-
)
19-
}
20-
2110
/**
2211
* A taint tracking configuration for tainted Intents being used to start Android components.
2312
*/
24-
private class IntentRedirectionConfiguration extends TaintTracking::Configuration {
13+
class IntentRedirectionConfiguration extends TaintTracking::Configuration {
2514
IntentRedirectionConfiguration() { this = "IntentRedirectionConfiguration" }
2615

27-
override predicate isSource(DataFlow::Node source) {
28-
exists(ChangeIntentComponent c |
29-
c = source
30-
or
31-
// This is needed for PathGraph to be able to build the full flow
32-
c.hasFlowFrom(source)
33-
)
34-
}
16+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
3517

3618
override predicate isSink(DataFlow::Node sink) { sink instanceof IntentRedirectionSink }
3719

@@ -44,17 +26,48 @@ private class IntentRedirectionConfiguration extends TaintTracking::Configuratio
4426
}
4527
}
4628

47-
/** An expression modifying an `Intent` component with tainted data. */
48-
private class ChangeIntentComponent extends DataFlow::Node {
49-
DataFlow::Node src;
29+
/**
30+
* A sanitizer for sinks that receive the original incoming Intent,
31+
* since its component cannot be arbitrarily set.
32+
*/
33+
private class OriginalIntentSanitizer extends IntentRedirectionSanitizer {
34+
OriginalIntentSanitizer() { any(SameIntentBeingRelaunchedConfiguration c).hasFlowTo(this) }
35+
}
36+
37+
/**
38+
* Data flow configuration used to discard incoming Intents
39+
* flowing directly to sinks that start Android components.
40+
*/
41+
private class SameIntentBeingRelaunchedConfiguration extends DataFlow3::Configuration {
42+
SameIntentBeingRelaunchedConfiguration() { this = "SameIntentBeingRelaunchedConfiguration" }
43+
44+
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
5045

51-
ChangeIntentComponent() {
52-
changesIntentComponent(this.asExpr()) and
53-
exists(TaintedIntentComponentConf conf | conf.hasFlow(src, this))
46+
override predicate isSink(DataFlow::Node sink) { sink instanceof IntentRedirectionSink }
47+
48+
override predicate isBarrier(DataFlow::Node barrier) {
49+
// Don't discard the Intent if its original component is tainted
50+
barrier instanceof IntentWithTaintedComponent
51+
}
52+
53+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
54+
// Intents being built with the copy constructor from the original Intent are discarded too
55+
exists(ClassInstanceExpr cie |
56+
cie.getConstructedType() instanceof TypeIntent and
57+
node1.asExpr() = cie.getArgument(0) and
58+
node2.asExpr() = cie
59+
)
5460
}
61+
}
5562

56-
/** Holds if `source` flows to `this`. */
57-
predicate hasFlowFrom(DataFlow::Node source) { source = src }
63+
/** An `Intent` with a tainted component. */
64+
private class IntentWithTaintedComponent extends DataFlow::Node {
65+
IntentWithTaintedComponent() {
66+
exists(IntentSetComponent setExpr, TaintedIntentComponentConf conf |
67+
setExpr.getQualifier() = this.asExpr() and
68+
conf.hasFlowTo(DataFlow::exprNode(setExpr.getSink()))
69+
)
70+
}
5871
}
5972

6073
/**
@@ -65,25 +78,8 @@ private class TaintedIntentComponentConf extends TaintTracking2::Configuration {
6578

6679
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
6780

68-
override predicate isSink(DataFlow::Node sink) { changesIntentComponent(sink.asExpr()) }
69-
}
70-
71-
/** Holds if `expr` modifies the component of an `Intent`. */
72-
private predicate changesIntentComponent(Expr expr) {
73-
any(IntentSetComponent isc).getSink() = expr
74-
or
75-
// obtaining an arbitrary Intent as a Parcelable extra
76-
expr instanceof IntentGetParcelableExtra
77-
}
78-
79-
/** A call to the method `Intent.getParcelableExtra`. */
80-
private class IntentGetParcelableExtra extends MethodAccess {
81-
IntentGetParcelableExtra() {
82-
exists(Method m |
83-
this.getMethod() = m and
84-
m.getDeclaringType() instanceof TypeIntent and
85-
m.hasName("getParcelableExtra")
86-
)
81+
override predicate isSink(DataFlow::Node sink) {
82+
any(IntentSetComponent setComponent).getSink() = sink.asExpr()
8783
}
8884
}
8985

java/ql/src/Security/CWE/CWE-940/AndroidIntentRedirection.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import java
1717
import semmle.code.java.security.AndroidIntentRedirectionQuery
1818
import DataFlow::PathGraph
1919

20-
from DataFlow::PathNode source, DataFlow::PathNode sink
21-
where hasIntentRedirectionFlowPath(source, sink)
20+
from DataFlow::PathNode source, DataFlow::PathNode sink, IntentRedirectionConfiguration conf
21+
where conf.hasFlowPath(source, sink)
2222
select sink.getNode(), source, sink,
2323
"Arbitrary Android activities or services can be started from $@.", source.getNode(),
2424
"this user input"

java/ql/test/query-tests/security/CWE-940/AndroidIntentRedirectionTest.java

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public void onCreate(Bundle savedInstanceState) {
5555
}
5656

5757
try {
58+
{
59+
// Delayed cast
60+
Object obj = getIntent().getParcelableExtra("forward_intent");
61+
Intent fwdIntent = (Intent) obj;
62+
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
63+
}
5864
{
5965
Intent fwdIntent = new Intent();
6066
fwdIntent.setClassName((Context) null, (String) intent.getExtra("className"));
@@ -132,11 +138,6 @@ public void onCreate(Bundle savedInstanceState) {
132138
fwdIntent.setComponent(component);
133139
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
134140
}
135-
{
136-
Intent originalIntent = getIntent();
137-
Intent fwdIntent = (Intent) originalIntent.getParcelableExtra("forward_intent");
138-
startActivity(originalIntent); // Safe - not an Intent obtained from the Extras
139-
}
140141
{
141142
Intent originalIntent = getIntent();
142143
ComponentName cp = new ComponentName(originalIntent.getStringExtra("packageName"),
@@ -146,10 +147,35 @@ public void onCreate(Bundle savedInstanceState) {
146147
startActivity(originalIntent); // Safe - not a tainted Intent
147148
}
148149
{
149-
// Delayed cast
150-
Object obj = getIntent().getParcelableExtra("forward_intent");
151-
Intent fwdIntent = (Intent) obj;
152-
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
150+
Intent originalIntent = getIntent();
151+
Intent fwdIntent = (Intent) originalIntent.getParcelableExtra("forward_intent");
152+
if (originalIntent.getBooleanExtra("use_fwd_intent", false)) {
153+
startActivity(fwdIntent); // $ hasAndroidIntentRedirection
154+
} else {
155+
startActivity(originalIntent); // Safe - not an Intent obtained from the Extras
156+
}
157+
}
158+
{
159+
Intent originalIntent = getIntent();
160+
originalIntent.setClassName(originalIntent.getStringExtra("package_name"),
161+
originalIntent.getStringExtra("class_name"));
162+
startActivity(originalIntent); // $ hasAndroidIntentRedirection
163+
}
164+
{
165+
Intent originalIntent = getIntent();
166+
originalIntent.setClassName("not_user_provided", "not_user_provided");
167+
startActivity(originalIntent); // Safe - component changed but not tainted
168+
}
169+
{
170+
Intent originalIntent = getIntent();
171+
Intent fwdIntent;
172+
if (originalIntent.getBooleanExtra("use_fwd_intent", false)) {
173+
fwdIntent = (Intent) originalIntent.getParcelableExtra("forward_intent");
174+
} else {
175+
fwdIntent = originalIntent;
176+
}
177+
// Conditionally tainted sinks aren't supported currently
178+
startActivity(fwdIntent); // $ MISSING: $hasAndroidIntentRedirection
153179
}
154180
} catch (Exception e) {
155181
}

java/ql/test/query-tests/security/CWE-940/AndroidIntentRedirectionTest.ql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ class HasAndroidIntentRedirectionTest extends InlineExpectationsTest {
99

1010
override predicate hasActualResult(Location location, string element, string tag, string value) {
1111
tag = "hasAndroidIntentRedirection" and
12-
exists(DataFlow::PathNode src, DataFlow::PathNode sink |
13-
hasIntentRedirectionFlowPath(src, sink)
12+
exists(DataFlow::Node src, DataFlow::Node sink, IntentRedirectionConfiguration conf |
13+
conf.hasFlow(src, sink)
1414
|
15-
sink.getNode().getLocation() = location and
15+
sink.getLocation() = location and
1616
element = sink.toString() and
1717
value = ""
1818
)

0 commit comments

Comments
 (0)