Skip to content

Commit 211cb93

Browse files
committed
Add the Intent parameter of onActivityResult as a source
1 parent 520d8f5 commit 211cb93

File tree

12 files changed

+215
-6
lines changed

12 files changed

+215
-6
lines changed

java/ql/lib/semmle/code/java/dataflow/FlowSources.qll

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import semmle.code.java.frameworks.android.WebView
1717
import semmle.code.java.frameworks.JaxWS
1818
import semmle.code.java.frameworks.javase.WebSocket
1919
import semmle.code.java.frameworks.android.Android
20+
import semmle.code.java.frameworks.android.OnActivityResultSource
2021
import semmle.code.java.frameworks.android.Intent
2122
import semmle.code.java.frameworks.play.Play
2223
import semmle.code.java.frameworks.spring.SpringWeb
@@ -264,3 +265,13 @@ class ExportedAndroidContentProviderInput extends RemoteFlowSource, AndroidConte
264265

265266
override string getSourceType() { result = "Exported Android content provider source" }
266267
}
268+
269+
/**
270+
* The data Intent parameter in the `onActivityResult` method in an Activity or Fragment that
271+
* calls `startActivityForResult` with an implicit Intent.
272+
*/
273+
class OnActivityResultIntentSource extends OnActivityResultIncomingIntent, RemoteFlowSource {
274+
OnActivityResultIntentSource() { isRemoteSource() }
275+
276+
override string getSourceType() { result = "Android onActivityResult incoming Intent" }
277+
}
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
1-
/** Provides classes and predicates to track Android fragments. */
2-
31
import java
42

