Skip to content

Commit 8224f17

Browse files
authored
Merge pull request github#14257 from michaelnebel/java/threatmodelsources
Java: Introduce a class of dataflow nodes for the threat modeling.
2 parents df988e4 + fcbd301 commit 8224f17

27 files changed

+576
-23
lines changed

java/ql/lib/ext/threatmodels/threat-model-grouping.model.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,17 @@ extensions:
66
data:
77
# Default threat model
88
- ["remote", "default"]
9-
- ["uri-path", "default"]
10-
11-
# Android threat models
12-
- ["android-external-storage-dir", "android"]
13-
- ["contentprovider", "android"]
149

1510
# Remote threat models
1611
- ["request", "remote"]
1712
- ["response", "remote"]
1813

1914
# Local threat models
2015
- ["database", "local"]
21-
- ["cli", "local"]
16+
- ["commandargs", "local"]
2217
- ["environment", "local"]
2318
- ["file", "local"]
19+
20+
# Android threat models
21+
- ["android-external-storage-dir", "android"]
22+
- ["contentprovider", "android"]

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ private string getChildThreatModel(string group) { threatModelGrouping(result, g
2626
* Holds if the source model kind `kind` is relevant for generic queries
2727
* under the current threat model configuration.
2828
*/
29-
predicate sourceModelKindConfig(string kind) {
29+
predicate currentThreatModel(string kind) {
3030
exists(string group | supportedThreatModels(group) and kind = getChildThreatModel*(group))
3131
}

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

Lines changed: 97 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,42 @@ import semmle.code.java.frameworks.struts.StrutsActions
2929
import semmle.code.java.frameworks.Thrift
3030
import semmle.code.java.frameworks.javaee.jsf.JSFRenderer
3131
private import semmle.code.java.dataflow.ExternalFlow
32+
private import semmle.code.java.dataflow.ExternalFlowConfiguration
33+
34+
/**
35+
* A data flow source.
36+
*/
37+
abstract class SourceNode extends DataFlow::Node {
38+
/**
39+
* Gets a string that represents the source kind with respect to threat modeling.
40+
*/
41+
abstract string getThreatModel();
42+
}
43+
44+
/**
45+
* A class of data flow sources that respects the
46+
* current threat model configuration.
47+
*/
48+
class ThreatModelFlowSource extends DataFlow::Node {
49+
ThreatModelFlowSource() {
50+
// Expansive threat model.
51+
currentThreatModel("all") and
52+
(this instanceof SourceNode or sourceNode(this, _))
53+
or
54+
exists(string kind |
55+
// Specific threat model.
56+
currentThreatModel(kind) and
57+
(this.(SourceNode).getThreatModel() = kind or sourceNode(this, kind))
58+
)
59+
}
60+
}
3261

3362
/** A data flow source of remote user input. */
34-
abstract class RemoteFlowSource extends DataFlow::Node {
63+
abstract class RemoteFlowSource extends SourceNode {
3564
/** Gets a string that describes the type of this remote flow source. */
3665
abstract string getSourceType();
66+
67+
override string getThreatModel() { result = "remote" }
3768
}
3869

3970
/**
@@ -175,14 +206,47 @@ abstract class UserInput extends DataFlow::Node { }
175206
private class RemoteUserInput extends UserInput instanceof RemoteFlowSource { }
176207

177208
/** A node with input that may be controlled by a local user. */
178-
abstract class LocalUserInput extends UserInput { }
209+
abstract class LocalUserInput extends UserInput, SourceNode {
210+
override string getThreatModel() { result = "local" }
211+
}
179212

180213
/**
214+
* DEPRECATED: Use the threat models feature.
215+
* That is, use `ThreatModelFlowSource` as the class of nodes for sources
216+
* and set up the threat model configuration to filter source nodes.
217+
* Alternatively, use `getThreatModel` to filter nodes to create the
218+
* class of nodes you need.
219+
*
181220
* A node with input from the local environment, such as files, standard in,
182221
* environment variables, and main method parameters.
183222
*/
184-
class EnvInput extends LocalUserInput {
223+
deprecated class EnvInput extends DataFlow::Node {
185224
EnvInput() {
225+
this instanceof EnvironmentInput or
226+
this instanceof CliInput or
227+
this instanceof FileInput
228+
}
229+
}
230+
231+
/**
232+
* A node with input from the local environment, such as
233+
* environment variables.
234+
*/
235+
private class EnvironmentInput extends LocalUserInput {
236+
EnvironmentInput() {
237+
// Results from various specific methods.
238+
this.asExpr().(MethodAccess).getMethod() instanceof EnvReadMethod
239+
}
240+
241+
override string getThreatModel() { result = "environment" }
242+
}
243+
244+
/**
245+
* A node with input from the command line, such as standard in
246+
* and main method parameters.
247+
*/
248+
private class CliInput extends LocalUserInput {
249+
CliInput() {
186250
// Parameters to a main method.
187251
exists(MainMethod main | this.asParameter() = main.getParameter(0))
188252
or
@@ -191,23 +255,46 @@ class EnvInput extends LocalUserInput {
191255
f.getAnAnnotation().getType().getQualifiedName() = "org.kohsuke.args4j.Argument"
192256
)
193257
or
194-
// Results from various specific methods.
195-
this.asExpr().(MethodAccess).getMethod() instanceof EnvReadMethod
196-
or
197258
// Access to `System.in`.
198259
exists(Field f | this.asExpr() = f.getAnAccess() | f instanceof SystemIn)
199-
or
260+
}
261+
262+
override string getThreatModel() { result = "commandargs" }
263+
}
264+
265+
/**
266+
* A node with input from the local environment, such as files.
267+
*/
268+
private class FileInput extends LocalUserInput {
269+
FileInput() {
200270
// Access to files.
201271
this.asExpr()
202272
.(ConstructorCall)
203273
.getConstructedType()
204274
.hasQualifiedName("java.io", "FileInputStream")
205275
}
276+
277+
override string getThreatModel() { result = "file" }
206278
}
207279

208-
/** A node with input from a database. */
209-
class DatabaseInput extends LocalUserInput {
210-
DatabaseInput() { this.asExpr().(MethodAccess).getMethod() instanceof ResultSetGetStringMethod }
280+
/**
281+
* DEPRECATED: Use the threat models feature.
282+
* That is, use `ThreatModelFlowSource` as the class of nodes for sources
283+
* and set up the threat model configuration to filter source nodes.
284+
* Alternatively, use `getThreatModel` to filter nodes to create the
285+
* class of nodes you need.
286+
*
287+
* A node with input from a database.
288+
*/
289+
deprecated class DatabaseInput = DbInput;
290+
291+
/**
292+
* A node with input from a database.
293+
*/
294+
private class DbInput extends LocalUserInput {
295+
DbInput() { this.asExpr().(MethodAccess).getMethod() instanceof ResultSetGetStringMethod }
296+
297+
override string getThreatModel() { result = "database" }
211298
}
212299

213300
/** A method that reads from the environment, such as `System.getProperty` or `System.getenv`. */

java/ql/lib/semmle/code/java/frameworks/hudson/Hudson.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ private class FilePathRead extends LocalUserInput {
2424
"readToString"
2525
])
2626
}
27+
28+
override string getThreatModel() { result = "file" }
2729
}
2830

2931
private class HudsonUtilXssSanitizer extends XssSanitizer {

java/ql/test/library-tests/dataflow/threat-models/Empty.java

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import java.sql.*;
2+
import java.net.*;
3+
import java.util.logging.*;
4+
import java.nio.charset.StandardCharsets;
5+
import testlib.TestSources;
6+
7+
class Test {
8+
private TestSources sources = new TestSources();
9+
10+
private String byteToString(byte[] data) {
11+
return new String(data, StandardCharsets.UTF_8);
12+
}
13+
14+
public void M1(Statement handle) throws Exception {
15+
// Only a source if "remote" is a selected threat model.
16+
// This is included in the "default" threat model.
17+
Socket sock = new Socket("localhost", 1234);
18+
byte[] data = new byte[1024];
19+
sock.getInputStream().read(data);
20+
21+
// Logging sink
22+
Logger.getLogger("foo").severe(byteToString(data));
23+
24+
// SQL sink
25+
handle.executeUpdate("INSERT INTO foo VALUES ('" + byteToString(data) + "')");
26+
}
27+
28+
public void M2(Statement handle) throws Exception {
29+
// Only a source if "database" is a selected threat model.
30+
String result = sources.executeQuery("SELECT * FROM foo");
31+
32+
// SQL sink
33+
handle.executeUpdate("INSERT INTO foo VALUES ('" + result + "')");
34+
35+
// Logging sink
36+
Logger.getLogger("foo").severe(result);
37+
}
38+
39+
public void M3(Statement handle) throws Exception {
40+
// Only a source if "environment" is a selected threat model.
41+
String result = sources.readEnv("MY_ENV_VAR");
42+
43+
// SQL sink
44+
handle.executeUpdate("INSERT INTO foo VALUES ('" + result + "')");
45+
46+
// Logging sink
47+
Logger.getLogger("foo").severe(result);
48+
}
49+
50+
public void M4(Statement handle) throws Exception {
51+
// Only a source if "custom" is a selected threat model.
52+
String result = sources.getCustom("custom");
53+
54+
// SQL sink
55+
handle.executeUpdate("INSERT INTO foo VALUES ('" + result + "')");
56+
57+
// Logging sink
58+
Logger.getLogger("foo").severe(result);
59+
}
60+
61+
public void M5(Statement handle) throws Exception {
62+
// Only a source if "commandargs" is a selected threat model.
63+
byte[] data = new byte[1024];
64+
System.in.read(data);
65+
66+
// SQL sink
67+
handle.executeUpdate("INSERT INTO foo VALUES ('" + byteToString(data) + "')");
68+
69+
// Logging sink
70+
Logger.getLogger("foo").severe(byteToString(data));
71+
}
72+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
private import java
2+
private import semmle.code.java.dataflow.DataFlow
3+
private import semmle.code.java.dataflow.ExternalFlow
4+
private import semmle.code.java.dataflow.FlowSources
5+
private import semmle.code.java.dataflow.TaintTracking
6+
7+
private module ThreatModelConfig implements DataFlow::ConfigSig {
8+
predicate isSource(DataFlow::Node source) { source instanceof ThreatModelFlowSource }
9+
10+
predicate isSink(DataFlow::Node sink) { sinkNode(sink, _) }
11+
}
12+
13+
module ThreatModel = TaintTracking::Global<ThreatModelConfig>;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package testlib;
2+
3+
public class TestSources {
4+
public String executeQuery(String query) { return null; }
5+
6+
public String readEnv(String env) { return null; }
7+
8+
public String getCustom(String s) { return null;}
9+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
edges
2+
| Test.java:10:31:10:41 | data : byte[] | Test.java:11:23:11:26 | data : byte[] |
3+
| Test.java:11:23:11:26 | data : byte[] | Test.java:11:12:11:51 | new String(...) : String |
4+
| Test.java:19:5:19:25 | getInputStream(...) : InputStream | Test.java:19:32:19:35 | data [post update] : byte[] |
5+
| Test.java:19:32:19:35 | data [post update] : byte[] | Test.java:22:49:22:52 | data : byte[] |
6+
| Test.java:19:32:19:35 | data [post update] : byte[] | Test.java:25:69:25:72 | data : byte[] |
7+
| Test.java:22:49:22:52 | data : byte[] | Test.java:10:31:10:41 | data : byte[] |
8+
| Test.java:22:49:22:52 | data : byte[] | Test.java:22:36:22:53 | byteToString(...) |
9+
| Test.java:25:56:25:73 | byteToString(...) : String | Test.java:25:26:25:80 | ... + ... |
10+
| Test.java:25:69:25:72 | data : byte[] | Test.java:10:31:10:41 | data : byte[] |
11+
| Test.java:25:69:25:72 | data : byte[] | Test.java:25:56:25:73 | byteToString(...) : String |
12+
nodes
13+
| Test.java:10:31:10:41 | data : byte[] | semmle.label | data : byte[] |
14+
| Test.java:11:12:11:51 | new String(...) : String | semmle.label | new String(...) : String |
15+
| Test.java:11:23:11:26 | data : byte[] | semmle.label | data : byte[] |
16+
| Test.java:19:5:19:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
17+
| Test.java:19:32:19:35 | data [post update] : byte[] | semmle.label | data [post update] : byte[] |
18+
| Test.java:22:36:22:53 | byteToString(...) | semmle.label | byteToString(...) |
19+
| Test.java:22:49:22:52 | data : byte[] | semmle.label | data : byte[] |
20+
| Test.java:25:26:25:80 | ... + ... | semmle.label | ... + ... |
21+
| Test.java:25:56:25:73 | byteToString(...) : String | semmle.label | byteToString(...) : String |
22+
| Test.java:25:69:25:72 | data : byte[] | semmle.label | data : byte[] |
23+
subpaths
24+
| Test.java:22:49:22:52 | data : byte[] | Test.java:10:31:10:41 | data : byte[] | Test.java:11:12:11:51 | new String(...) : String | Test.java:22:36:22:53 | byteToString(...) |
25+
| Test.java:25:69:25:72 | data : byte[] | Test.java:10:31:10:41 | data : byte[] | Test.java:11:12:11:51 | new String(...) : String | Test.java:25:56:25:73 | byteToString(...) : String |
26+
#select
27+
| Test.java:19:5:19:25 | getInputStream(...) : InputStream | Test.java:22:36:22:53 | byteToString(...) |
28+
| Test.java:19:5:19:25 | getInputStream(...) : InputStream | Test.java:25:26:25:80 | ... + ... |
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
extensions:
2+
3+
- addsTo:
4+
pack: codeql/java-all
5+
extensible: supportedThreatModels
6+
data: []
7+
8+
- addsTo:
9+
pack: codeql/java-all
10+
extensible: sourceModel
11+
data:
12+
- ["testlib", "TestSources", False, "executeQuery", "(String)", "", "ReturnValue", "database", "manual"]
13+
- ["testlib", "TestSources", False, "readEnv", "(String)", "", "ReturnValue", "environment", "manual"]
14+
- ["testlib", "TestSources", False, "getCustom", "(String)", "", "ReturnValue", "custom", "manual"]

0 commit comments

Comments
 (0)