mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
create simple query and initial experimentation
This commit is contained in:
committed by
Tony Torralba
parent
d5478a01ab
commit
7576047214
173
java/ql/lib/semmle/code/java/frameworks/android/DeepLink.qll
Normal file
173
java/ql/lib/semmle/code/java/frameworks/android/DeepLink.qll
Normal file
@@ -0,0 +1,173 @@
|
||||
/** Provides classes and predicates to reason about deep links in Android. */
|
||||
|
||||
import java
|
||||
private import semmle.code.java.frameworks.android.Intent
|
||||
private import semmle.code.java.frameworks.android.AsyncTask
|
||||
private import semmle.code.java.dataflow.DataFlow
|
||||
private import semmle.code.java.dataflow.FlowSteps
|
||||
private import semmle.code.java.dataflow.ExternalFlow
|
||||
|
||||
/**
|
||||
* The method `Intent.getSerializableExtra`
|
||||
*/
|
||||
class AndroidGetSerializableExtraMethod extends Method {
|
||||
AndroidGetSerializableExtraMethod() {
|
||||
this.hasName("getSerializableExtra") and this.getDeclaringType() instanceof TypeIntent
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method `Context.startService`.
|
||||
*/
|
||||
class ContextStartServiceMethod extends Method {
|
||||
ContextStartServiceMethod() {
|
||||
this.hasName("startService") and
|
||||
this.getDeclaringType() instanceof TypeContext
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * A value-preserving step from the Intent argument of a `startService` call to
|
||||
// * a `getSerializableExtra` call in the Service the Intent pointed to in its constructor.
|
||||
// */
|
||||
// class StartServiceIntentStep extends AdditionalValueStep {
|
||||
// override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
// exists(
|
||||
// MethodAccess startService, MethodAccess getSerializableExtra, ClassInstanceExpr newIntent
|
||||
// |
|
||||
// startService.getMethod().overrides*(any(ContextStartServiceMethod m)) and
|
||||
// getSerializableExtra.getMethod().overrides*(any(AndroidGetSerializableExtraMethod m)) and
|
||||
// newIntent.getConstructedType() instanceof TypeIntent and
|
||||
// DataFlow::localExprFlow(newIntent, startService.getArgument(0)) and
|
||||
// //newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
|
||||
// // getSerializableExtra.getReceiverType() and
|
||||
// newIntent.getArgument(1).toString() = "FetcherService.class" and // BAD
|
||||
// getSerializableExtra.getFile().getBaseName() = "RouterActivity.java" and // BAD
|
||||
// n1.asExpr() = startService.getArgument(0) and
|
||||
// n2.asExpr() = getSerializableExtra
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
/**
|
||||
* A value-preserving step from the Intent argument of a `startService` call to
|
||||
* an `Intent` TypeAccess in the Service the Intent pointed to in its constructor.
|
||||
*/
|
||||
class StartServiceIntentStep extends AdditionalValueStep {
|
||||
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
exists(MethodAccess startService, VarAccess intentVar, ClassInstanceExpr newIntent |
|
||||
startService.getMethod().overrides*(any(ContextStartServiceMethod m)) and
|
||||
//getSerializableExtra.getMethod().overrides*(any(AndroidGetSerializableExtraMethod m)) and
|
||||
intentVar.getType() instanceof TypeIntent and
|
||||
newIntent.getConstructedType() instanceof TypeIntent and
|
||||
DataFlow::localExprFlow(newIntent, startService.getArgument(0)) and
|
||||
// newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
|
||||
// intentVar.getBasicBlock().getBasicBlock() and
|
||||
// newIntent.getArgument(1).getType().(ParameterizedType).getATypeArgument() =
|
||||
// intent.getType().(ParameterizedType).getATypeArgument() and
|
||||
newIntent.getArgument(1).toString() = "FetcherService.class" and // BAD
|
||||
intentVar.getFile().getBaseName() = "RouterActivity.java" and // BAD
|
||||
n1.asExpr() = startService.getArgument(0) and
|
||||
n2.asExpr() = intentVar
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// *************************************************************************************************
|
||||
/*
|
||||
* The following flow steps aim to model the life-cycle of `AsyncTask`s described here:
|
||||
* https://developer.android.com/reference/android/os/AsyncTask#the-4-steps
|
||||
*/
|
||||
|
||||
/**
|
||||
* A taint step from the vararg arguments of `AsyncTask::execute` and `AsyncTask::executeOnExecutor`
|
||||
* to the parameter of `AsyncTask::doInBackground`.
|
||||
*/
|
||||
private class AsyncTaskExecuteAdditionalValueStep extends AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(ExecuteAsyncTaskMethodAccess ma, AsyncTaskRunInBackgroundMethod m |
|
||||
DataFlow::getInstanceArgument(ma).getType() = m.getDeclaringType()
|
||||
|
|
||||
node1.asExpr() = ma.getParamsArgument() and
|
||||
node2.asParameter() = m.getParameter(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value-preserving step from the return value of `AsyncTask::doInBackground`
|
||||
* to the parameter of `AsyncTask::onPostExecute`.
|
||||
*/
|
||||
private class AsyncTaskOnPostExecuteAdditionalValueStep extends AdditionalValueStep {
|
||||
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
|
||||
exists(
|
||||
AsyncTaskRunInBackgroundMethod runInBackground, AsyncTaskOnPostExecuteMethod onPostExecute
|
||||
|
|
||||
onPostExecute.getDeclaringType() = runInBackground.getDeclaringType()
|
||||
|
|
||||
node1.asExpr() = any(ReturnStmt r | r.getEnclosingCallable() = runInBackground).getResult() and
|
||||
node2.asParameter() = onPostExecute.getParameter(0)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value-preserving step from field initializers in `AsyncTask`'s constructor or initializer method
|
||||
* to the instance parameter of `AsyncTask::runInBackground` and `AsyncTask::onPostExecute`.
|
||||
*/
|
||||
private class AsyncTaskFieldInitQualifierToInstanceParameterStep extends AdditionalValueStep {
|
||||
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
|
||||
exists(AsyncTaskInit init, Callable receiver |
|
||||
n1.(DataFlow::PostUpdateNode).getPreUpdateNode() =
|
||||
DataFlow::getFieldQualifier(any(FieldWrite f | f.getEnclosingCallable() = init)) and
|
||||
n2.(DataFlow::InstanceParameterNode).getCallable() = receiver and
|
||||
receiver.getDeclaringType() = init.getDeclaringType() and
|
||||
(
|
||||
receiver instanceof AsyncTaskRunInBackgroundMethod or
|
||||
receiver instanceof AsyncTaskOnPostExecuteMethod
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Android class `android.os.AsyncTask`.
|
||||
*/
|
||||
private class AsyncTask extends RefType {
|
||||
AsyncTask() { this.hasQualifiedName("android.os", "AsyncTask") }
|
||||
}
|
||||
|
||||
/** The constructor or initializer method of the `android.os.AsyncTask` class. */
|
||||
private class AsyncTaskInit extends Callable {
|
||||
AsyncTaskInit() {
|
||||
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof AsyncTask and
|
||||
(this instanceof Constructor or this instanceof InitializerMethod)
|
||||
}
|
||||
}
|
||||
|
||||
/** A call to the `execute` or `executeOnExecutor` methods of the `android.os.AsyncTask` class. */
|
||||
private class ExecuteAsyncTaskMethodAccess extends MethodAccess {
|
||||
ExecuteAsyncTaskMethodAccess() {
|
||||
this.getMethod().hasName(["execute", "executeOnExecutor"]) and
|
||||
this.getMethod().getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof
|
||||
AsyncTask
|
||||
}
|
||||
|
||||
/** Returns the `params` argument of this call. */
|
||||
Argument getParamsArgument() { result = this.getAnArgument() and result.isVararg() }
|
||||
}
|
||||
|
||||
/** The `doInBackground` method of the `android.os.AsyncTask` class. */
|
||||
private class AsyncTaskRunInBackgroundMethod extends Method {
|
||||
AsyncTaskRunInBackgroundMethod() {
|
||||
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof AsyncTask and
|
||||
this.hasName("doInBackground")
|
||||
}
|
||||
}
|
||||
|
||||
/** The `onPostExecute` method of the `android.os.AsyncTask` class. */
|
||||
private class AsyncTaskOnPostExecuteMethod extends Method {
|
||||
AsyncTaskOnPostExecuteMethod() {
|
||||
this.getDeclaringType().getSourceDeclaration().getASourceSupertype*() instanceof AsyncTask and
|
||||
this.hasName("onPostExecute")
|
||||
}
|
||||
}
|
||||
@@ -129,6 +129,29 @@ class AndroidApplicationXmlElement extends XmlElement {
|
||||
*/
|
||||
class AndroidActivityXmlElement extends AndroidComponentXmlElement {
|
||||
AndroidActivityXmlElement() { this.getName() = "activity" }
|
||||
|
||||
// ! Double-check that no other components can have deep links.
|
||||
// ! Consider moving this to its own .qll file like for Implicit Export Query.
|
||||
// ! Also double-check that the below actions and categories are REQUIRED for it to
|
||||
// ! count as a deep link versus just recommended (e.g. should I just look for the
|
||||
// ! data element instead?).
|
||||
/**
|
||||
* Holds if this `<activity>` element has a deep link.
|
||||
*/
|
||||
predicate hasDeepLink() {
|
||||
//exists(this.getAnIntentFilterElement()) and // has an intent filter - below all show that it has an intent-filter, duplicates work
|
||||
this.getAnIntentFilterElement().getAnActionElement().getActionName() =
|
||||
"android.intent.action.VIEW" and
|
||||
this.getAnIntentFilterElement().getACategoryElement().getCategoryName() =
|
||||
"android.intent.category.BROWSABLE" and
|
||||
this.getAnIntentFilterElement().getACategoryElement().getCategoryName() =
|
||||
"android.intent.category.DEFAULT" and
|
||||
//this.getAnIntentFilterElement().getAChild("data").hasAttribute("scheme") // use below instead for 'android' prefix
|
||||
exists(AndroidXmlAttribute attr |
|
||||
this.getAnIntentFilterElement().getAChild("data").getAnAttribute() = attr and
|
||||
attr.getName() = "scheme"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* @name Android deep links
|
||||
* @description Android deep links
|
||||
* @problem.severity recommendation
|
||||
* @security-severity 0.1
|
||||
* @id java/android/deeplinks
|
||||
* @tags security
|
||||
* external/cwe/cwe-939
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
// import java
|
||||
// import semmle.code.xml.AndroidManifest
|
||||
// import semmle.code.java.frameworks.android.Android
|
||||
// import semmle.code.java.frameworks.android.Intent
|
||||
// import semmle.code.java.frameworks.android.AsyncTask
|
||||
// import semmle.code.java.frameworks.android.DeepLink
|
||||
// import semmle.code.java.dataflow.DataFlow
|
||||
// import semmle.code.java.dataflow.TaintTracking
|
||||
// import semmle.code.java.dataflow.FlowSources
|
||||
// import semmle.code.java.dataflow.FlowSteps
|
||||
// import semmle.code.java.dataflow.ExternalFlow
|
||||
//* select getData() method access in RouterActivity
|
||||
// from AndroidComponent andComp, MethodAccess ma
|
||||
// where
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getAnActionElement()
|
||||
// .getActionName() = "android.intent.action.VIEW" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getACategoryElement()
|
||||
// .getCategoryName() = "android.intent.category.BROWSABLE" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getACategoryElement()
|
||||
// .getCategoryName() = "android.intent.category.DEFAULT" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getAChild("data")
|
||||
// .hasAttribute("scheme") and // make sure to check for 'android' prefix in real query
|
||||
// ma.getMethod().hasName("getData") and
|
||||
// //ma.getCompilationUnit().toString() = andComp.toString() // string is "RouterActivity"
|
||||
// andComp.getFile() = ma.getFile()
|
||||
// select ma, "getData usage related to deeplink"
|
||||
// * play with taint/data
|
||||
// class DeepLinkConfiguration extends TaintTracking::Configuration {
|
||||
// DeepLinkConfiguration() { this = "DeepLinkConfiguration" }
|
||||
// override predicate isSource(DataFlow::Node source) {
|
||||
// exists(MethodAccess ma, AndroidComponent andComp |
|
||||
// ma.getMethod().hasName("getData") and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getAnActionElement()
|
||||
// .getActionName() = "android.intent.action.VIEW" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getACategoryElement()
|
||||
// .getCategoryName() = "android.intent.category.BROWSABLE" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getACategoryElement()
|
||||
// .getCategoryName() = "android.intent.category.DEFAULT" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getAChild("data")
|
||||
// .hasAttribute("scheme") and
|
||||
// andComp.getFile() = ma.getFile() and
|
||||
// source.asExpr() = ma
|
||||
// )
|
||||
// }
|
||||
// override predicate isSink(DataFlow::Node sink) {
|
||||
// exists(Variable v | v.hasName("currentUrl") and sink.asExpr() = v.getAnAccess())
|
||||
// }
|
||||
// }
|
||||
// from DataFlow::Node src, DataFlow::Node sink, DeepLinkConfiguration config
|
||||
// where config.hasFlow(src, sink)
|
||||
// select src, "This environment variable constructs a URL $@.", sink, "here"
|
||||
// * Intent experimentation:
|
||||
//from
|
||||
// AndroidComponent andComp, MethodAccess ma, StartActivityIntentStep startActIntStep,
|
||||
// DataFlow::Node n1, DataFlow::Node n2
|
||||
// where
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getAnActionElement()
|
||||
// .getActionName() = "android.intent.action.VIEW" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getACategoryElement()
|
||||
// .getCategoryName() = "android.intent.category.BROWSABLE" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getACategoryElement()
|
||||
// .getCategoryName() = "android.intent.category.DEFAULT" and
|
||||
// andComp
|
||||
// .getAndroidComponentXmlElement()
|
||||
// .getAnIntentFilterElement()
|
||||
// .getAChild("data")
|
||||
// .hasAttribute("scheme") and // make sure to check for 'android' prefix in real query
|
||||
// //ma.getMethod().hasName("getData") and
|
||||
// //andComp.getFile() = ma.getFile() and
|
||||
// //n1.asExpr() = ma and
|
||||
// //n1.asExpr() = ma and
|
||||
// andComp.getAnAnnotation() = n1.asExpr() and
|
||||
// startActIntStep.step(n1, n2)
|
||||
// select n1, "deeplink"
|
||||
// * experiment with StartActivityIntentStep
|
||||
import java
|
||||
import semmle.code.java.frameworks.android.DeepLink
|
||||
import semmle.code.java.dataflow.DataFlow
|
||||
|
||||
from StartServiceIntentStep startServiceIntStep, DataFlow::Node n1, DataFlow::Node n2
|
||||
where startServiceIntStep.step(n1, n2)
|
||||
select n2, "placeholder"
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* @name Android deep links
|
||||
* @description Android deep links
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @security-severity 0.1
|
||||
* @id java/android/deeplinks
|
||||
* @tags security
|
||||
* external/cwe/cwe-939
|
||||
* @precision high
|
||||
*/
|
||||
|
||||
import java
|
||||
import semmle.code.xml.AndroidManifest
|
||||
|
||||
from AndroidActivityXmlElement actXmlElement
|
||||
where
|
||||
actXmlElement.hasDeepLink() and
|
||||
not actXmlElement.getFile().(AndroidManifestXmlFile).isInBuildDirectory()
|
||||
select actXmlElement, "A deeplink is used here."
|
||||
@@ -0,0 +1,72 @@
|
||||
|
||||
// !!! From AsyncTask, update for DeepLinks... !!!
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
public class Test {
|
||||
|
||||
private static Object source(String kind) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void sink(Object o) {}
|
||||
|
||||
public void test() {
|
||||
TestAsyncTask t = new TestAsyncTask();
|
||||
t.execute(source("execute"), null);
|
||||
t.executeOnExecutor(null, source("executeOnExecutor"), null);
|
||||
SafeAsyncTask t2 = new SafeAsyncTask();
|
||||
t2.execute("safe");
|
||||
TestConstructorTask t3 = new TestConstructorTask(source("constructor"), "safe");
|
||||
t3.execute(source("params"));
|
||||
}
|
||||
|
||||
private class TestAsyncTask extends AsyncTask<Object, Object, Object> {
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
sink(params[0]); // $ hasTaintFlow=execute hasTaintFlow=executeOnExecutor
|
||||
sink(params[1]); // $ SPURIOUS: hasTaintFlow=execute hasTaintFlow=executeOnExecutor
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class SafeAsyncTask extends AsyncTask<Object, Object, Object> {
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
sink(params[0]); // Safe
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static class TestConstructorTask extends AsyncTask<Object, Object, Object> {
|
||||
private Object field;
|
||||
private Object safeField;
|
||||
private Object initField;
|
||||
{
|
||||
initField = Test.source("init");
|
||||
}
|
||||
|
||||
public TestConstructorTask(Object field, Object safeField) {
|
||||
this.field = field;
|
||||
this.safeField = safeField;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object doInBackground(Object... params) {
|
||||
sink(params[0]); // $ hasTaintFlow=params
|
||||
sink(field); // $ hasValueFlow=constructor
|
||||
sink(safeField); // Safe
|
||||
sink(initField); // $ hasValueFlow=init
|
||||
return params[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Object param) {
|
||||
sink(param); // $ hasTaintFlow=params
|
||||
sink(field); // $ hasValueFlow=constructor
|
||||
sink(safeField); // Safe
|
||||
sink(initField); // $ hasValueFlow=init
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0
|
||||
@@ -0,0 +1,2 @@
|
||||
import java
|
||||
import TestUtilities.InlineFlowTest
|
||||
Reference in New Issue
Block a user