5-
/** The class `android.app.Fragment` */
6-
class Fragment extends Class {
7-
Fragment() { this.hasQualifiedName("android.app", "Fragment") }
3+
/** An Android Fragment. */
4+
class AndroidFragment extends Class {
5+
AndroidFragment() { this.getASupertype*().hasQualifiedName("android.app", "Fragment") }
86
}
97

108
/** The method `instantiate` of the class `android.app.Fragment`. */
119
class FragmentInstantiateMethod extends Method {
1210
FragmentInstantiateMethod() {
13-
this.getDeclaringType() instanceof Fragment and
11+
this.getDeclaringType() instanceof AndroidFragment and
1412
this.hasName("instantiate")
1513
}
1614
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/** Provides a remote flow source for Android's `Activity.onActivityResult` method. */
2+
3+
import java
4+
private import semmle.code.java.dataflow.DataFlow
5+
private import semmle.code.java.dataflow.DataFlow5
6+
private import semmle.code.java.frameworks.android.Android
7+
private import semmle.code.java.frameworks.android.Fragment
8+
private import semmle.code.java.frameworks.android.Intent
9+
10+
/**
11+
* The data Intent parameter in the `onActivityResult` method.
12+
*/
13+
class OnActivityResultIncomingIntent extends DataFlow::Node {
14+
OnActivityResultIncomingIntent() {
15+
exists(Method onActivityResult |
16+
onActivityResult.getDeclaringType() instanceof ActivityOrFragment and
17+
onActivityResult.hasName("onActivityResult") and
18+
this.asParameter() = onActivityResult.getParameter(2)
19+
)
20+
}
21+
22+
/**
23+
* Holds if this node is a remote flow source.
24+
*
25+
* This is only a source when the Activity or Fragment that implements `onActivityResult` is
26+
* also using an implicit Intent to start another Activity with `startActivityForResult`. This
27+
* means that a malicious application can intercept it to start itself and return an arbitrary
28+
* Intent to `onActivityResult`.
29+
*/
30+
predicate isRemoteSource() {
31+
exists(ImplicitStartActivityForResultConf conf, DataFlow::Node sink |
32+
conf.hasFlowTo(sink) and
33+
DataFlow::getInstanceArgument(sink.asExpr().(Argument).getCall()).getType() =
34+
this.getEnclosingCallable().getDeclaringType()
35+
)
36+
}
37+
}
38+
39+
/**
40+
* A data flow configuration for implicit intents being used in `startActivityForResult`.
41+
*/
42+
private class ImplicitStartActivityForResultConf extends DataFlow5::Configuration {
43+
ImplicitStartActivityForResultConf() { this = "ImplicitStartActivityForResultConf" }
44+
45+
override predicate isSource(DataFlow::Node source) {
46+
exists(ClassInstanceExpr cc |
47+
cc.getConstructedType() instanceof TypeIntent and source.asExpr() = cc
48+
)
49+
}
50+
51+
override predicate isSink(DataFlow::Node sink) {
52+
exists(ActivityOrFragment actOrFrag, MethodAccess startActivityForResult |
53+
startActivityForResult.getMethod().hasName("startActivityForResult") and
54+
startActivityForResult.getEnclosingCallable() = actOrFrag.getACallable() and
55+
sink.asExpr() = startActivityForResult.getArgument(0)
56+
)
57+
}
58+
59+
override predicate isBarrier(DataFlow::Node barrier) {
60+
barrier instanceof ExplicitIntentSanitizer
61+
}
62+
}
63+
64+
/** An Android Activity or Fragment. */
65+
private class ActivityOrFragment extends Class {
66+
ActivityOrFragment() {
67+
this instanceof AndroidActivity or
68+
this instanceof AndroidFragment
69+
}
70+
}

java/ql/test/library-tests/frameworks/android/sources/OnActivityResultSourceTest.expected

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import java
2+
import semmle.code.java.dataflow.FlowSources
3+
import TestUtilities.InlineFlowTest
4+
5+
class SourceValueFlowConf extends DefaultValueFlowConf {
6+
override predicate isSource(DataFlow::Node sink) { sink instanceof RemoteFlowSource }
7+
}
8+
9+
class SourceInlineFlowTest extends InlineFlowTest {
10+
override DataFlow::Configuration getTaintFlowConfig() { none() }
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example.app;
2+
3+
import android.app.Activity;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
7+
public class Safe extends Activity {
8+
9+
void sink(Object o) {}
10+
11+
public void onCreate(Bundle saved) {
12+
Intent explicitIntent = new Intent(this, Activity.class);
13+
startActivityForResult(explicitIntent, 0);
14+
}
15+
16+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
17+
sink(requestCode); // safe
18+
sink(resultCode); // safe
19+
sink(data); // Safe
20+
}
21+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.example.app;
2+
3+
import android.app.Activity;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
7+
public class Safe2 extends Activity {
8+
9+
void sink(Object o) {}
10+
11+
public void onCreate(Bundle saved) {
12+
// activityForResult not called
13+
}
14+
15+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
16+
sink(requestCode); // safe
17+
sink(resultCode); // safe
18+
sink(data); // Safe
19+
}
20+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example.app;
2+
3+
import android.app.Activity;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
7+
public class Test extends Activity {
8+
9+
void sink(Object o) {}
10+
11+
public void onCreate(Bundle saved) {
12+
Intent implicitIntent = new Intent("SOME_ACTION");
13+
startActivityForResult(implicitIntent, 0);
14+
}
15+
16+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
17+
sink(requestCode); // safe
18+
sink(resultCode); // safe
19+
sink(data); // $ hasValueFlow
20+
}
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.example.app;
2+
3+
import android.app.Fragment;
4+
import android.content.Intent;
5+
import android.os.Bundle;
6+
7+
public class TestFragment extends Fragment {
8+
9+
void sink(Object o) {}
10+
11+
public void onCreate(Bundle savedInstance) {
12+
Intent implicitIntent = new Intent("SOME_ACTION");
13+
startActivityForResult(implicitIntent, 0);
14+
}
15+
16+
@Override
17+
public void onActivityResult(int requestCode, int resultCode, Intent data) {
18+
sink(requestCode); // safe
19+
sink(resultCode); // safe
20+
sink(data); // $ hasValueFlow
21+
}
22+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.example.app;
2+
3+
import android.app.Activity;
4+
import android.content.Context;
5+
import android.content.Intent;
6+
import android.os.Bundle;
7+
8+
public class TestMissing extends Activity {
9+
10+
void sink(Object o) {}
11+
12+
public void onCreate(Bundle saved) {
13+
Helper.startNewActivity(this);
14+
}
15+
16+
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
17+
sink(requestCode); // safe
18+
sink(resultCode); // safe
19+
sink(data); // $ MISSING: $hasValueFlow
20+
}
21+
22+
static class Helper {
23+
public static void startNewActivity(Activity ctx) {
24+
Intent implicitIntent = new Intent("SOME_ACTION");
25+
ctx.startActivityForResult(implicitIntent, 0);
26+
}
27+
}
28+
}

0 commit comments

Comments
 (0)