Skip to content

Commit b16b027

Browse files
authored
Merge pull request github#6779 from atorralba/atorralba/android-implicit-pending-intents
Java: CWE-927 - Query to detect the use of implicit PendingIntents
2 parents 9819752 + f103d45 commit b16b027

File tree

237 files changed

+8599
-5479
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

237 files changed

+8599
-5479
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ private module Frameworks {
118118
private import semmle.code.java.security.InformationLeak
119119
private import semmle.code.java.security.Files
120120
private import semmle.code.java.security.GroovyInjection
121+
private import semmle.code.java.security.ImplicitPendingIntents
121122
private import semmle.code.java.security.JexlInjectionSinkModels
122123
private import semmle.code.java.security.JndiInjection
123124
private import semmle.code.java.security.LdapInjection
@@ -685,7 +686,7 @@ class SyntheticField extends string {
685686

686687
private predicate parseSynthField(string c, string f) {
687688
specSplit(_, c, _) and
688-
c.regexpCapture("SyntheticField\\[([.a-zA-Z0-9]+)\\]", 1) = f
689+
c.regexpCapture("SyntheticField\\[([.a-zA-Z0-9$]+)\\]", 1) = f
689690
}
690691

691692
/** Holds if the specification component parses as a `Content`. */

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,24 @@ class AndroidBundle extends Class {
8787
AndroidBundle() { this.getASupertype*().hasQualifiedName("android.os", "BaseBundle") }
8888
}
8989

90-
/** An `Intent` that explicitly sets a destination component. */
90+
/**
91+
* An `Intent` that explicitly sets a destination component.
92+
*
93+
* The `Intent` is not considered explicit if a `null` value ever flows to the destination
94+
* component, even if only conditionally.
95+
*
96+
* For example, in the following code, `intent` is not considered an `ExplicitIntent`:
97+
* ```java
98+
* intent.setClass(condition ? null : "MyClass");
99+
* ```
100+
*/
91101
class ExplicitIntent extends Expr {
92102
ExplicitIntent() {
93103
exists(MethodAccess ma, Method m |
94104
ma.getMethod() = m and
95105
m.getDeclaringType() instanceof TypeIntent and
96106
m.hasName(["setPackage", "setClass", "setClassName", "setComponent"]) and
107+
not exists(NullLiteral nullLiteral | DataFlow::localExprFlow(nullLiteral, ma.getAnArgument())) and
97108
ma.getQualifier() = this
98109
)
99110
or

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ private class NotificationBuildersSummaryModels extends SummaryModelCsv {
3232
"android.app;Notification$Builder;true;setExtras;;;Argument[0];SyntheticField[android.content.Intent.extras] of Argument[-1];value",
3333
"android.app;Notification$Builder;true;setDeleteIntent;;;Argument[0];Argument[-1];taint",
3434
"android.app;Notification$Builder;true;setPublicVersion;;;Argument[0];Argument[-1];taint",
35+
"android.app;Notification$Style;true;build;;;Argument[-1];ReturnValue;taint",
36+
"android.app;Notification$BigPictureStyle;true;BigPictureStyle;(Builder);;Argument[0];Argument[-1];taint",
37+
"android.app;Notification$BigTextStyle;true;BigTextStyle;(Builder);;Argument[0];Argument[-1];taint",
38+
"android.app;Notification$InboxStyle;true;InboxStyle;(Builder);;Argument[0];Argument[-1];taint",
39+
"android.app;Notification$MediaStyle;true;MediaStyle;(Builder);;Argument[0];Argument[-1];taint",
3540
// Fluent models
3641
"android.app;Notification$Action$Builder;true;" +
3742
[
@@ -52,7 +57,18 @@ private class NotificationBuildersSummaryModels extends SummaryModelCsv {
5257
"setShortcutId", "setShowWhen", "setSmallIcon", "setSortKey", "setSound", "setStyle",
5358
"setSubText", "setTicker", "setTimeoutAfter", "setUsesChronometer", "setVibrate",
5459
"setVisibility", "setWhen"
55-
] + ";;;Argument[-1];ReturnValue;value"
60+
] + ";;;Argument[-1];ReturnValue;value",
61+
"android.app;Notification$BigPictureStyle;true;" +
62+
[
63+
"bigLargeIcon", "bigPicture", "setBigContentTitle", "setContentDescription",
64+
"setSummaryText", "showBigPictureWhenCollapsed"
65+
] + ";;;Argument[-1];ReturnValue;value",
66+
"android.app;Notification$BigTextStyle;true;" +
67+
["bigText", "setBigContentTitle", "setSummaryText"] + ";;;Argument[-1];ReturnValue;value",
68+
"android.app;Notification$InboxStyle;true;" +
69+
["addLine", "setBigContentTitle", "setSummaryText"] + ";;;Argument[-1];ReturnValue;value",
70+
"android.app;Notification$MediaStyle;true;" +
71+
["setMediaSession", "setShowActionsInCompactView"] + ";;;Argument[-1];ReturnValue;value"
5672
]
5773
}
5874
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/** Provides classes and predicates related to the class `PendingIntent`. */
2+
3+
import java
4+
5+
/** The class `android.app.PendingIntent`. */
6+
class PendingIntent extends Class {
7+
PendingIntent() { this.hasQualifiedName("android.app", "PendingIntent") }
8+
}
9+
10+
/** A call to a method that creates a `PendingIntent`. */
11+
class PendingIntentCreation extends MethodAccess {
12+
PendingIntentCreation() {
13+
exists(Method m |
14+
this.getMethod() = m and
15+
m.getDeclaringType() instanceof PendingIntent and
16+
m.hasName([
17+
"getActivity", "getActivityAsUser", "getActivities", "getActivitiesAsUser",
18+
"getBroadcast", "getBroadcastAsUser", "getService", "getForegroundService"
19+
])
20+
)
21+
}
22+
23+
/** The `Intent` argument of this call. */
24+
Argument getIntentArg() { result = this.getArgument(2) }
25+
26+
/** The flags argument of this call. */
27+
Argument getFlagsArg() { result = this.getArgument(3) }
28+
}
29+
30+
/** A field of the class `PendingIntent` representing a flag. */
31+
class PendingIntentFlag extends Field {
32+
PendingIntentFlag() {
33+
this.getDeclaringType() instanceof PendingIntent and
34+
this.isPublic() and
35+
this.getName().matches("FLAG_%")
36+
}
37+
}
38+
39+
/** The field `FLAG_IMMUTABLE` of the class `PendingIntent`. */
40+
class ImmutablePendingIntentFlag extends PendingIntentFlag {
41+
ImmutablePendingIntentFlag() { this.hasName("FLAG_IMMUTABLE") }
42+
}
43+
44+
/** The field `FLAG_MUTABLE` of the class `PendingIntent`. */
45+
class MutablePendingIntentFlag extends PendingIntentFlag {
46+
MutablePendingIntentFlag() { this.hasName("FLAG_MUTABLE") }
47+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/** Provides classes and predicates for working with implicit `PendingIntent`s. */
2+
3+
import java
4+
private import semmle.code.java.dataflow.TaintTracking
5+
private import semmle.code.java.frameworks.android.Intent
6+
private import semmle.code.java.frameworks.android.PendingIntent
7+
8+
/** A source for an implicit `PendingIntent` flow. */
9+
abstract class ImplicitPendingIntentSource extends DataFlow::Node {
10+
/** Holds if this source has the specified `state`. */
11+
predicate hasState(DataFlow::FlowState state) { state = "" }
12+
}
13+
14+
/** A sink that sends an implicit and mutable `PendingIntent` to a third party. */
15+
abstract class ImplicitPendingIntentSink extends DataFlow::Node {
16+
/** Holds if this sink has the specified `state`. */
17+
predicate hasState(DataFlow::FlowState state) { state = "" }
18+
}
19+
20+
/**
21+
* A unit class for adding additional taint steps.
22+
*
23+
* Extend this class to add additional taint steps that should apply to flows related to the use
24+
* of implicit `PendingIntent`s.
25+
*/
26+
class ImplicitPendingIntentAdditionalTaintStep extends Unit {
27+
/**
28+
* Holds if the step from `node1` to `node2` should be considered a taint
29+
* step for flows related to the use of implicit `PendingIntent`s.
30+
*/
31+
predicate step(DataFlow::Node node1, DataFlow::Node node2) { none() }
32+
33+
/**
34+
* Holds if the step from `node1` to `node2` should be considered a taint
35+
* step for flows related to the use of implicit `PendingIntent`s. This step is only applicable
36+
* in `state1` and updates the flow state to `state2`.
37+
*/
38+
predicate step(
39+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
40+
DataFlow::FlowState state2
41+
) {
42+
none()
43+
}
44+
}
45+
46+
private class IntentCreationSource extends ImplicitPendingIntentSource {
47+
IntentCreationSource() {
48+
exists(ClassInstanceExpr cc |
49+
cc.getConstructedType() instanceof TypeIntent and this.asExpr() = cc
50+
)
51+
}
52+
}
53+
54+
private class SendPendingIntent extends ImplicitPendingIntentSink {
55+
SendPendingIntent() {
56+
sinkNode(this, "intent-start") and
57+
// implicit intents can't be started as services since API 21
58+
not exists(MethodAccess ma, Method m |
59+
ma.getMethod() = m and
60+
m.getDeclaringType().getASupertype*() instanceof TypeContext and
61+
m.getName().matches(["start%Service%", "bindService%"]) and
62+
this.asExpr() = ma.getArgument(0)
63+
)
64+
or
65+
sinkNode(this, "pending-intent-sent")
66+
}
67+
68+
override predicate hasState(DataFlow::FlowState state) { state = "MutablePendingIntent" }
69+
}
70+
71+
/**
72+
* Propagates taint from any tainted object to reads from its `PendingIntent`-typed fields.
73+
*/
74+
private class PendingIntentAsFieldAdditionalTaintStep extends ImplicitPendingIntentAdditionalTaintStep {
75+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
76+
exists(Field f |
77+
f.getType() instanceof PendingIntent and
78+
node1.(DataFlow::PostUpdateNode).getPreUpdateNode() =
79+
DataFlow::getFieldQualifier(f.getAnAccess().(FieldWrite)) and
80+
node2.asExpr().(FieldRead).getField() = f
81+
)
82+
}
83+
}
84+
85+
private class MutablePendingIntentFlowStep extends PendingIntentAsFieldAdditionalTaintStep {
86+
override predicate step(
87+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
88+
DataFlow::FlowState state2
89+
) {
90+
state1 = "" and
91+
state2 = "MutablePendingIntent" and
92+
exists(PendingIntentCreation pic, Argument flagArg |
93+
node1.asExpr() = pic.getIntentArg() and
94+
node2.asExpr() = pic and
95+
flagArg = pic.getFlagsArg()
96+
|
97+
// We err on the side of false positives here, assuming a PendingIntent may be mutable
98+
// unless it is at least sometimes explicitly marked immutable and never marked mutable.
99+
// Note: for API level < 31, PendingIntents were mutable by default, whereas since then
100+
// they are immutable by default.
101+
not TaintTracking::localExprTaint(any(ImmutablePendingIntentFlag flag).getAnAccess(), flagArg)
102+
or
103+
TaintTracking::localExprTaint(any(MutablePendingIntentFlag flag).getAnAccess(), flagArg)
104+
)
105+
}
106+
}
107+
108+
private class PendingIntentSentSinkModels extends SinkModelCsv {
109+
override predicate row(string row) {
110+
row =
111+
[
112+
"androidx.slice;SliceProvider;true;onBindSlice;;;ReturnValue;pending-intent-sent",
113+
"androidx.slice;SliceProvider;true;onCreatePermissionRequest;;;ReturnValue;pending-intent-sent",
114+
"android.app;NotificationManager;true;notify;(int,Notification);;Argument[1];pending-intent-sent",
115+
"android.app;NotificationManager;true;notify;(String,int,Notification);;Argument[2];pending-intent-sent",
116+
"android.app;NotificationManager;true;notifyAsPackage;(String,String,int,Notification);;Argument[3];pending-intent-sent",
117+
"android.app;NotificationManager;true;notifyAsUser;(String,int,Notification,UserHandle);;Argument[2];pending-intent-sent",
118+
"android.app;PendingIntent;false;send;(Context,int,Intent,OnFinished,Handler,String,Bundle);;Argument[2];pending-intent-sent",
119+
"android.app;PendingIntent;false;send;(Context,int,Intent,OnFinished,Handler,String);;Argument[2];pending-intent-sent",
120+
"android.app;PendingIntent;false;send;(Context,int,Intent,OnFinished,Handler);;Argument[2];pending-intent-sent",
121+
"android.app;PendingIntent;false;send;(Context,int,Intent);;Argument[2];pending-intent-sent",
122+
"android.app;Activity;true;setResult;(int,Intent);;Argument[1];pending-intent-sent"
123+
]
124+
}
125+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/** Provides taint tracking configurations to be used in queries related to implicit `PendingIntent`s. */
2+
3+
import java
4+
import semmle.code.java.dataflow.TaintTracking
5+
import semmle.code.java.frameworks.android.Intent
6+
import semmle.code.java.frameworks.android.PendingIntent
7+
import semmle.code.java.security.ImplicitPendingIntents
8+
9+
/**
10+
* A taint tracking configuration for implicit `PendingIntent`s
11+
* being wrapped in another implicit `Intent` that gets started.
12+
*/
13+
class ImplicitPendingIntentStartConf extends TaintTracking::Configuration {
14+
ImplicitPendingIntentStartConf() { this = "ImplicitPendingIntentStartConf" }
15+
16+
override predicate isSource(DataFlow::Node source, DataFlow::FlowState state) {
17+
source.(ImplicitPendingIntentSource).hasState(state)
18+
}
19+
20+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowState state) {
21+
sink.(ImplicitPendingIntentSink).hasState(state)
22+
}
23+
24+
override predicate isSanitizer(DataFlow::Node sanitizer) {
25+
sanitizer instanceof ExplicitIntentSanitizer
26+
}
27+
28+
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
29+
any(ImplicitPendingIntentAdditionalTaintStep c).step(node1, node2)
30+
}
31+
32+
override predicate isAdditionalFlowStep(
33+
DataFlow::Node node1, DataFlow::FlowState state1, DataFlow::Node node2,
34+
DataFlow::FlowState state2
35+
) {
36+
any(ImplicitPendingIntentAdditionalTaintStep c).step(node1, state1, node2, state2)
37+
}
38+
39+
override predicate allowImplicitRead(DataFlow::Node node, DataFlow::Content c) {
40+
super.allowImplicitRead(node, c)
41+
or
42+
this.isSink(node, _) and
43+
allowIntentExtrasImplicitRead(node, c)
44+
or
45+
this.isAdditionalTaintStep(node, _) and
46+
c.(DataFlow::FieldContent).getType() instanceof PendingIntent
47+
or
48+
// Allow implicit reads of Intent arrays for steps like getActivities
49+
// or sinks like startActivities
50+
(this.isSink(node, _) or this.isAdditionalFlowStep(node, _, _, _)) and
51+
node.getType().(Array).getElementType() instanceof TypeIntent and
52+
c instanceof DataFlow::ArrayContent
53+
}
54+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import android.app.Activity;
2+
import android.app.PendingIntent;
3+
import android.content.Intent;
4+
import android.os.Bundle;
5+
6+
public class ImplicitPendingIntents extends Activity {
7+
8+
public void onCreate(Bundle savedInstance) {
9+
{
10+
// BAD: an implicit Intent is used to create a PendingIntent.
11+
// The PendingIntent is then added to another implicit Intent
12+
// and started.
13+
Intent baseIntent = new Intent();
14+
PendingIntent pi =
15+
PendingIntent.getActivity(this, 0, baseIntent, PendingIntent.FLAG_ONE_SHOT);
16+
Intent fwdIntent = new Intent("SOME_ACTION");
17+
fwdIntent.putExtra("fwdIntent", pi);
18+
sendBroadcast(fwdIntent);
19+
}
20+
21+
{
22+
// GOOD: both the PendingIntent and the wrapping Intent are explicit.
23+
Intent safeIntent = new Intent(this, AnotherActivity.class);
24+
PendingIntent pi =
25+
PendingIntent.getActivity(this, 0, safeIntent, PendingIntent.FLAG_ONE_SHOT);
26+
Intent fwdIntent = new Intent();
27+
fwdIntent.setClassName("destination.package", "DestinationClass");
28+
fwdIntent.putExtra("fwdIntent", pi);
29+
startActivity(fwdIntent);
30+
}
31+
32+
{
33+
// GOOD: The PendingIntent is created with FLAG_IMMUTABLE.
34+
Intent baseIntent = new Intent("SOME_ACTION");
35+
PendingIntent pi =
36+
PendingIntent.getActivity(this, 0, baseIntent, PendingIntent.FLAG_IMMUTABLE);
37+
Intent fwdIntent = new Intent();
38+
fwdIntent.setClassName("destination.package", "DestinationClass");
39+
fwdIntent.putExtra("fwdIntent", pi);
40+
startActivity(fwdIntent);
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)