Move common code to a library and add more test cases

This commit is contained in:
luchua-bc
2020-11-06 04:44:56 +00:00
parent b10552aa2e
commit bc899b6337
6 changed files with 81 additions and 74 deletions

View File

@@ -1,7 +1,7 @@
/**
* @name Local Android DoS Caused By NumberFormatException
* @id java/android/nfe-local-android-dos
* @description NumberFormatException thrown but not caught by an Android application that allows external inputs can crash the application, which is a local Denial of Service (Dos) attack.
* @description NumberFormatException thrown but not caught by an Android application that allows external inputs can crash the application, constituting a local Denial of Service (DoS) attack.
* @kind path-problem
* @tags security
* external/cwe/cwe-755
@@ -10,74 +10,9 @@
import java
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.NumberFormatException
import DataFlow::PathGraph
/** Code from java/ql/src/Violations of Best Practice/Exception Handling/NumberFormatException.ql */
private class SpecialMethodAccess extends MethodAccess {
predicate isValueOfMethod(string klass) {
this.getMethod().getName() = "valueOf" and
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass) and
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String")
}
predicate isParseMethod(string klass, string name) {
this.getMethod().getName() = name and
this.getQualifier().getType().(RefType).hasQualifiedName("java.lang", klass)
}
predicate throwsNFE() {
this.isParseMethod("Byte", "parseByte") or
this.isParseMethod("Short", "parseShort") or
this.isParseMethod("Integer", "parseInt") or
this.isParseMethod("Long", "parseLong") or
this.isParseMethod("Float", "parseFloat") or
this.isParseMethod("Double", "parseDouble") or
this.isParseMethod("Byte", "decode") or
this.isParseMethod("Short", "decode") or
this.isParseMethod("Integer", "decode") or
this.isParseMethod("Long", "decode") or
this.isValueOfMethod("Byte") or
this.isValueOfMethod("Short") or
this.isValueOfMethod("Integer") or
this.isValueOfMethod("Long") or
this.isValueOfMethod("Float") or
this.isValueOfMethod("Double")
}
}
private class SpecialClassInstanceExpr extends ClassInstanceExpr {
predicate isStringConstructor(string klass) {
this.getType().(RefType).hasQualifiedName("java.lang", klass) and
this.getAnArgument().getType().(RefType).hasQualifiedName("java.lang", "String") and
this.getNumArgument() = 1
}
predicate throwsNFE() {
this.isStringConstructor("Byte") or
this.isStringConstructor("Short") or
this.isStringConstructor("Integer") or
this.isStringConstructor("Long") or
this.isStringConstructor("Float") or
this.isStringConstructor("Double")
}
}
class NumberFormatException extends RefType {
NumberFormatException() { this.hasQualifiedName("java.lang", "NumberFormatException") }
}
private predicate catchesNFE(TryStmt t) {
exists(CatchClause cc, LocalVariableDeclExpr v |
t.getACatchClause() = cc and
cc.getVariable() = v and
v.getType().(RefType).getASubtype*() instanceof NumberFormatException
)
}
private predicate throwsNFE(Expr e) {
e.(SpecialClassInstanceExpr).throwsNFE() or e.(SpecialMethodAccess).throwsNFE()
}
/**
* Taint configuration tracking flow from untrusted inputs to number conversion calls in exported Android compononents.
*/
@@ -90,7 +25,7 @@ class NFELocalDoSConfiguration extends TaintTracking::Configuration {
/** Holds if NFE is thrown but not caught */
override predicate isSink(DataFlow::Node sink) {
exists(Expr e |
e.getEnclosingCallable().getDeclaringType() instanceof ExportableAndroidComponent and
e.getEnclosingCallable().getDeclaringType().(ExportableAndroidComponent).isExported() and
throwsNFE(e) and
not exists(TryStmt t |
t.getBlock() = e.getEnclosingStmt().getEnclosingStmt*() and
@@ -103,5 +38,6 @@ class NFELocalDoSConfiguration extends TaintTracking::Configuration {
from DataFlow::PathNode source, DataFlow::PathNode sink, NFELocalDoSConfiguration conf
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Local Android Denial of Service due to $@.", source.getNode(),
select sink.getNode(), source, sink,
"Uncaught NumberFormatException in an exported Android component due to $@.", source.getNode(),
"user-provided value"

View File

@@ -19,6 +19,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SafeActivity" />
</application>
</manifest>

View File

@@ -0,0 +1,16 @@
package com.example.app;
import android.app.Activity;
import android.os.Bundle;
/** A utility program for getting intent extra information from Android activity */
public class IntentUtils {
/** Get double extra */
public static double getDoubleExtra(Activity a, String key) {
String value = a.getIntent().getStringExtra(key);
return Double.parseDouble(value);
}
}

View File

@@ -15,8 +15,8 @@ nodes
| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | semmle.label | new Double(...) |
| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | semmle.label | valueOf(...) |
#select
| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | Local Android Denial of Service due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) : Intent | NFEAndroidDoS.java:14:21:14:51 | parseDouble(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:13:24:13:34 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) : Intent | NFEAndroidDoS.java:23:15:23:40 | parseInt(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:22:21:22:31 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) : Intent | NFEAndroidDoS.java:26:16:26:42 | parseInt(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:25:22:25:32 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:44:21:44:43 | new Double(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:44:21:44:43 | new Double(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value |
| NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) : Intent | NFEAndroidDoS.java:47:21:47:47 | valueOf(...) | Uncaught NumberFormatException in an exported Android component due to $@. | NFEAndroidDoS.java:43:24:43:34 | getIntent(...) | user-provided value |

View File

@@ -46,4 +46,41 @@ public class NFEAndroidDoS extends Activity {
String maxPriceStr = getIntent().getStringExtra("priceMax");
double maxPrice = Double.valueOf(minPriceStr);
}
// GOOD - parse string extra to double with caught NFE
public void testOnCreate5(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(-1);
double minPrice = 0;
try {
String minPriceStr = getIntent().getStringExtra("priceMin");
minPrice = Double.parseDouble(minPriceStr);
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
}
}
// GOOD - parse string extra to double with caught NFE as the supertype Throwable
public void testOnCreate6(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(-1);
double minPrice = 0;
try {
String minPriceStr = getIntent().getStringExtra("priceMin");
minPrice = Double.parseDouble(minPriceStr);
} catch (Throwable te) {
te.printStackTrace();
}
}
// BAD - parse string extra to double
// Note this case of invoking utility method that takes an Activity a then calls `a.getIntent().getStringExtra(...)` is not yet detected thus is beyond what the query is capable of.
public void testOnCreate7(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(-1);
double priceMin = IntentUtils.getDoubleExtra(this, "priceMin");
}
}

View File

@@ -0,0 +1,16 @@
package com.example.app;
import android.app.Activity;
import android.os.Bundle;
/** Android activity that tests app crash by NumberFormatException, which is not exported in `AndroidManifest.xml` */
public class SafeActivity extends Activity {
// BAD - parse string extra to double
public void testOnCreate1(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(-1);
String minPriceStr = getIntent().getStringExtra("priceMin");
double minPrice = Double.parseDouble(minPriceStr);
}
}