Skip to content

Commit 7576047

Browse files
Jami Cogswellatorralba
authored andcommitted
create simple query and initial experimentation
1 parent d5478a0 commit 7576047

File tree

8 files changed

+417
-0
lines changed

8 files changed

+417
-0
lines changed
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/** Provides classes and predicates to reason about deep links in Android. */
2+
3+
import java
4+
private import semmle.code.java.frameworks.android.Intent
5+
private import semmle.code.java.frameworks.android.AsyncTask
6+
private import semmle.code.java.dataflow.DataFlow
7+
private import semmle.code.java.dataflow.FlowSteps
8+
private import semmle.code.java.dataflow.ExternalFlow
9+
10+
/**
11+
* The method `Intent.getSerializableExtra`
12+
*/
13+
class AndroidGetSerializableExtraMethod extends Method {
14+
AndroidGetSerializableExtraMethod() {
15+
this.hasName("getSerializableExtra") and this.getDeclaringType() instanceof TypeIntent
16+
}
17+
}
18+
19+
/**
20+
* The method `Context.startService`.
21+
*/
22+
class ContextStartServiceMethod extends Method {
23+
ContextStartServiceMethod() {
24+
this.hasName("startService") and
25+
this.getDeclaringType() instanceof TypeContext
26+
}
27+
}
28+
29+
// /**
30+
// * A value-preserving step from the Intent argument of a `startService` call to
31+
// * a `getSerializableExtra` call in the Service the Intent pointed to in its constructor.
32+
// */
33+
// class StartServiceIntentStep extends AdditionalValueStep {
34+
// override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
35+
// exists(
36+
// MethodAccess startService, MethodAccess getSerializableExtra, ClassInstanceExpr newIntent
37+
// |
38+
// startService.getMethod().overrides*(any(ContextStartServiceMethod m)) and
39+
// getSerializableExtra.getMethod().overrides*(any(AndroidGetSerializableExtraMethod m)) and
40+
// newIntent.getConstructedType() instanceof TypeIntent and
41+
// DataFlow::localExprFlow(newIntent, startService.getArgument(0)) and
42+
// //newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
43+
// // getSerializableExtra.getReceiverType() and
44+
// newIntent.getArgument(1).toString() = "FetcherService.class" and // BAD
45+
// getSerializableExtra.getFile().getBaseName() = "RouterActivity.java" and // BAD
46+
// n1.asExpr() = startService.getArgument(0) and
47+
// n2.asExpr() = getSerializableExtra
48+
// )
49+
// }
50+
// }
51+
/**
52+
* A value-preserving step from the Intent argument of a `startService` call to
53+
* an `Intent` TypeAccess in the Service the Intent pointed to in its constructor.
54+
*/
55+
class StartServiceIntentStep extends AdditionalValueStep {
56+
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
57+
exists(MethodAccess startService, VarAccess intentVar, ClassInstanceExpr newIntent |
58+
startService.getMethod().overrides*(any(ContextStartServiceMethod m)) and
59+
//getSerializableExtra.getMethod().overrides*(any(AndroidGetSerializableExtraMethod m)) and
60+
intentVar.getType() instanceof TypeIntent and
61+
newIntent.getConstructedType() instanceof TypeIntent and
62+
DataFlow::localExprFlow(newIntent, startService.getArgument(0)) and
63+
// newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
64+
// intentVar.getBasicBlock().getBasicBlock() and
65+
// newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
66+
// intent.getType().(ParameterizedType).getATypeArgument() and
67+
newIntent.getArgument(1).toString() = "FetcherService.class" and // BAD
68+
intentVar.getFile().getBaseName() = "RouterActivity.java" and // BAD
69+
n1.asExpr() = startService.getArgument(0) and
70+
n2.asExpr() = intentVar
71+
)
72+
}
73+
}
74+
75+
// *************************************************************************************************
76+
/*
77+
* The following flow steps aim to model the life-cycle of `AsyncTask`s described here:
78+
* https://developer.android.com/reference/android/os/AsyncTask#the-4-steps
79+
*/
80+
81+
/**
82+
* A taint step from the vararg arguments of `AsyncTask::execute` and `AsyncTask::executeOnExecutor`
83+
* to the parameter of `AsyncTask::doInBackground`.
84+
*/
85+
private class AsyncTaskExecuteAdditionalValueStep extends AdditionalTaintStep {
86+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
87+
exists(ExecuteAsyncTaskMethodAccess ma, AsyncTaskRunInBackgroundMethod m |
88+
DataFlow::getInstanceArgument(ma).getType() = m.getDeclaringType()
89+
|
90+
node1.asExpr() = ma.getParamsArgument() and
91+
node2.asParameter() = m.getParameter(0)
92+
)
93+
}
94+
}
95+
96+
/**
97+
* A value-preserving step from the return value of `AsyncTask::doInBackground`
98+
* to the parameter of `AsyncTask::onPostExecute`.
99+
*/
100+
private class AsyncTaskOnPostExecuteAdditionalValueStep extends AdditionalValueStep {
101+
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
102+
exists(
103+
AsyncTaskRunInBackgroundMethod runInBackground, AsyncTaskOnPostExecuteMethod onPostExecute
104+
|
105+
onPostExecute.getDeclaringType() = runInBackground.getDeclaringType()
106+
|
107+
node1.asExpr() = any(ReturnStmt r | r.getEnclosingCallable() = runInBackground).getResult() and
108+
node2.asParameter() = onPostExecute.getParameter(0)
109+
)
110+
}
111+
}
112+
113+
/**
114+
* A value-preserving step from field initializers in `AsyncTask`'s constructor or initializer method
115+
* to the instance parameter of `AsyncTask::runInBackground` and `AsyncTask::onPostExecute`.
116+
*/
117+
private class AsyncTaskFieldInitQualifierToInstanceParameterStep extends AdditionalValueStep {
118+
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
119+
exists(AsyncTaskInit init, Callable receiver |
120+
n1.(DataFlow::PostUpdateNode).getPreUpdateNode() =
121+
DataFlow::getFieldQualifier(any(FieldWrite f | f.getEnclosingCallable() = init)) and
122+
n2.(DataFlow::InstanceParameterNode).getCallable() = receiver and
123+
receiver.getDeclaringType() = init.getDeclaringType() and
124+
(
125+
receiver instanceof AsyncTaskRunInBackgroundMethod or
126+
receiver instanceof AsyncTaskOnPostExecuteMethod
127+
)
128+
)
129+
}
130+
}
131+
132+
/**
133+
* The Android class `android.os.AsyncTask`.
134+
*/
135+
private class AsyncTask extends RefType {
136+
AsyncTask() { this.hasQualifiedName("android.os", "AsyncTask") }
137+
}
138+
139+
/** The constructor or initializer method of the `android.os.AsyncTask` class. */
140+
private class AsyncTaskInit extends Callable {
141+
AsyncTaskInit() {
142+
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof AsyncTask and
143+
(this instanceof Constructor or this instanceof InitializerMethod)
144+
}
145+
}
146+
147+
/** A call to the `execute` or `executeOnExecutor` methods of the `android.os.AsyncTask` class. */
148+
private class ExecuteAsyncTaskMethodAccess extends MethodAccess {
149+
ExecuteAsyncTaskMethodAccess() {
150+
this.getMethod().hasName(["execute", "executeOnExecutor"]) and
151+
this.getMethod().getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof
152+
AsyncTask
153+
}
154+
155+
/** Returns the `params` argument of this call. */
156+
Argument getParamsArgument() { result = this.getAnArgument() and result.isVararg() }
157+
}
158+
159+
/** The `doInBackground` method of the `android.os.AsyncTask` class. */
160+
private class AsyncTaskRunInBackgroundMethod extends Method {
161+
AsyncTaskRunInBackgroundMethod() {
162+
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof AsyncTask and
163+
this.hasName("doInBackground")
164+
}
165+
}
166+
167+
/** The `onPostExecute` method of the `android.os.AsyncTask` class. */
168+
private class AsyncTaskOnPostExecuteMethod extends Method {
169+
AsyncTaskOnPostExecuteMethod() {
170+
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof AsyncTask and
171+
this.hasName("onPostExecute")
172+
}
173+
}

