Skip to content

Commit 741b2a9

Browse files
authored
Merge pull request github#9207 from joefarebrother/android-external-storage
Java: Add sources for Android external storage
2 parents 6c68872 + e0b4c63 commit 741b2a9

File tree

10 files changed

+214
-3
lines changed

10 files changed

+214
-3
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
Added additional flow sources for uses of external storage on Android.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ private module Frameworks {
8484
private import internal.ContainerFlow
8585
private import semmle.code.java.frameworks.android.Android
8686
private import semmle.code.java.frameworks.android.ContentProviders
87+
private import semmle.code.java.frameworks.android.ExternalStorage
8788
private import semmle.code.java.frameworks.android.Intent
8889
private import semmle.code.java.frameworks.android.Notifications
8990
private import semmle.code.java.frameworks.android.SharedPreferences
@@ -646,7 +647,7 @@ module CsvValidation {
646647
or
647648
exists(string row, string kind | sourceModel(row) |
648649
kind = row.splitAt(";", 7) and
649-
not kind = ["remote", "contentprovider", "android-widget"] and
650+
not kind = ["remote", "contentprovider", "android-widget", "android-external-storage-dir"] and
650651
not kind.matches("qltest%") and
651652
msg = "Invalid kind \"" + kind + "\" in source model."
652653
)

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

Lines changed: 7 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.ExternalStorage
2021
import semmle.code.java.frameworks.android.OnActivityResultSource
2122
import semmle.code.java.frameworks.android.Intent
2223
import semmle.code.java.frameworks.play.Play
@@ -152,6 +153,12 @@ private class ThriftIfaceParameterSource extends RemoteFlowSource {
152153
override string getSourceType() { result = "Thrift Iface parameter" }
153154
}
154155

156+
private class AndroidExternalStorageSource extends RemoteFlowSource {
157+
AndroidExternalStorageSource() { androidExternalStorageSource(this) }
158+
159+
override string getSourceType() { result = "Android external storage" }
160+
}
161+
155162
/** Class for `tainted` user input. */
156163
abstract class UserInput extends DataFlow::Node { }
157164

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/** Provides definitions for working with uses of Android external storage */
2+
3+
import java
4+
private import semmle.code.java.security.FileReadWrite
5+
private import semmle.code.java.dataflow.DataFlow
6+
private import semmle.code.java.dataflow.ExternalFlow
7+
8+
private class ExternalStorageDirSourceModel extends SourceModelCsv {
9+
override predicate row(string row) {
10+
row =
11+
[
12+
//"package;type;overrides;name;signature;ext;spec;kind"
13+
"android.content;Context;true;getExternalFilesDir;(String);;ReturnValue;android-external-storage-dir;manual",
14+
"android.content;Context;true;getExternalFilesDirs;(String);;ReturnValue;android-external-storage-dir;manual",
15+
"android.content;Context;true;getExternalCacheDir;();;ReturnValue;android-external-storage-dir;manual",
16+
"android.content;Context;true;getExternalCacheDirs;();;ReturnValue;android-external-storage-dir;manual",
17+
"android.os;Environment;false;getExternalStorageDirectory;();;ReturnValue;android-external-storage-dir;manual",
18+
"android.os;Environment;false;getExternalStoragePublicDirectory;(String);;ReturnValue;android-external-storage-dir;manual",
19+
]
20+
}
21+
}
22+
23+
private predicate externalStorageFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
24+
DataFlow::localFlowStep(node1, node2)
25+
or
26+
exists(ConstructorCall c | c.getConstructedType() instanceof TypeFile |
27+
node1.asExpr() = c.getArgument(0) and
28+
node2.asExpr() = c
29+
)
30+
or
31+
node2.asExpr().(ArrayAccess).getArray() = node1.asExpr()
32+
or
33+
node2.asExpr().(FieldRead).getField().getInitializer() = node1.asExpr()
34+
}
35+
36+
private predicate externalStorageFlow(DataFlow::Node node1, DataFlow::Node node2) {
37+
externalStorageFlowStep*(node1, node2)
38+
}
39+
40+
/**
41+
* Holds if `n` is a node that reads the contents of an external file in Android.
42+
* This is controllable by third-party applications, so is treated as a remote flow source.
43+
*/
44+
predicate androidExternalStorageSource(DataFlow::Node n) {
45+
exists(DataFlow::Node externalDir, DirectFileReadExpr read |
46+
sourceNode(externalDir, "android-external-storage-dir") and
47+
n.asExpr() = read and
48+
externalStorageFlow(externalDir, DataFlow::exprNode(read.getFileExpr()))
49+
)
50+
}

java/ql/lib/semmle/code/java/security/FileReadWrite.qll

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import java
22

33
/**
4-
* Holds if `fileAccess` is used in the `fileReadingExpr` to read the represented file.
4+
* Holds if `fileAccess` is directly used in the `fileReadingExpr` to read the represented file.
55
*/
6-
private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
6+
private predicate directFileRead(Expr fileAccess, Expr fileReadingExpr) {
77
// `fileAccess` used to construct a class that reads a file.
88
exists(ClassInstanceExpr cie |
99
cie = fileReadingExpr and
@@ -28,6 +28,13 @@ private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
2828
])
2929
)
3030
)
31+
}
32+
33+
/**
34+
* Holds if `fileAccess` is used in the `fileReadingExpr` to read the represented file.
35+
*/
36+
private predicate fileRead(VarAccess fileAccess, Expr fileReadingExpr) {
37+
directFileRead(fileAccess, fileReadingExpr)
3138
or
3239
// The `fileAccess` is used in a call which directly or indirectly accesses the file.
3340
exists(Call call, int parameterPos, VarAccess nestedFileAccess, Expr nestedFileReadingExpr |
@@ -49,3 +56,15 @@ class FileReadExpr extends Expr {
4956
*/
5057
VarAccess getFileVarAccess() { fileRead(result, this) }
5158
}
59+
60+
/**
61+
* An expression that directly reads from a file and returns its contents.
62+
*/
63+
class DirectFileReadExpr extends Expr {
64+
DirectFileReadExpr() { directFileRead(_, this) }
65+
66+
/**
67+
* Gets the `Expr` representing the file that is read.
68+
*/
69+
Expr getFileExpr() { directFileRead(result, this) }
70+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import java.io.File;
2+
import java.io.InputStream;
3+
import java.io.FileInputStream;
4+
import java.io.IOException;
5+
import java.io.FileReader;
6+
import java.io.RandomAccessFile;
7+
import android.content.Context;
8+
import android.os.Environment;
9+
10+
class Test {
11+
void sink(Object o) {}
12+
13+
void test1(Context ctx) throws IOException {
14+
File f = new File(ctx.getExternalFilesDir(null), "file.txt");
15+
InputStream is = new FileInputStream(f);
16+
byte[] data = new byte[is.available()];
17+
is.read(data);
18+
sink(data); // $ hasTaintFlow
19+
is.close();
20+
}
21+
22+
void test2(Context ctx) throws IOException {
23+
File f = new File(new File(new File(ctx.getExternalFilesDirs(null)[0], "things"), "stuff"), "file.txt");
24+
sink(new FileInputStream(f)); // $ hasTaintFlow
25+
}
26+
27+
void test3(Context ctx) throws IOException {
28+
File f = new File(ctx.getExternalCacheDir(), "file.txt");
29+
sink(new FileInputStream(f)); // $ hasTaintFlow
30+
}
31+
32+
void test4(Context ctx) throws IOException {
33+
File f = new File(ctx.getExternalCacheDirs()[0], "file.txt");
34+
sink(new FileInputStream(f)); // $ hasTaintFlow
35+
}
36+
37+
void test5(Context ctx) throws IOException {
38+
File f = new File(Environment.getExternalStorageDirectory(), "file.txt");
39+
sink(new FileInputStream(f)); // $ hasTaintFlow
40+
}
41+
42+
void test6(Context ctx) throws IOException {
43+
File f = new File(Environment.getExternalStoragePublicDirectory(null), "file.txt");
44+
sink(new FileInputStream(f)); // $ hasTaintFlow
45+
}
46+
47+
static final File dir = Environment.getExternalStorageDirectory();
48+
49+
void test7(Context ctx) throws IOException {
50+
File f = new File(dir, "file.txt");
51+
sink(new FileInputStream(f)); // $ hasTaintFlow
52+
}
53+
54+
void test8() throws IOException {
55+
File f = new File(dir, "file.txt");
56+
sink(new FileReader(f)); // $ hasTaintFlow
57+
sink(new RandomAccessFile(f, "r")); // $ hasTaintFlow
58+
}
59+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/google-android-9.0.0

java/ql/test/library-tests/frameworks/android/external-storage/test.expected

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import java
2+
import semmle.code.java.dataflow.DataFlow
3+
import semmle.code.java.dataflow.FlowSources
4+
import TestUtilities.InlineFlowTest
5+
6+
class Conf extends TaintTracking::Configuration {
7+
Conf() { this = "test:AndroidExternalFlowConf" }
8+
9+
override predicate isSource(DataFlow::Node src) { src instanceof RemoteFlowSource }
10+
11+
override predicate isSink(DataFlow::Node sink) {
12+
sink.asExpr().(Argument).getCall().getCallee().hasName("sink")
13+
}
14+
}
15+
16+
class ExternalStorageTest extends InlineFlowTest {
17+
override DataFlow::Configuration getValueFlowConfig() { none() }
18+
19+
override DataFlow::Configuration getTaintFlowConfig() { result instanceof Conf }
20+
}

java/ql/test/stubs/google-android-9.0.0/android/os/Environment.java

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)