Skip to content

Commit bb0f2f7

Browse files
authored
Merge pull request #10368 from jcogs33/android-deeplink-analysis
Java: Android deeplink analysis
2 parents 5ee7986 + 25cb323 commit bb0f2f7

File tree

7 files changed

+461
-44
lines changed

7 files changed

+461
-44
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* Added data flow steps for tainted Android intents that are sent to services and receivers.
5+
* Improved the data flow step for tainted Android intents that are sent to activities so that more cases are covered.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: deprecated
3+
---
4+
* Deprecated `ContextStartActivityMethod`. Use `StartActivityMethod` instead.

java/ql/lib/semmle/code/java/frameworks/android/Intent.qll

+130-38
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ private import semmle.code.java.dataflow.DataFlow
33
private import semmle.code.java.dataflow.ExternalFlow
44
private import semmle.code.java.dataflow.FlowSteps
55

6-
/**
7-
* The class `android.content.Intent`.
8-
*/
6+
/** The class `android.content.Intent`. */
97
class TypeIntent extends Class {
108
TypeIntent() { this.hasQualifiedName("android.content", "Intent") }
119
}
@@ -15,49 +13,37 @@ class TypeComponentName extends Class {
1513
TypeComponentName() { this.hasQualifiedName("android.content", "ComponentName") }
1614
}
1715

18-
/**
19-
* The class `android.app.Activity`.
20-
*/
16+
/** The class `android.app.Activity`. */
2117
class TypeActivity extends Class {
2218
TypeActivity() { this.hasQualifiedName("android.app", "Activity") }
2319
}
2420

25-
/**
26-
* The class `android.app.Service`.
27-
*/
21+
/** The class `android.app.Service`. */
2822
class TypeService extends Class {
2923
TypeService() { this.hasQualifiedName("android.app", "Service") }
3024
}
3125

32-
/**
33-
* The class `android.content.Context`.
34-
*/
26+
/** The class `android.content.Context`. */
3527
class TypeContext extends RefType {
3628
// Not inlining this makes it more likely to be used as a sentinel,
3729
// which is useful when running Android queries on non-Android projects.
3830
pragma[noinline]
3931
TypeContext() { this.hasQualifiedName("android.content", "Context") }
4032
}
4133

42-
/**
43-
* The class `android.content.BroadcastReceiver`.
44-
*/
34+
/** The class `android.content.BroadcastReceiver`. */
4535
class TypeBroadcastReceiver extends Class {
4636
TypeBroadcastReceiver() { this.hasQualifiedName("android.content", "BroadcastReceiver") }
4737
}
4838

49-
/**
50-
* The method `Activity.getIntent`
51-
*/
39+
/** The method `Activity.getIntent` */
5240
class AndroidGetIntentMethod extends Method {
5341
AndroidGetIntentMethod() {
5442
this.hasName("getIntent") and this.getDeclaringType() instanceof TypeActivity
5543
}
5644
}
5745