java/ql/lib/semmle/code/xml/AndroidManifest.qll

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,29 @@ class AndroidApplicationXmlElement extends XmlElement {
129129
*/
130130
class AndroidActivityXmlElement extends AndroidComponentXmlElement {
131131
AndroidActivityXmlElement() { this.getName() = "activity" }
132+
133+
// ! Double-check that no other components can have deep links.
134+
// ! Consider moving this to its own .qll file like for Implicit Export Query.
135+
// ! Also double-check that the below actions and categories are REQUIRED for it to
136+
// ! count as a deep link versus just recommended (e.g. should I just look for the
137+
// ! data element instead?).
138+
/**
139+
* Holds if this `<activity>` element has a deep link.
140+
*/
141+
predicate hasDeepLink() {
142+
//exists(this.getAnIntentFilterElement()) and // has an intent filter - below all show that it has an intent-filter, duplicates work
143+
this.getAnIntentFilterElement().getAnActionElement().getActionName() =
144+
"android.intent.action.VIEW" and
145+
this.getAnIntentFilterElement().getACategoryElement().getCategoryName() =
146+
"android.intent.category.BROWSABLE" and
147+
this.getAnIntentFilterElement().getACategoryElement().getCategoryName() =
148+
"android.intent.category.DEFAULT" and
149+
//this.getAnIntentFilterElement().getAChild("data").hasAttribute("scheme") // use below instead for 'android' prefix
150+
exists(AndroidXmlAttribute attr |
151+
this.getAnIntentFilterElement().getAChild("data").getAnAttribute() = attr and
152+
attr.getName() = "scheme"
153+
)
154+
}
132155
}
133156

134157
/**
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @name Android deep links
3+
* @description Android deep links
4+
* @problem.severity recommendation
5+
* @security-severity 0.1
6+
* @id java/android/deeplinks
7+
* @tags security
8+
* external/cwe/cwe-939
9+
* @precision high
10+
*/
11+
12+
// import java
13+
// import semmle.code.xml.AndroidManifest
14+
// import semmle.code.java.frameworks.android.Android
15+
// import semmle.code.java.frameworks.android.Intent
16+
// import semmle.code.java.frameworks.android.AsyncTask
17+
// import semmle.code.java.frameworks.android.DeepLink
18+
// import semmle.code.java.dataflow.DataFlow
19+
// import semmle.code.java.dataflow.TaintTracking
20+
// import semmle.code.java.dataflow.FlowSources
21+
// import semmle.code.java.dataflow.FlowSteps
22+
// import semmle.code.java.dataflow.ExternalFlow
23+
//* select getData() method access in RouterActivity
24+
// from AndroidComponent andComp, MethodAccess ma
25+
// where
26+
// andComp
27+
// .getAndroidComponentXmlElement()
28+
// .getAnIntentFilterElement()
29+
// .getAnActionElement()
30+
// .getActionName() = "android.intent.action.VIEW" and
31+
// andComp
32+
// .getAndroidComponentXmlElement()
33+
// .getAnIntentFilterElement()
34+
// .getACategoryElement()
35+
// .getCategoryName() = "android.intent.category.BROWSABLE" and
36+
// andComp
37+
// .getAndroidComponentXmlElement()
38+
// .getAnIntentFilterElement()
39+
// .getACategoryElement()
40+
// .getCategoryName() = "android.intent.category.DEFAULT" and
41+
// andComp
42+
// .getAndroidComponentXmlElement()
43+
// .getAnIntentFilterElement()
44+
// .getAChild("data")
45+
// .hasAttribute("scheme") and // make sure to check for 'android' prefix in real query
46+
// ma.getMethod().hasName("getData") and
47+
// //ma.getCompilationUnit().toString() = andComp.toString() // string is "RouterActivity"
48+
// andComp.getFile() = ma.getFile()
49+
// select ma, "getData usage related to deeplink"
50+
// * play with taint/data
51+
// class DeepLinkConfiguration extends TaintTracking::Configuration {
52+
// DeepLinkConfiguration() { this = "DeepLinkConfiguration" }
53+
// override predicate isSource(DataFlow::Node source) {
54+
// exists(MethodAccess ma, AndroidComponent andComp |
55+
// ma.getMethod().hasName("getData") and
56+
// andComp
57+
// .getAndroidComponentXmlElement()
58+
// .getAnIntentFilterElement()
59+
// .getAnActionElement()
60+
// .getActionName() = "android.intent.action.VIEW" and
61+
// andComp
62+
// .getAndroidComponentXmlElement()
63+
// .getAnIntentFilterElement()
64+
// .getACategoryElement()
65+
// .getCategoryName() = "android.intent.category.BROWSABLE" and
66+
// andComp
67+
// .getAndroidComponentXmlElement()
68+
// .getAnIntentFilterElement()
69+
// .getACategoryElement()
70+
// .getCategoryName() = "android.intent.category.DEFAULT" and
71+
// andComp
72+
// .getAndroidComponentXmlElement()
73+
// .getAnIntentFilterElement()
74+
// .getAChild("data")
75+
// .hasAttribute("scheme") and
76+
// andComp.getFile() = ma.getFile() and
77+
// source.asExpr() = ma
78+
// )
79+
// }
80+
// override predicate isSink(DataFlow::Node sink) {
81+
// exists(Variable v | v.hasName("currentUrl") and sink.asExpr() = v.getAnAccess())
82+
// }
83+
// }
84+
// from DataFlow::Node src, DataFlow::Node sink, DeepLinkConfiguration config
85+
// where config.hasFlow(src, sink)
86+
// select src, "This environment variable constructs a URL $@.", sink, "here"
87+
// * Intent experimentation:
88+
//from
89+
// AndroidComponent andComp, MethodAccess ma, StartActivityIntentStep startActIntStep,
90+
// DataFlow::Node n1, DataFlow::Node n2
91+
// where
92+
// andComp
93+
// .getAndroidComponentXmlElement()
94+
// .getAnIntentFilterElement()
95+
// .getAnActionElement()
96+
// .getActionName() = "android.intent.action.VIEW" and
97+
// andComp
98+
// .getAndroidComponentXmlElement()
99+
// .getAnIntentFilterElement()
100+
// .getACategoryElement()
101+
// .getCategoryName() = "android.intent.category.BROWSABLE" and
102+
// andComp
103+
// .getAndroidComponentXmlElement()
104+
// .getAnIntentFilterElement()
105+
// .getACategoryElement()
106+
// .getCategoryName() = "android.intent.category.DEFAULT" and
107+
// andComp
108+
// .getAndroidComponentXmlElement()
109+
// .getAnIntentFilterElement()
110+
// .getAChild("data")
111+
// .hasAttribute("scheme") and // make sure to check for 'android' prefix in real query
112+
// //ma.getMethod().hasName("getData") and
113+
// //andComp.getFile() = ma.getFile() and
114+
// //n1.asExpr() = ma and
115+
// //n1.asExpr() = ma and
116+
// andComp.getAnAnnotation() = n1.asExpr() and
117+
// startActIntStep.step(n1, n2)
118+
// select n1, "deeplink"
119+
// * experiment with StartActivityIntentStep
120+
import java
121+
import semmle.code.java.frameworks.android.DeepLink
122+
import semmle.code.java.dataflow.DataFlow
123+
124+
from StartServiceIntentStep startServiceIntStep, DataFlow::Node n1, DataFlow::Node n2
125+
where startServiceIntStep.step(n1, n2)
126+
select n2, "placeholder"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @name Android deep links
3+
* @description Android deep links
4+
* @kind problem
5+
* @problem.severity recommendation
6+
* @security-severity 0.1
7+
* @id java/android/deeplinks
8+
* @tags security
9+
* external/cwe/cwe-939
10+
* @precision high
11+
*/
12+
13+
import java
14+
import semmle.code.xml.AndroidManifest
15+
16+
from AndroidActivityXmlElement actXmlElement
17+
where
18+
actXmlElement.hasDeepLink() and
19+
not actXmlElement.getFile().(AndroidManifestXmlFile).isInBuildDirectory()
20+
select actXmlElement, "A deeplink is used here."

0 commit comments

Comments
 (0)