58-
/**
59-
* The method `BroadcastReceiver.onReceive`.
60-
*/
46+
/** The method `BroadcastReceiver.onReceive`. */
6147
class AndroidReceiveIntentMethod extends Method {
6248
AndroidReceiveIntentMethod() {
6349
this.hasName("onReceive") and this.getDeclaringType() instanceof TypeBroadcastReceiver
@@ -77,25 +63,68 @@ class AndroidServiceIntentMethod extends Method {
7763

7864
/**
7965
* The method `Context.startActivity` or `startActivities`.
66+
*
67+
* DEPRECATED: Use `StartActivityMethod` instead.
8068
*/
81-
class ContextStartActivityMethod extends Method {
69+
deprecated class ContextStartActivityMethod extends Method {
8270
ContextStartActivityMethod() {
8371
(this.hasName("startActivity") or this.hasName("startActivities")) and
8472
this.getDeclaringType() instanceof TypeContext
8573
}
8674
}
8775

8876
/**
89-
* Specifies that if an `Intent` is tainted, then so are its synthetic fields.
77+
* The method `Context.startActivity`, `Context.startActivities`,
78+
* `Activity.startActivity`,`Activity.startActivities`,
79+
* `Activity.startActivityForResult`, `Activity.startActivityIfNeeded`,
80+
* `Activity.startNextMatchingActivity`, `Activity.startActivityFromChild`,
81+
* or `Activity.startActivityFromFragment`.
9082
*/
83+
class StartActivityMethod extends Method {
84+
StartActivityMethod() {
85+
this.getName().matches("start%Activit%") and
86+
(
87+
this.getDeclaringType() instanceof TypeContext or
88+
this.getDeclaringType() instanceof TypeActivity
89+
)
90+
}
91+
}
92+
93+
/**
94+
* The method `Context.sendBroadcast`, `sendBroadcastAsUser`,
95+
* `sendOrderedBroadcast`, `sendOrderedBroadcastAsUser`,
96+
* `sendStickyBroadcast`, `sendStickyBroadcastAsUser`,
97+
* `sendStickyOrderedBroadcast`, `sendStickyOrderedBroadcastAsUser`,
98+
* or `sendBroadcastWithMultiplePermissions`.
99+
*/
100+
class SendBroadcastMethod extends Method {
101+
SendBroadcastMethod() {
102+
this.getName().matches("send%Broadcast%") and
103+
this.getDeclaringType() instanceof TypeContext
104+
}
105+
}
106+
107+
/**
108+
* The method `Context.startService`, `startForegroundService`,
109+
* `bindIsolatedService`, `bindService`, or `bindServiceAsUser`.
110+
*/
111+
class StartServiceMethod extends Method {
112+
StartServiceMethod() {
113+
this.hasName([
114+
"startService", "startForegroundService", "bindIsolatedService", "bindService",
115+
"bindServiceAsUser"
116+
]) and
117+
this.getDeclaringType() instanceof TypeContext
118+
}
119+
}
120+
121+
/** Specifies that if an `Intent` is tainted, then so are its synthetic fields. */
91122
private class IntentFieldsInheritTaint extends DataFlow::SyntheticFieldContent,
92123
TaintInheritingContent {
93124
IntentFieldsInheritTaint() { this.getField().matches("android.content.Intent.%") }
94125
}
95126

96-
/**
97-
* The method `Intent.getParcelableExtra`.
98-
*/
127+
/** The method `Intent.getParcelableExtra`. */
99128
class IntentGetParcelableExtraMethod extends Method {
100129
IntentGetParcelableExtraMethod() {
101130
this.hasName("getParcelableExtra") and
@@ -157,9 +186,7 @@ private class BundleExtrasSyntheticField extends SyntheticField {
157186
override RefType getType() { result instanceof AndroidBundle }
158187
}
159188

160-
/**
161-
* Holds if extras may be implicitly read from the Intent `node`.
162-
*/
189+
/** Holds if extras may be implicitly read from the Intent `node`. */
163190
predicate allowIntentExtrasImplicitRead(DataFlow::Node node, DataFlow::Content c) {
164191
node.getType() instanceof TypeIntent and
165192
(
@@ -194,25 +221,90 @@ class GrantWriteUriPermissionFlag extends GrantUriPermissionFlag {
194221
GrantWriteUriPermissionFlag() { this.hasName("FLAG_GRANT_WRITE_URI_PERMISSION") }
195222
}
196223

224+
/** An instantiation of `android.content.Intent`. */
225+
private class NewIntent extends ClassInstanceExpr {
226+
NewIntent() { this.getConstructedType() instanceof TypeIntent }
227+
228+
/** Gets the `Class<?>` argument of this call. */
229+
Argument getClassArg() {
230+
result.getType() instanceof TypeClass and
231+
result = this.getAnArgument()
232+
}
233+
}
234+
235+
/** A call to a method that starts an Android component. */
236+
private class StartComponentMethodAccess extends MethodAccess {
237+
StartComponentMethodAccess() {
238+
this.getMethod().overrides*(any(StartActivityMethod m)) or
239+
this.getMethod().overrides*(any(StartServiceMethod m)) or
240+
this.getMethod().overrides*(any(SendBroadcastMethod m))
241+
}
242+
243+
/** Gets the intent argument of this call. */
244+
Argument getIntentArg() {
245+
result.getType() instanceof TypeIntent and
246+
result = this.getAnArgument()
247+
}
248+
249+
/** Holds if this targets a component of type `targetType`. */
250+
predicate targetsComponentType(RefType targetType) {
251+
exists(NewIntent newIntent |
252+
DataFlow::localExprFlow(newIntent, this.getIntentArg()) and
253+
newIntent.getClassArg().getType().(ParameterizedType).getATypeArgument() = targetType
254+
)
255+
}
256+
}
257+
197258
/**
198-
* A value-preserving step from the Intent argument of a `startActivity` call to
199-
* a `getIntent` call in the Activity the Intent pointed to in its constructor.
259+
* A value-preserving step from the intent argument of a `startActivity` call to
260+
* a `getIntent` call in the activity the intent targeted in its constructor.
200261
*/
201262
private class StartActivityIntentStep extends AdditionalValueStep {
202263
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
203-
exists(MethodAccess startActivity, MethodAccess getIntent, ClassInstanceExpr newIntent |
204-
startActivity.getMethod().overrides*(any(ContextStartActivityMethod m)) and
264+
exists(StartComponentMethodAccess startActivity, MethodAccess getIntent |
265+
startActivity.getMethod().overrides*(any(StartActivityMethod m)) and
205266
getIntent.getMethod().overrides*(any(AndroidGetIntentMethod m)) and
206-
newIntent.getConstructedType() instanceof TypeIntent and
207-
DataFlow::localExprFlow(newIntent, startActivity.getArgument(0)) and
208-
newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
209-
getIntent.getReceiverType() and
210-
n1.asExpr() = startActivity.getArgument(0) and
267+
startActivity.targetsComponentType(getIntent.getReceiverType()) and
268+
n1.asExpr() = startActivity.getIntentArg() and
211269
n2.asExpr() = getIntent
212270
)
213271
}
214272
}
215273

274+
/**
275+
* A value-preserving step from the intent argument of a `sendBroadcast` call to
276+
* the intent parameter in the `onReceive` method of the receiver the
277+
* intent targeted in its constructor.
278+
*/
279+
private class SendBroadcastReceiverIntentStep extends AdditionalValueStep {
280+
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
281+
exists(StartComponentMethodAccess sendBroadcast, Method onReceive |
282+
sendBroadcast.getMethod().overrides*(any(SendBroadcastMethod m)) and
283+
onReceive.overrides*(any(AndroidReceiveIntentMethod m)) and
284+
sendBroadcast.targetsComponentType(onReceive.getDeclaringType()) and
285+
n1.asExpr() = sendBroadcast.getIntentArg() and
286+
n2.asParameter() = onReceive.getParameter(1)
287+
)
288+
}
289+
}
290+
291+
/**
292+
* A value-preserving step from the intent argument of a `startService` call to
293+
* the intent parameter in an `AndroidServiceIntentMethod` of the service the
294+
* intent targeted in its constructor.
295+
*/
296+
private class StartServiceIntentStep extends AdditionalValueStep {
297+
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
298+
exists(StartComponentMethodAccess startService, Method serviceIntent |
299+
startService.getMethod().overrides*(any(StartServiceMethod m)) and
300+
serviceIntent.overrides*(any(AndroidServiceIntentMethod m)) and
301+
startService.targetsComponentType(serviceIntent.getDeclaringType()) and
302+
n1.asExpr() = startService.getIntentArg() and
303+
n2.asParameter() = serviceIntent.getParameter(0)
304+
)
305+
}
306+
}
307+
216308
private class IntentBundleFlowSteps extends SummaryModelCsv {
217309
override predicate row(string row) {
218310
row =

java/ql/test/library-tests/frameworks/android/intent/AndroidManifest.xml

+25
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,30 @@
1818
android:exported="false">
1919
</activity>
2020

21+
<activity
22+
android:name=".TestStartActivityToGetIntent.SafeActivity"
23+
android:exported="false">
24+
</activity>
25+
26+
<service
27+
android:name=".TestStartServiceToGetIntent.SomeService"
28+
android:exported="false">
29+
</service>
30+
31+
<service
32+
android:name=".TestStartServiceToGetIntent.SafeService"
33+
android:exported="false">
34+
</service>
35+
36+
<receiver
37+
android:name=".TestStartBroadcastReceiverToGetIntent.SomeBroadcastReceiver"
38+
android:exported="false">
39+
</receiver>
40+
41+
<receiver
42+
android:name=".TestStartBroadcastReceiverToGetIntent.SafeBroadcastReceiver"
43+
android:exported="false">
44+
</receiver>
45+
2146
</application>
2247
</manifest>

java/ql/test/library-tests/frameworks/android/intent/TestStartActivityToGetIntent.java

+74-6
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,90 @@
44

55
public class TestStartActivityToGetIntent {
66

7-
static Object source() {
7+
static Object source(String kind) {
88
return null;
99
}
1010

1111
static void sink(Object sink) {}
1212

13-
public void test(Context ctx) {
14-
Intent intent = new Intent(null, SomeActivity.class);
15-
intent.putExtra("data", (String) source());
16-
ctx.startActivity(intent);
13+
public void test(Context ctx, Activity act) {
14+
15+
// test all methods that start an activity
16+
{
17+
Intent intent = new Intent(null, SomeActivity.class);
18+
intent.putExtra("data", (String) source("ctx-start"));
19+
ctx.startActivity(intent);
20+
}
21+
{
22+
Intent intent = new Intent(null, SomeActivity.class);
23+
intent.putExtra("data", (String) source("ctx-start-acts"));
24+
Intent[] intents = new Intent[] {intent};
25+
ctx.startActivities(intents);
26+
}
27+
{
28+
Intent intent = new Intent(null, SomeActivity.class);
29+
intent.putExtra("data", (String) source("act-start"));
30+
act.startActivity(intent);
31+
}
32+
{
33+
Intent intent = new Intent(null, SomeActivity.class);
34+
intent.putExtra("data", (String) source("act-start-acts"));
35+
Intent[] intents = new Intent[] {intent};
36+
act.startActivities(intents);
37+
}
38+
{
39+
Intent intent = new Intent(null, SomeActivity.class);
40+
intent.putExtra("data", (String) source("start-for-result"));
41+
act.startActivityForResult(intent, 0);
42+
}
43+
{
44+
Intent intent = new Intent(null, SomeActivity.class);
45+
intent.putExtra("data", (String) source("start-if-needed"));
46+
act.startActivityIfNeeded(intent, 0);
47+
}
48+
{
49+
Intent intent = new Intent(null, SomeActivity.class);
50+
intent.putExtra("data", (String) source("start-matching"));
51+
act.startNextMatchingActivity(intent);
52+
}
53+
{
54+
Intent intent = new Intent(null, SomeActivity.class);
55+
intent.putExtra("data", (String) source("start-from-child"));
56+
act.startActivityFromChild(null, intent, 0);
57+
}
58+
{
59+
Intent intent = new Intent(null, SomeActivity.class);
60+
intent.putExtra("data", (String) source("start-from-frag"));
61+
act.startActivityFromFragment(null, intent, 0);
62+
}
63+
64+
// test 4-arg Intent constructor
65+
{
66+
Intent intent = new Intent(null, null, null, SomeActivity.class);
67+
intent.putExtra("data", (String) source("4-arg"));
68+
ctx.startActivity(intent);
69+
}
70+
71+
// safe test
72+
{
73+
Intent intent = new Intent(null, SafeActivity.class);
74+
intent.putExtra("data", "safe");
75+
ctx.startActivity(intent);
76+
}
1777
}
1878

1979
static class SomeActivity extends Activity {
2080

2181
public void test() {
22-
sink(getIntent().getStringExtra("data")); // $ hasValueFlow
82+
sink(getIntent().getStringExtra("data")); // $ hasValueFlow=ctx-start hasValueFlow=act-start hasValueFlow=start-for-result hasValueFlow=start-if-needed hasValueFlow=start-matching hasValueFlow=start-from-child hasValueFlow=start-from-frag hasValueFlow=4-arg MISSING: hasValueFlow=ctx-start-acts hasValueFlow=act-start-acts
83+
}
84+
85+
}
86+
87+
static class SafeActivity extends Activity {
88+
89+
public void test() {
90+
sink(getIntent().getStringExtra("data")); // Safe
2391
}
2492
}
2593
}

0 commit comments

Comments
 (0)