If an HTTP Header is built using string concatenation or string formatting, and the
+components of the concatenation include user input, a user
+is likely to be able to manipulate the response.
+
+
+
+
User input should not be included in an HTTP Header.
+
+
+
+
In the following example, the code appends a user-provided value into a header.
+
+
From 789c5857fa2962a31e3271594af2971519eb072a Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 9 Apr 2021 01:26:28 +0200
Subject: [PATCH 008/361] Create qhelp example
---
.../experimental/Security/CWE-113/header_injection.py | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 python/ql/src/experimental/Security/CWE-113/header_injection.py
diff --git a/python/ql/src/experimental/Security/CWE-113/header_injection.py b/python/ql/src/experimental/Security/CWE-113/header_injection.py
new file mode 100644
index 00000000000..117383710e3
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-113/header_injection.py
@@ -0,0 +1,9 @@
+from flask import Response, request, Flask, make_response
+
+
+@app.route("/flask_Response")
+def flask_Response():
+ rfs_header = request.args["rfs_header"]
+ response = Response()
+ response.headers['HeaderName'] = rfs_header
+ return response
From e9c457455252654ce4fdade7edaf7e4047e2723b Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 9 Apr 2021 01:26:53 +0200
Subject: [PATCH 009/361] Apply structure
---
.../Security/CWE-113/HeaderInjection.ql | 154 +-----------------
.../Security/CWE-113/HeaderInjection.qlref | 0
.../experimental/semmle/python/Concepts.qll | 14 ++
.../semmle/python/frameworks/Stdlib.qll | 130 +++++++++++++++
.../Security/CWE-113}/django_bad.py | 0
.../Security/CWE-113}/flask_bad.py | 0
6 files changed, 150 insertions(+), 148 deletions(-)
delete mode 100644 python/ql/src/experimental/Security/CWE-113/HeaderInjection.qlref
rename python/ql/{src/experimental/Security/CWE-113/tests => test/experimental/query-tests/Security/CWE-113}/django_bad.py (100%)
rename python/ql/{src/experimental/Security/CWE-113/tests => test/experimental/query-tests/Security/CWE-113}/flask_bad.py (100%)
diff --git a/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql b/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql
index c5d2984c150..3cb4a20d5de 100644
--- a/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql
+++ b/python/ql/src/experimental/Security/CWE-113/HeaderInjection.ql
@@ -1,10 +1,10 @@
/**
* @name HTTP Header Injection
- * @description User input should not be used in HTTP headers without first being escaped,
- * otherwise a malicious user may be able to inject a value that could manipulate the response.
+ * @description User input should not be used in HTTP headers, otherwise a malicious user
+ * may be able to inject a value that could manipulate the response.
* @kind path-problem
* @problem.severity error
- * @id python/header-injection
+ * @id py/header-injection
* @tags security
* external/cwe/cwe-113
* external/cwe/cwe-079
@@ -12,152 +12,10 @@
// determine precision above
import python
-import semmle.python.dataflow.new.RemoteFlowSources
-import semmle.python.dataflow.new.DataFlow
-import semmle.python.dataflow.new.TaintTracking
-import semmle.python.ApiGraphs
+import experimental.semmle.python.security.injection.HTTPHeaders
import DataFlow::PathGraph
-class WerkzeugHeaderCall extends DataFlow::CallCfgNode {
- WerkzeugHeaderCall() {
- exists(DataFlow::AttrRead addMethod |
- this.getFunction() = addMethod and
- addMethod.getObject().getALocalSource() =
- API::moduleImport("werkzeug").getMember("datastructures").getMember("Headers").getACall() and
- addMethod.getAttributeName() = "add"
- )
- }
-
- DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
-}
-
-class FlaskHeaderCall extends DataFlow::Node {
- DataFlow::Node headerInputNode;
-
- FlaskHeaderCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
- AssignStmt sinkDeclaration
- |
- headerInstance = API::moduleImport("flask").getMember("Response").getACall() and
- responseMethod.getAttributeName() = "headers" and
- responseMethod.getObject().getALocalSource() = headerInstance and
- sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
- headerInputNode.asExpr() = sinkDeclaration.getValue() and
- this.asExpr() = sinkDeclaration.getATarget()
- )
- }
-
- DataFlow::Node getHeaderInputNode() { result = headerInputNode }
-}
-
-class FlaskMakeResponseCall extends DataFlow::Node {
- DataFlow::Node headerInputNode;
-
- FlaskMakeResponseCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
- AssignStmt sinkDeclaration
- |
- headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
- responseMethod.getAttributeName() = "headers" and
- responseMethod.getObject().getALocalSource() = headerInstance and
- sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
- this.asExpr() = sinkDeclaration.getATarget() and
- headerInputNode.asExpr() = sinkDeclaration.getValue()
- )
- }
-
- DataFlow::Node getHeaderInputNode() { result = headerInputNode }
-}
-
-class FlaskMakeResponseExtendCall extends DataFlow::CallCfgNode {
- DataFlow::Node headerInputNode;
-
- FlaskMakeResponseExtendCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
- DataFlow::AttrRead extendMethod
- |
- headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
- responseMethod.getAttributeName() = "headers" and
- responseMethod.getObject().getALocalSource() = headerInstance and
- extendMethod.getAttributeName() = "extend" and
- extendMethod.getObject().getALocalSource() = responseMethod and
- this.getFunction() = extendMethod and
- headerInputNode = this.getArg(0)
- )
- }
-
- DataFlow::Node getHeaderInputNode() { result = headerInputNode }
-}
-
-class FlaskResponseArg extends DataFlow::CallCfgNode {
- DataFlow::Node headerInputNode;
-
- FlaskResponseArg() {
- this = API::moduleImport("flask").getMember("Response").getACall() and
- headerInputNode = this.getArgByName("headers")
- }
-
- DataFlow::Node getHeaderInputNode() { result = headerInputNode }
-}
-
-class DjangoResponseSetItemCall extends DataFlow::CallCfgNode {
- DjangoResponseSetItemCall() {
- exists(DataFlow::AttrRead setItemMethod |
- this.getFunction() = setItemMethod and
- setItemMethod.getObject().getALocalSource() =
- API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
- setItemMethod.getAttributeName() = "__setitem__"
- )
- }
-
- DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
-}
-
-class DjangoResponseAssignCall extends DataFlow::Node {
- DataFlow::Node headerInputNode;
-
- DjangoResponseAssignCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, Subscript responseMethod, DataFlow::Node responseToNode,
- AssignStmt sinkDeclaration
- |
- headerInstance =
- API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
- responseMethod.getValue() = responseToNode.asExpr() and
- responseToNode.getALocalSource().asExpr() = headerInstance.asExpr() and
- sinkDeclaration.getATarget() = responseMethod and
- this.asExpr() = sinkDeclaration.getATarget() and
- headerInputNode.asExpr() = sinkDeclaration.getValue()
- )
- }
-
- DataFlow::Node getHeaderInputNode() { result = headerInputNode }
-}
-
-class HeaderInjectionSink extends DataFlow::Node {
- HeaderInjectionSink() {
- this = any(WerkzeugHeaderCall a).getHeaderInputNode() or
- this = any(FlaskHeaderCall a).getHeaderInputNode() or
- this = any(FlaskMakeResponseCall a).getHeaderInputNode() or
- this = any(FlaskMakeResponseExtendCall a).getHeaderInputNode() or
- this = any(FlaskResponseArg a).getHeaderInputNode() or
- this = any(DjangoResponseSetItemCall a).getHeaderInputNode() or
- this = any(DjangoResponseAssignCall a).getHeaderInputNode()
- }
-}
-
-class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
- HeaderInjectionFlowConfig() { this = "HeaderInjectionFlowConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof HeaderInjectionSink }
-}
-
from HeaderInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "$@ header is constructed from a $@.", sink.getNode(), "This",
- source.getNode(), "user-provided value"
+select sink.getNode(), source, sink, "$@ HTTP header is constructed from a $@.", sink.getNode(),
+ "This", source.getNode(), "user-provided value"
diff --git a/python/ql/src/experimental/Security/CWE-113/HeaderInjection.qlref b/python/ql/src/experimental/Security/CWE-113/HeaderInjection.qlref
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index 904b7967ee8..530b8d20ec6 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -13,3 +13,17 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
+
+module HeaderDeclaration {
+ abstract class Range extends DataFlow::Node {
+ abstract DataFlow::Node getHeaderInputNode();
+ }
+}
+
+class HeaderDeclaration extends DataFlow::Node {
+ HeaderDeclaration::Range range;
+
+ HeaderDeclaration() { this = range }
+
+ DataFlow::Node getHeaderInputNode() { result = range.getHeaderInputNode() }
+}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll b/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
index 420caf0d73b..636a0621bd4 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
@@ -9,3 +9,133 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
+
+private module Headers {
+ private module Werkzeug {
+ class WerkzeugHeaderCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ WerkzeugHeaderCall() {
+ exists(DataFlow::AttrRead addMethod |
+ this.getFunction() = addMethod and
+ addMethod.getObject().getALocalSource() =
+ API::moduleImport("werkzeug")
+ .getMember("datastructures")
+ .getMember("Headers")
+ .getACall() and
+ addMethod.getAttributeName() = "add"
+ )
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
+ }
+ }
+
+ private module Flask {
+ class FlaskHeaderCall extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInputNode;
+
+ FlaskHeaderCall() {
+ exists(
+ DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
+ AssignStmt sinkDeclaration
+ |
+ headerInstance = API::moduleImport("flask").getMember("Response").getACall() and
+ responseMethod.getAttributeName() = "headers" and
+ responseMethod.getObject().getALocalSource() = headerInstance and
+ sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
+ headerInputNode.asExpr() = sinkDeclaration.getValue() and
+ this.asExpr() = sinkDeclaration.getATarget()
+ )
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
+ }
+
+ class FlaskMakeResponseCall extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInputNode;
+
+ FlaskMakeResponseCall() {
+ exists(
+ DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
+ AssignStmt sinkDeclaration
+ |
+ headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
+ responseMethod.getAttributeName() = "headers" and
+ responseMethod.getObject().getALocalSource() = headerInstance and
+ sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
+ this.asExpr() = sinkDeclaration.getATarget() and
+ headerInputNode.asExpr() = sinkDeclaration.getValue()
+ )
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
+ }
+
+ class FlaskMakeResponseExtendCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ DataFlow::Node headerInputNode;
+
+ FlaskMakeResponseExtendCall() {
+ exists(
+ DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
+ DataFlow::AttrRead extendMethod
+ |
+ headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
+ responseMethod.getAttributeName() = "headers" and
+ responseMethod.getObject().getALocalSource() = headerInstance and
+ extendMethod.getAttributeName() = "extend" and
+ extendMethod.getObject().getALocalSource() = responseMethod and
+ this.getFunction() = extendMethod and
+ headerInputNode = this.getArg(0)
+ )
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
+ }
+
+ class FlaskResponseArg extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ DataFlow::Node headerInputNode;
+
+ FlaskResponseArg() {
+ this = API::moduleImport("flask").getMember("Response").getACall() and
+ headerInputNode = this.getArgByName("headers")
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
+ }
+
+ class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ DjangoResponseSetItemCall() {
+ exists(DataFlow::AttrRead setItemMethod |
+ this.getFunction() = setItemMethod and
+ setItemMethod.getObject().getALocalSource() =
+ API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
+ setItemMethod.getAttributeName() = "__setitem__"
+ )
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
+ }
+ }
+
+ private module Django {
+ class DjangoResponseAssignCall extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInputNode;
+
+ DjangoResponseAssignCall() {
+ exists(
+ DataFlow::CallCfgNode headerInstance, Subscript responseMethod,
+ DataFlow::Node responseToNode, AssignStmt sinkDeclaration
+ |
+ headerInstance =
+ API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
+ responseMethod.getValue() = responseToNode.asExpr() and
+ responseToNode.getALocalSource().asExpr() = headerInstance.asExpr() and
+ sinkDeclaration.getATarget() = responseMethod and
+ this.asExpr() = sinkDeclaration.getATarget() and
+ headerInputNode.asExpr() = sinkDeclaration.getValue()
+ )
+ }
+
+ override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
+ }
+ }
+}
diff --git a/python/ql/src/experimental/Security/CWE-113/tests/django_bad.py b/python/ql/test/experimental/query-tests/Security/CWE-113/django_bad.py
similarity index 100%
rename from python/ql/src/experimental/Security/CWE-113/tests/django_bad.py
rename to python/ql/test/experimental/query-tests/Security/CWE-113/django_bad.py
diff --git a/python/ql/src/experimental/Security/CWE-113/tests/flask_bad.py b/python/ql/test/experimental/query-tests/Security/CWE-113/flask_bad.py
similarity index 100%
rename from python/ql/src/experimental/Security/CWE-113/tests/flask_bad.py
rename to python/ql/test/experimental/query-tests/Security/CWE-113/flask_bad.py
From 632dc61d5e6778fe9aa6767f257e0942b3be0010 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 9 Apr 2021 01:28:22 +0200
Subject: [PATCH 010/361] Create qlref
---
.../query-tests/Security/CWE-113/HeaderInjection.qlref | 1 +
1 file changed, 1 insertion(+)
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.qlref
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.qlref b/python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.qlref
new file mode 100644
index 00000000000..915175a7b6a
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-113/HeaderInjection.ql
From f02c2855adc8e017f9c1d1a60aff3bc8124e27f0 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 9 Apr 2021 01:28:38 +0200
Subject: [PATCH 011/361] Generate .expected
---
.../Security/CWE-113/HeaderInjection.expected | 43 +++++++++++++++++++
1 file changed, 43 insertions(+)
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.expected
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.expected b/python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.expected
new file mode 100644
index 00000000000..9e0eff704b6
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-113/HeaderInjection.expected
@@ -0,0 +1,43 @@
+edges
+| flask_bad.py:9:18:9:24 | ControlFlowNode for request | flask_bad.py:9:18:9:29 | ControlFlowNode for Attribute |
+| flask_bad.py:9:18:9:29 | ControlFlowNode for Attribute | flask_bad.py:9:18:9:43 | ControlFlowNode for Subscript |
+| flask_bad.py:9:18:9:43 | ControlFlowNode for Subscript | flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header |
+| flask_bad.py:19:18:19:24 | ControlFlowNode for request | flask_bad.py:19:18:19:29 | ControlFlowNode for Attribute |
+| flask_bad.py:19:18:19:29 | ControlFlowNode for Attribute | flask_bad.py:19:18:19:43 | ControlFlowNode for Subscript |
+| flask_bad.py:19:18:19:43 | ControlFlowNode for Subscript | flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header |
+| flask_bad.py:27:18:27:24 | ControlFlowNode for request | flask_bad.py:27:18:27:29 | ControlFlowNode for Attribute |
+| flask_bad.py:27:18:27:29 | ControlFlowNode for Attribute | flask_bad.py:27:18:27:43 | ControlFlowNode for Subscript |
+| flask_bad.py:27:18:27:43 | ControlFlowNode for Subscript | flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header |
+| flask_bad.py:35:18:35:24 | ControlFlowNode for request | flask_bad.py:35:18:35:29 | ControlFlowNode for Attribute |
+| flask_bad.py:35:18:35:29 | ControlFlowNode for Attribute | flask_bad.py:35:18:35:43 | ControlFlowNode for Subscript |
+| flask_bad.py:35:18:35:43 | ControlFlowNode for Subscript | flask_bad.py:38:9:38:34 | ControlFlowNode for Dict |
+| flask_bad.py:44:44:44:50 | ControlFlowNode for request | flask_bad.py:44:44:44:55 | ControlFlowNode for Attribute |
+| flask_bad.py:44:44:44:55 | ControlFlowNode for Attribute | flask_bad.py:44:44:44:69 | ControlFlowNode for Subscript |
+| flask_bad.py:44:44:44:69 | ControlFlowNode for Subscript | flask_bad.py:44:29:44:70 | ControlFlowNode for Dict |
+nodes
+| flask_bad.py:9:18:9:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| flask_bad.py:9:18:9:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| flask_bad.py:9:18:9:43 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
+| flask_bad.py:19:18:19:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| flask_bad.py:19:18:19:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| flask_bad.py:19:18:19:43 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
+| flask_bad.py:27:18:27:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| flask_bad.py:27:18:27:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| flask_bad.py:27:18:27:43 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | semmle.label | ControlFlowNode for rfs_header |
+| flask_bad.py:35:18:35:24 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| flask_bad.py:35:18:35:29 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| flask_bad.py:35:18:35:43 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| flask_bad.py:38:9:38:34 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
+| flask_bad.py:44:29:44:70 | ControlFlowNode for Dict | semmle.label | ControlFlowNode for Dict |
+| flask_bad.py:44:44:44:50 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| flask_bad.py:44:44:44:55 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| flask_bad.py:44:44:44:69 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+#select
+| flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | flask_bad.py:9:18:9:24 | ControlFlowNode for request | flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | $@ HTTP header is constructed from a $@. | flask_bad.py:12:31:12:40 | ControlFlowNode for rfs_header | This | flask_bad.py:9:18:9:24 | ControlFlowNode for request | user-provided value |
+| flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | flask_bad.py:19:18:19:24 | ControlFlowNode for request | flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | $@ HTTP header is constructed from a $@. | flask_bad.py:21:38:21:47 | ControlFlowNode for rfs_header | This | flask_bad.py:19:18:19:24 | ControlFlowNode for request | user-provided value |
+| flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | flask_bad.py:27:18:27:24 | ControlFlowNode for request | flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | $@ HTTP header is constructed from a $@. | flask_bad.py:29:34:29:43 | ControlFlowNode for rfs_header | This | flask_bad.py:27:18:27:24 | ControlFlowNode for request | user-provided value |
+| flask_bad.py:38:9:38:34 | ControlFlowNode for Dict | flask_bad.py:35:18:35:24 | ControlFlowNode for request | flask_bad.py:38:9:38:34 | ControlFlowNode for Dict | $@ HTTP header is constructed from a $@. | flask_bad.py:38:9:38:34 | ControlFlowNode for Dict | This | flask_bad.py:35:18:35:24 | ControlFlowNode for request | user-provided value |
+| flask_bad.py:44:29:44:70 | ControlFlowNode for Dict | flask_bad.py:44:44:44:50 | ControlFlowNode for request | flask_bad.py:44:29:44:70 | ControlFlowNode for Dict | $@ HTTP header is constructed from a $@. | flask_bad.py:44:29:44:70 | ControlFlowNode for Dict | This | flask_bad.py:44:44:44:50 | ControlFlowNode for request | user-provided value |
From 066504e79efaf953ab000bae3bec966193bba57f Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 18 Jun 2021 02:02:47 +0200
Subject: [PATCH 012/361] Checkout Stdlib.qll
---
.../semmle/python/frameworks/Stdlib.qll | 130 ------------------
1 file changed, 130 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll b/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
index 636a0621bd4..420caf0d73b 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
@@ -9,133 +9,3 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
-
-private module Headers {
- private module Werkzeug {
- class WerkzeugHeaderCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
- WerkzeugHeaderCall() {
- exists(DataFlow::AttrRead addMethod |
- this.getFunction() = addMethod and
- addMethod.getObject().getALocalSource() =
- API::moduleImport("werkzeug")
- .getMember("datastructures")
- .getMember("Headers")
- .getACall() and
- addMethod.getAttributeName() = "add"
- )
- }
-
- override DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
- }
- }
-
- private module Flask {
- class FlaskHeaderCall extends DataFlow::Node, HeaderDeclaration::Range {
- DataFlow::Node headerInputNode;
-
- FlaskHeaderCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
- AssignStmt sinkDeclaration
- |
- headerInstance = API::moduleImport("flask").getMember("Response").getACall() and
- responseMethod.getAttributeName() = "headers" and
- responseMethod.getObject().getALocalSource() = headerInstance and
- sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
- headerInputNode.asExpr() = sinkDeclaration.getValue() and
- this.asExpr() = sinkDeclaration.getATarget()
- )
- }
-
- override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
- }
-
- class FlaskMakeResponseCall extends DataFlow::Node, HeaderDeclaration::Range {
- DataFlow::Node headerInputNode;
-
- FlaskMakeResponseCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
- AssignStmt sinkDeclaration
- |
- headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
- responseMethod.getAttributeName() = "headers" and
- responseMethod.getObject().getALocalSource() = headerInstance and
- sinkDeclaration.getATarget() = responseMethod.asExpr().getParentNode() and
- this.asExpr() = sinkDeclaration.getATarget() and
- headerInputNode.asExpr() = sinkDeclaration.getValue()
- )
- }
-
- override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
- }
-
- class FlaskMakeResponseExtendCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
- DataFlow::Node headerInputNode;
-
- FlaskMakeResponseExtendCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, DataFlow::AttrRead responseMethod,
- DataFlow::AttrRead extendMethod
- |
- headerInstance = API::moduleImport("flask").getMember("make_response").getACall() and
- responseMethod.getAttributeName() = "headers" and
- responseMethod.getObject().getALocalSource() = headerInstance and
- extendMethod.getAttributeName() = "extend" and
- extendMethod.getObject().getALocalSource() = responseMethod and
- this.getFunction() = extendMethod and
- headerInputNode = this.getArg(0)
- )
- }
-
- override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
- }
-
- class FlaskResponseArg extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
- DataFlow::Node headerInputNode;
-
- FlaskResponseArg() {
- this = API::moduleImport("flask").getMember("Response").getACall() and
- headerInputNode = this.getArgByName("headers")
- }
-
- override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
- }
-
- class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
- DjangoResponseSetItemCall() {
- exists(DataFlow::AttrRead setItemMethod |
- this.getFunction() = setItemMethod and
- setItemMethod.getObject().getALocalSource() =
- API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
- setItemMethod.getAttributeName() = "__setitem__"
- )
- }
-
- override DataFlow::Node getHeaderInputNode() { result = this.getArg(1) }
- }
- }
-
- private module Django {
- class DjangoResponseAssignCall extends DataFlow::Node, HeaderDeclaration::Range {
- DataFlow::Node headerInputNode;
-
- DjangoResponseAssignCall() {
- exists(
- DataFlow::CallCfgNode headerInstance, Subscript responseMethod,
- DataFlow::Node responseToNode, AssignStmt sinkDeclaration
- |
- headerInstance =
- API::moduleImport("django").getMember("http").getMember("HttpResponse").getACall() and
- responseMethod.getValue() = responseToNode.asExpr() and
- responseToNode.getALocalSource().asExpr() = headerInstance.asExpr() and
- sinkDeclaration.getATarget() = responseMethod and
- this.asExpr() = sinkDeclaration.getATarget() and
- headerInputNode.asExpr() = sinkDeclaration.getValue()
- )
- }
-
- override DataFlow::Node getHeaderInputNode() { result = headerInputNode }
- }
- }
-}
From 4963caf5067c0467e7a88f1b361dfda09c4b9822 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 18 Jun 2021 02:03:27 +0200
Subject: [PATCH 013/361] Rewrite frameworks modeling
---
.../experimental/semmle/python/Frameworks.qll | 3 +
.../semmle/python/frameworks/Django.qll | 78 +++++++++++++++++++
.../semmle/python/frameworks/Flask.qll | 74 ++++++++++++++++++
.../semmle/python/frameworks/Werkzeug.qll | 31 ++++++++
4 files changed, 186 insertions(+)
create mode 100644 python/ql/src/experimental/semmle/python/frameworks/Django.qll
create mode 100644 python/ql/src/experimental/semmle/python/frameworks/Flask.qll
create mode 100644 python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
diff --git a/python/ql/src/experimental/semmle/python/Frameworks.qll b/python/ql/src/experimental/semmle/python/Frameworks.qll
index ca1dd04e57d..da57f56e8cc 100644
--- a/python/ql/src/experimental/semmle/python/Frameworks.qll
+++ b/python/ql/src/experimental/semmle/python/Frameworks.qll
@@ -3,3 +3,6 @@
*/
private import experimental.semmle.python.frameworks.Stdlib
+private import experimental.semmle.python.frameworks.Flask
+private import experimental.semmle.python.frameworks.Django
+private import experimental.semmle.python.frameworks.Werkzeug
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Django.qll b/python/ql/src/experimental/semmle/python/frameworks/Django.qll
new file mode 100644
index 00000000000..3353881fff8
--- /dev/null
+++ b/python/ql/src/experimental/semmle/python/frameworks/Django.qll
@@ -0,0 +1,78 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `django` PyPI package.
+ * See https://www.djangoproject.com/.
+ */
+
+private import python
+private import semmle.python.frameworks.Django
+private import semmle.python.dataflow.new.DataFlow
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+private module PrivateDjango {
+ API::Node django() { result = API::moduleImport("django") }
+
+ private module django {
+ API::Node http() { result = django().getMember("http") }
+
+ module http {
+ API::Node response() { result = http().getMember("response") }
+
+ module response {
+ module HttpResponse {
+ API::Node baseClassRef() {
+ result = response().getMember("HttpResponse").getReturn()
+ or
+ // Handle `django.http.HttpResponse` alias
+ result = http().getMember("HttpResponse").getReturn()
+ }
+
+ /** Gets a reference to a header instance. */
+ private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
+ t.start() and
+ (
+ exists(SubscriptNode subscript |
+ subscript.getObject() = baseClassRef().getAUse().asCfgNode() and
+ result.asCfgNode() = subscript
+ )
+ or
+ result.(DataFlow::AttrRead).getObject() = baseClassRef().getAUse()
+ )
+ or
+ exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to a header instance use. */
+ private DataFlow::Node headerInstance() {
+ headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
+
+ /** Gets a reference to a header instance call with `__setitem__`. */
+ private DataFlow::Node headerSetItemCall() {
+ result = headerInstance() and
+ result.(DataFlow::AttrRead).getAttributeName() = "__setitem__"
+ }
+
+ class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ DjangoResponseSetItemCall() { this.getFunction() = headerSetItemCall() }
+
+ override DataFlow::Node getHeaderInput() { result = this.getArg([0, 1]) }
+ }
+
+ class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInput;
+
+ DjangoResponseDefinition() {
+ this.asCfgNode().(DefinitionNode) = headerInstance().asCfgNode() and
+ headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
+ }
+
+ override DataFlow::Node getHeaderInput() {
+ result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
new file mode 100644
index 00000000000..4ed3ed58e3c
--- /dev/null
+++ b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
@@ -0,0 +1,74 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `flask` PyPI package.
+ * See https://flask.palletsprojects.com/en/1.1.x/.
+ */
+
+private import python
+private import semmle.python.frameworks.Flask
+private import semmle.python.dataflow.new.DataFlow
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+module ExperimentalFlask {
+ /**
+ * A reference to either `flask.make_response` function, or the `make_response` method on
+ * an instance of `flask.Flask`. This creates an instance of the `flask_response`
+ * class (class-attribute on a flask application), which by default is
+ * `flask.Response`.
+ *
+ * See
+ * - https://flask.palletsprojects.com/en/1.1.x/api/#flask.Flask.make_response
+ * - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
+ */
+ private API::Node flaskMakeResponse() {
+ result in [
+ API::moduleImport("flask").getMember("make_response"),
+ Flask::FlaskApp::instance().getMember("make_response")
+ ]
+ }
+
+ /** Gets a reference to a header instance. */
+ private DataFlow::LocalSourceNode headerInstance(DataFlow::TypeTracker t) {
+ t.start() and
+ result.(DataFlow::AttrRead).getObject().getALocalSource() =
+ [Flask::Response::classRef(), flaskMakeResponse()].getReturn().getAUse()
+ or
+ exists(DataFlow::TypeTracker t2 | result = headerInstance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to a header instance use. */
+ private DataFlow::Node headerInstance() {
+ headerInstance(DataFlow::TypeTracker::end()).flowsTo(result)
+ }
+
+ /** Gets a reference to a header instance call/subscript */
+ private DataFlow::Node headerInstanceCall() {
+ headerInstance() in [result.(DataFlow::AttrRead), result.(DataFlow::AttrRead).getObject()] or
+ headerInstance().asExpr() = result.asExpr().(Subscript).getObject()
+ }
+
+ class FlaskHeaderDefinition extends DataFlow::Node, HeaderDeclaration::Range {
+ DataFlow::Node headerInput;
+
+ FlaskHeaderDefinition() {
+ this.asCfgNode().(DefinitionNode) = headerInstanceCall().asCfgNode() and
+ headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
+ }
+
+ override DataFlow::Node getHeaderInput() {
+ result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
+ }
+ }
+
+ private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ FlaskMakeResponseExtend() { this.getFunction() = headerInstanceCall() }
+
+ override DataFlow::Node getHeaderInput() { result = this.getArg(0) }
+ }
+
+ private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ FlaskResponse() { this = Flask::Response::classRef().getACall() }
+
+ override DataFlow::Node getHeaderInput() { result = this.getArgByName("headers") }
+ }
+}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll b/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
new file mode 100644
index 00000000000..4c7e68f165a
--- /dev/null
+++ b/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
@@ -0,0 +1,31 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `Werkzeug` PyPI package.
+ * See
+ * - https://pypi.org/project/Werkzeug/
+ * - https://werkzeug.palletsprojects.com/en/1.0.x/#werkzeug
+ */
+
+private import python
+private import semmle.python.frameworks.Flask
+private import semmle.python.dataflow.new.DataFlow
+private import experimental.semmle.python.Concepts
+private import semmle.python.ApiGraphs
+
+private module Werkzeug {
+ module datastructures {
+ module Headers {
+ class WerkzeugHeaderAddCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ WerkzeugHeaderAddCall() {
+ this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
+ API::moduleImport("werkzeug")
+ .getMember("datastructures")
+ .getMember("Headers")
+ .getACall() and
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() = "add"
+ }
+
+ override DataFlow::Node getHeaderInput() { result = this.getArg(_) }
+ }
+ }
+ }
+}
From dcb1da338b1a8d4c378b1ac88d04c5e0dce970f9 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 18 Jun 2021 02:03:56 +0200
Subject: [PATCH 014/361] Extend documentation
---
.../experimental/semmle/python/Concepts.qll | 23 +++++++++++++++++--
.../python/security/injection/HTTPHeaders.qll | 5 +++-
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index 530b8d20ec6..2036a6a671e 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -14,16 +14,35 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
+/** Provides classes for modeling HTTP Header APIs. */
module HeaderDeclaration {
+ /**
+ * A data-flow node that collects functions setting HTTP Headers' content.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HeaderDeclaration` instead.
+ */
abstract class Range extends DataFlow::Node {
- abstract DataFlow::Node getHeaderInputNode();
+ /**
+ * Gets the argument containing the header value.
+ */
+ abstract DataFlow::Node getHeaderInput();
}
}
+/**
+ * A data-flow node that collects functions setting HTTP Headers' content.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HeaderDeclaration` instead.
+ */
class HeaderDeclaration extends DataFlow::Node {
HeaderDeclaration::Range range;
HeaderDeclaration() { this = range }
- DataFlow::Node getHeaderInputNode() { result = range.getHeaderInputNode() }
+ /**
+ * Gets the argument containing the header value.
+ */
+ DataFlow::Node getHeaderInput() { result = range.getHeaderInput() }
}
diff --git a/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll b/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
index 1215710c1aa..e049caa2609 100644
--- a/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
+++ b/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
@@ -4,12 +4,15 @@ import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
import semmle.python.dataflow.new.RemoteFlowSources
+/**
+ * A taint-tracking configuration for detecting HTTP Header injections.
+ */
class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
HeaderInjectionFlowConfig() { this = "HeaderInjectionFlowConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
- sink = any(HeaderDeclaration headerDeclaration).getHeaderInputNode()
+ sink = any(HeaderDeclaration headerDeclaration).getHeaderInput()
}
}
From 017a778a20d8980700966d54f98b6041b78ad08f Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Fri, 18 Jun 2021 20:21:11 +0200
Subject: [PATCH 015/361] Polish make_response and fix extend argument
---
.../src/experimental/semmle/python/frameworks/Flask.qll | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
index 4ed3ed58e3c..7ad1aa2e358 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
@@ -21,10 +21,9 @@ module ExperimentalFlask {
* - https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response
*/
private API::Node flaskMakeResponse() {
- result in [
- API::moduleImport("flask").getMember("make_response"),
- Flask::FlaskApp::instance().getMember("make_response")
- ]
+ result =
+ [API::moduleImport("flask"), Flask::FlaskApp::instance()]
+ .getMember(["make_response", "jsonify", "make_default_options_response"])
}
/** Gets a reference to a header instance. */
@@ -63,7 +62,7 @@ module ExperimentalFlask {
private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
FlaskMakeResponseExtend() { this.getFunction() = headerInstanceCall() }
- override DataFlow::Node getHeaderInput() { result = this.getArg(0) }
+ override DataFlow::Node getHeaderInput() { result = this.getArg(_) }
}
private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
From b10ade17beeeefe3b5811f8b7a97c60b8b14555a Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Sun, 20 Jun 2021 00:13:59 +0200
Subject: [PATCH 016/361] Update HeaderDeclaration input naming
---
python/ql/src/experimental/semmle/python/Concepts.qll | 4 ++--
.../ql/src/experimental/semmle/python/frameworks/Django.qll | 4 ++--
.../ql/src/experimental/semmle/python/frameworks/Flask.qll | 6 +++---
.../src/experimental/semmle/python/frameworks/Werkzeug.qll | 2 +-
.../semmle/python/security/injection/HTTPHeaders.qll | 2 +-
5 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index 309e40b4471..a3d2104df52 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -159,7 +159,7 @@ module HeaderDeclaration {
/**
* Gets the argument containing the header value.
*/
- abstract DataFlow::Node getHeaderInput();
+ abstract DataFlow::Node getAnInput();
}
}
@@ -177,5 +177,5 @@ class HeaderDeclaration extends DataFlow::Node {
/**
* Gets the argument containing the header value.
*/
- DataFlow::Node getHeaderInput() { result = range.getHeaderInput() }
+ DataFlow::Node getAnInput() { result = range.getAnInput() }
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Django.qll b/python/ql/src/experimental/semmle/python/frameworks/Django.qll
index 3353881fff8..68153dfae00 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Django.qll
@@ -56,7 +56,7 @@ private module PrivateDjango {
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
DjangoResponseSetItemCall() { this.getFunction() = headerSetItemCall() }
- override DataFlow::Node getHeaderInput() { result = this.getArg([0, 1]) }
+ override DataFlow::Node getAnInput() { result = this.getArg([0, 1]) }
}
class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
@@ -67,7 +67,7 @@ private module PrivateDjango {
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
}
- override DataFlow::Node getHeaderInput() {
+ override DataFlow::Node getAnInput() {
result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
}
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
index 7ad1aa2e358..a62c38bb060 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
@@ -54,7 +54,7 @@ module ExperimentalFlask {
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
}
- override DataFlow::Node getHeaderInput() {
+ override DataFlow::Node getAnInput() {
result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
}
}
@@ -62,12 +62,12 @@ module ExperimentalFlask {
private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
FlaskMakeResponseExtend() { this.getFunction() = headerInstanceCall() }
- override DataFlow::Node getHeaderInput() { result = this.getArg(_) }
+ override DataFlow::Node getAnInput() { result = this.getArg(_) }
}
private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
FlaskResponse() { this = Flask::Response::classRef().getACall() }
- override DataFlow::Node getHeaderInput() { result = this.getArgByName("headers") }
+ override DataFlow::Node getAnInput() { result = this.getArgByName("headers") }
}
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll b/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
index 4c7e68f165a..d37fefe2af8 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
@@ -24,7 +24,7 @@ private module Werkzeug {
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "add"
}
- override DataFlow::Node getHeaderInput() { result = this.getArg(_) }
+ override DataFlow::Node getAnInput() { result = this.getArg(_) }
}
}
}
diff --git a/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll b/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
index e049caa2609..d31a7d5ac9d 100644
--- a/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
+++ b/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
@@ -13,6 +13,6 @@ class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
- sink = any(HeaderDeclaration headerDeclaration).getHeaderInput()
+ sink = any(HeaderDeclaration headerDeclaration).getAnInput()
}
}
From b866f1b21eb89710c69c7460f1890e1e204fa386 Mon Sep 17 00:00:00 2001
From: haby0
Date: Fri, 2 Jul 2021 19:30:33 +0800
Subject: [PATCH 017/361] Add CWE-348 ClientSuppliedIpUsedInSecurityCheck
---
.../ClientSuppliedIpUsedInSecurityCheck.py | 35 +++++
.../ClientSuppliedIpUsedInSecurityCheck.qhelp | 35 +++++
.../ClientSuppliedIpUsedInSecurityCheck.ql | 48 +++++++
...ClientSuppliedIpUsedInSecurityCheckLib.qll | 130 ++++++++++++++++++
...ientSuppliedIpUsedInSecurityCheck.expected | 11 ++
.../ClientSuppliedIpUsedInSecurityCheck.py | 35 +++++
.../ClientSuppliedIpUsedInSecurityCheck.qlref | 1 +
7 files changed, 295 insertions(+)
create mode 100644 python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
create mode 100644 python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp
create mode 100644 python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
create mode 100644 python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
new file mode 100644
index 00000000000..5ae8d3dd1ab
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :ip address spoofing
+"""
+from flask import Flask
+from flask import request
+
+app = Flask(__name__)
+
+@app.route('/bad1')
+def bad1():
+ client_ip = request.headers.get('x-forwarded-for')
+ if not client_ip.startswith('192.168.'):
+ raise Exception('ip illegal')
+ return 'bad1'
+
+@app.route('/bad2')
+def bad2():
+ client_ip = request.headers.get('x-forwarded-for')
+ if not client_ip == '127.0.0.1':
+ raise Exception('ip illegal')
+ return 'bad2'
+
+@app.route('/good1')
+def good1():
+ client_ip = request.headers.get('x-forwarded-for')
+ client_ip = client_ip.split(',')[client_ip.split(',').length - 1]
+ if not client_ip == '127.0.0.1':
+ raise Exception('ip illegal')
+ return 'good1'
+
+if __name__ == '__main__':
+ app.debug = True
+ app.run()
\ No newline at end of file
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp
new file mode 100644
index 00000000000..653c93341c9
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qhelp
@@ -0,0 +1,35 @@
+
+
+
+
An original client IP address is retrieved from an http header (X-Forwarded-For or X-Real-IP or Proxy-Client-IP
+etc.), which is used to ensure security. Attackers can forge the value of these identifiers to
+bypass a ban-list, for example.
+
+
+
+
+
Do not trust the values of HTTP headers allegedly identifying the originating IP. If you are aware your application will run behind some reverse proxies then the last entry of a X-Forwarded-For header value may be more trustworthy than the rest of it because some reverse proxies append the IP address they observed to the end of any remote-supplied header.
+
+
+
+
+
The following examples show the bad case and the good case respectively.
+In bad1 method and bad2 method, the client ip the X-Forwarded-For is split into comma-separated values, but the less-trustworthy first one is used. Both of these examples could be deceived by providing a forged HTTP header. The method
+good1 similarly splits an X-Forwarded-For value, but uses the last, more-trustworthy entry.
+
+
+
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
new file mode 100644
index 00000000000..7866fbd681c
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
@@ -0,0 +1,48 @@
+/**
+ * @name IP address spoofing
+ * @description A remote endpoint identifier is read from an HTTP header. Attackers can modify the value
+ * of the identifier to forge the client ip.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id py/ip-address-spoofing
+ * @tags security
+ * external/cwe/cwe-348
+ */
+
+import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+import ClientSuppliedIpUsedInSecurityCheckLib
+import DataFlow::PathGraph
+
+/**
+ * Taint-tracking configuration tracing flow from obtaining a client ip from an HTTP header to a sensitive use.
+ */
+class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configuration {
+ ClientSuppliedIpUsedInSecurityCheckConfig() { this = "ClientSuppliedIpUsedInSecurityCheckConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof ClientSuppliedIpUsedInSecurityCheck
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink instanceof ClientSuppliedIpUsedInSecurityCheckSink
+ }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ exists(Subscript ss |
+ not ss.getIndex().(IntegerLiteral).getText() = "0" and
+ ss.getObject().(Call).getFunc().(Attribute).getName() = "split" and
+ ss.getObject().(Call).getArg(0).(StrConst).getText() = "," and
+ ss.getObject().(Call).getFunc().(Attribute).getObject() = node.asExpr()
+ )
+ }
+}
+
+from
+ ClientSuppliedIpUsedInSecurityCheckConfig config, DataFlow::PathNode source,
+ DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "IP address spoofing might include code from $@.",
+ source.getNode(), "this user input"
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
new file mode 100644
index 00000000000..49af8b2bbf5
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
@@ -0,0 +1,130 @@
+private import python
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.RemoteFlowSources
+
+/**
+ * A data flow source of the client ip obtained according to the remote endpoint identifier specified
+ * (`X-Forwarded-For`, `X-Real-IP`, `Proxy-Client-IP`, etc.) in the header.
+ *
+ * For example: `request.headers.get("X-Forwarded-For")`.
+ */
+abstract class ClientSuppliedIpUsedInSecurityCheck extends DataFlow::CallCfgNode { }
+
+private class FlaskClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck {
+ FlaskClientSuppliedIpUsedInSecurityCheck() {
+ this =
+ API::moduleImport("flask")
+ .getMember("request")
+ .getMember("headers")
+ .getMember(["get", "get_all", "getlist"])
+ .getACall() and
+ this.getArg(0).asCfgNode().getNode().(StrConst).getText().toLowerCase() =
+ clientIpParameterName()
+ }
+}
+
+private class DjangoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck {
+ DjangoClientSuppliedIpUsedInSecurityCheck() {
+ exists(RemoteFlowSource rfs, DataFlow::LocalSourceNode lsn |
+ rfs.getSourceType() = "django.http.request.HttpRequest" and rfs.asCfgNode() = lsn.asCfgNode()
+ |
+ lsn.flowsTo(DataFlow::exprNode(this.getFunction()
+ .asExpr()
+ .(Attribute)
+ .getObject()
+ .(Attribute)
+ .getObject())) and
+ this.getFunction().asExpr().(Attribute).getName() = "get" and
+ this.getFunction().asExpr().(Attribute).getObject().(Attribute).getName() in [
+ "headers", "META"
+ ] and
+ this.getArg(0).asCfgNode().getNode().(StrConst).getText().toLowerCase() =
+ clientIpParameterName()
+ )
+ }
+}
+
+private string clientIpParameterName() {
+ result in [
+ "x-forwarded-for", "x_forwarded_for", "x-real-ip", "x_real_ip", "proxy-client-ip",
+ "proxy_client_ip", "wl-proxy-client-ip", "wl_proxy_client_ip", "http_x_forwarded_for",
+ "http-x-forwarded-for", "http_x_forwarded", "http_x_cluster_client_ip", "http_client_ip",
+ "http_forwarded_for", "http_forwarded", "http_via", "remote_addr"
+ ]
+}
+
+/** A data flow sink for ip address forgery vulnerabilities. */
+abstract class ClientSuppliedIpUsedInSecurityCheckSink extends DataFlow::Node { }
+
+/** A data flow sink for sql operation. */
+private class SqlOperationSink extends ClientSuppliedIpUsedInSecurityCheckSink {
+ SqlOperationSink() { this = any(SqlExecution e).getSql() }
+}
+
+/**
+ * A data flow sink for remote client ip comparison.
+ *
+ * For example: `if not ipAddr.startswith('192.168.') : ...` determine whether the client ip starts
+ * with `192.168.`, and the program can be deceived by forging the ip address.
+ */
+private class CompareSink extends ClientSuppliedIpUsedInSecurityCheckSink {
+ CompareSink() {
+ exists(Call call |
+ call.getFunc().(Attribute).getName() = "startswith" and
+ call.getArg(0).(StrConst).getText().regexpMatch(getIpAddressRegex()) and
+ not call.getArg(0).(StrConst).getText() = "0:0:0:0:0:0:0:1" and
+ call.getFunc().(Attribute).getObject() = this.asExpr()
+ )
+ or
+ exists(Compare compare |
+ (
+ compare.getOp(0) instanceof Eq or
+ compare.getOp(0) instanceof NotEq
+ ) and
+ (
+ compare.getLeft() = this.asExpr() and
+ compare.getComparator(0).(StrConst).getText() instanceof PrivateHostName and
+ not compare.getComparator(0).(StrConst).getText() = "0:0:0:0:0:0:0:1"
+ or
+ compare.getComparator(0) = this.asExpr() and
+ compare.getLeft().(StrConst).getText() instanceof PrivateHostName and
+ not compare.getLeft().(StrConst).getText() = "0:0:0:0:0:0:0:1"
+ )
+ )
+ or
+ exists(Compare compare |
+ (
+ compare.getOp(0) instanceof In or
+ compare.getOp(0) instanceof NotIn
+ ) and
+ (
+ compare.getLeft() = this.asExpr()
+ or
+ compare.getComparator(0) = this.asExpr()
+ )
+ )
+ or
+ exists(Call call |
+ call.getFunc().(Attribute).getName() = "add" and
+ call.getArg(0) = this.asExpr()
+ )
+ }
+}
+
+string getIpAddressRegex() {
+ result =
+ "^((10\\.((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?)|(192\\.168\\.)|172\\.(1[6789]|2[0-9]|3[01])\\.)((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)(\\.)?((1\\d{2})?|(2[0-4]\\d)?|(25[0-5])?|([1-9]\\d|[0-9])?)$"
+}
+
+/**
+ * A string matching private host names of IPv4 and IPv6, which only matches the host portion therefore checking for port is not necessary.
+ * Several examples are localhost, reserved IPv4 IP addresses including 127.0.0.1, 10.x.x.x, 172.16.x,x, 192.168.x,x, and reserved IPv6 addresses including [0:0:0:0:0:0:0:1] and [::1]
+ */
+private class PrivateHostName extends string {
+ bindingset[this]
+ PrivateHostName() {
+ this.regexpMatch("(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?")
+ }
+}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
new file mode 100644
index 00000000000..2e6442cc17f
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
@@ -0,0 +1,11 @@
+edges
+| ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip |
+| ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip |
+nodes
+| ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
+| ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
+#select
+| ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip | ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | this user input |
+| ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip | ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | this user input |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
new file mode 100644
index 00000000000..3edb73e575f
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :ip address spoofing
+"""
+from flask import Flask
+from flask import request
+
+app = Flask(__name__)
+
+@app.route('/bad1')
+def bad1():
+ client_ip = request.headers.get('x-forwarded-for')
+ if not client_ip.startswith('192.168.'):
+ raise Exception('ip illegal')
+ return 'bad1'
+
+@app.route('/bad2')
+def bad2():
+ client_ip = request.headers.get('x-forwarded-for')
+ if not client_ip == '127.0.0.1':
+ raise Exception('ip illegal')
+ return 'bad2'
+
+@app.route('/good1')
+def good1():
+ client_ip = request.headers.get('x-forwarded-for')
+ client_ip = client_ip.split(',')[client_ip.split(',').length - 1]
+ if not client_ip == '127.0.0.1':
+ raise Exception('ip illegal')
+ return 'good1'
+
+if __name__ == '__main__':
+ app.debug = True
+ app.run()
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref
new file mode 100644
index 00000000000..2a1775fe06a
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
\ No newline at end of file
From e8d08279168c4dd494acdea4baa2448fb9c25804 Mon Sep 17 00:00:00 2001
From: haby0
Date: Mon, 5 Jul 2021 10:42:15 +0800
Subject: [PATCH 018/361] Add tornado source
---
...ClientSuppliedIpUsedInSecurityCheckLib.qll | 21 +++++++++++
...ientSuppliedIpUsedInSecurityCheck.expected | 20 ++++++-----
...dIpUsedInSecurityCheck.py => flask_bad.py} | 8 -----
.../Security/CWE-348/flask_good.py | 21 +++++++++++
.../Security/CWE-348/tornado_bad.py | 36 +++++++++++++++++++
5 files changed, 90 insertions(+), 16 deletions(-)
rename python/ql/test/experimental/query-tests/Security/CWE-348/{ClientSuppliedIpUsedInSecurityCheck.py => flask_bad.py} (69%)
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-348/flask_good.py
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-348/tornado_bad.py
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
index 49af8b2bbf5..8113fd17c9d 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
@@ -46,6 +46,27 @@ private class DjangoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIp
}
}
+private class TornadoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck {
+ TornadoClientSuppliedIpUsedInSecurityCheck() {
+ exists(RemoteFlowSource rfs, DataFlow::LocalSourceNode lsn |
+ rfs.getSourceType() = "tornado.web.RequestHandler" and rfs.asCfgNode() = lsn.asCfgNode()
+ |
+ lsn.flowsTo(DataFlow::exprNode(this.getFunction()
+ .asExpr()
+ .(Attribute)
+ .getObject()
+ .(Attribute)
+ .getObject()
+ .(Attribute)
+ .getObject())) and
+ this.getFunction().asExpr().(Attribute).getName() in ["get", "get_list"] and
+ this.getFunction().asExpr().(Attribute).getObject().(Attribute).getName() = "headers" and
+ this.getArg(0).asCfgNode().getNode().(StrConst).getText().toLowerCase() =
+ clientIpParameterName()
+ )
+ }
+}
+
private string clientIpParameterName() {
result in [
"x-forwarded-for", "x_forwarded_for", "x-real-ip", "x_real_ip", "proxy-client-ip",
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
index 2e6442cc17f..38536f2b7a2 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
@@ -1,11 +1,15 @@
edges
-| ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip |
-| ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip |
+| flask_bad.py:13:17:13:54 | ControlFlowNode for Attribute() | flask_bad.py:14:12:14:20 | ControlFlowNode for client_ip |
+| flask_bad.py:20:17:20:54 | ControlFlowNode for Attribute() | flask_bad.py:21:12:21:20 | ControlFlowNode for client_ip |
+| tornado_bad.py:22:25:22:69 | ControlFlowNode for Attribute() | tornado_bad.py:23:16:23:24 | ControlFlowNode for client_ip |
nodes
-| ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
-| ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
+| flask_bad.py:13:17:13:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| flask_bad.py:14:12:14:20 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
+| flask_bad.py:20:17:20:54 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| flask_bad.py:21:12:21:20 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
+| tornado_bad.py:22:25:22:69 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| tornado_bad.py:23:16:23:24 | ControlFlowNode for client_ip | semmle.label | ControlFlowNode for client_ip |
#select
-| ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip | ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:14:12:14:20 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | ClientSuppliedIpUsedInSecurityCheck.py:13:17:13:54 | ControlFlowNode for Attribute() | this user input |
-| ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip | ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | ClientSuppliedIpUsedInSecurityCheck.py:21:12:21:20 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | ClientSuppliedIpUsedInSecurityCheck.py:20:17:20:54 | ControlFlowNode for Attribute() | this user input |
+| flask_bad.py:14:12:14:20 | ControlFlowNode for client_ip | flask_bad.py:13:17:13:54 | ControlFlowNode for Attribute() | flask_bad.py:14:12:14:20 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | flask_bad.py:13:17:13:54 | ControlFlowNode for Attribute() | this user input |
+| flask_bad.py:21:12:21:20 | ControlFlowNode for client_ip | flask_bad.py:20:17:20:54 | ControlFlowNode for Attribute() | flask_bad.py:21:12:21:20 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | flask_bad.py:20:17:20:54 | ControlFlowNode for Attribute() | this user input |
+| tornado_bad.py:23:16:23:24 | ControlFlowNode for client_ip | tornado_bad.py:22:25:22:69 | ControlFlowNode for Attribute() | tornado_bad.py:23:16:23:24 | ControlFlowNode for client_ip | IP address spoofing might include code from $@. | tornado_bad.py:22:25:22:69 | ControlFlowNode for Attribute() | this user input |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py b/python/ql/test/experimental/query-tests/Security/CWE-348/flask_bad.py
similarity index 69%
rename from python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
rename to python/ql/test/experimental/query-tests/Security/CWE-348/flask_bad.py
index 3edb73e575f..b357a9316fd 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/flask_bad.py
@@ -22,14 +22,6 @@ def bad2():
raise Exception('ip illegal')
return 'bad2'
-@app.route('/good1')
-def good1():
- client_ip = request.headers.get('x-forwarded-for')
- client_ip = client_ip.split(',')[client_ip.split(',').length - 1]
- if not client_ip == '127.0.0.1':
- raise Exception('ip illegal')
- return 'good1'
-
if __name__ == '__main__':
app.debug = True
app.run()
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/flask_good.py b/python/ql/test/experimental/query-tests/Security/CWE-348/flask_good.py
new file mode 100644
index 00000000000..da42c59724a
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/flask_good.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :ip address spoofing
+"""
+from flask import Flask
+from flask import request
+
+app = Flask(__name__)
+
+@app.route('/good1')
+def good1():
+ client_ip = request.headers.get('x-forwarded-for')
+ client_ip = client_ip.split(',')[len(client_ip.split(',')) - 1]
+ if not client_ip == '127.0.0.1':
+ raise Exception('ip illegal')
+ return 'good1'
+
+if __name__ == '__main__':
+ app.debug = True
+ app.run()
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-348/tornado_bad.py b/python/ql/test/experimental/query-tests/Security/CWE-348/tornado_bad.py
new file mode 100644
index 00000000000..23ad29d8b09
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-348/tornado_bad.py
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :ip address spoofing
+"""
+import tornado.httpserver
+import tornado.options
+import tornado.web
+import tornado.ioloop
+
+from tornado.options import define, options
+
+define("port", default=8000, help="run on the given port,default 8000", type=int)
+
+
+class IndexHandler(tornado.web.RequestHandler):
+ def get(self):
+ client_ip = self.request.headers.get('x-forwarded-for')
+ if client_ip:
+ client_ip = client_ip.split(',')[len(client_ip.split(',')) - 1]
+ else:
+ client_ip = self.request.headers.get('REMOTE_ADDR', None)
+ if not client_ip == '127.0.0.1':
+ raise Exception('ip illegal')
+ self.write("hello.")
+
+handlers = [(r"/", IndexHandler)]
+
+if __name__ == "__main__":
+ tornado.options.parse_command_line()
+ app = tornado.web.Application(
+ handlers
+ )
+ http_server = tornado.httpserver.HTTPServer(app)
+ http_server.listen(options.port)
+ tornado.ioloop.IOLoop.instance().start()
From 8d0386b049a03778251e7a8893daf1623f14b3d8 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Sun, 25 Jul 2021 04:35:22 +0200
Subject: [PATCH 019/361] Split into `getNameArg` and `getValueArg`
---
.../experimental/semmle/python/Concepts.qll | 20 ++++++++++++----
.../semmle/python/frameworks/Django.qll | 10 +++++---
.../semmle/python/frameworks/Flask.qll | 23 ++++++++++++++-----
.../semmle/python/frameworks/Werkzeug.qll | 4 +++-
.../python/security/injection/HTTPHeaders.qll | 4 +++-
5 files changed, 45 insertions(+), 16 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index a3d2104df52..d5e99205bb1 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -150,21 +150,26 @@ class LDAPEscape extends DataFlow::Node {
/** Provides classes for modeling HTTP Header APIs. */
module HeaderDeclaration {
/**
- * A data-flow node that collects functions setting HTTP Headers' content.
+ * A data-flow node that collects functions setting HTTP Headers.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HeaderDeclaration` instead.
*/
abstract class Range extends DataFlow::Node {
+ /**
+ * Gets the argument containing the header name.
+ */
+ abstract DataFlow::Node getNameArg();
+
/**
* Gets the argument containing the header value.
*/
- abstract DataFlow::Node getAnInput();
+ abstract DataFlow::Node getValueArg();
}
}
/**
- * A data-flow node that collects functions setting HTTP Headers' content.
+ * A data-flow node that collects functions setting HTTP Headers.
*
* Extend this class to model new APIs. If you want to refine existing API models,
* extend `HeaderDeclaration` instead.
@@ -175,7 +180,12 @@ class HeaderDeclaration extends DataFlow::Node {
HeaderDeclaration() { this = range }
/**
- * Gets the argument containing the header value.
+ * Gets the argument containing the header name.
*/
- DataFlow::Node getAnInput() { result = range.getAnInput() }
+ DataFlow::Node getNameArg() { result = range.getNameArg() }
+
+ /**
+ * Gets the argument containing the header name.
+ */
+ DataFlow::Node getValueArg() { result = range.getValueArg() }
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Django.qll b/python/ql/src/experimental/semmle/python/frameworks/Django.qll
index 68153dfae00..c525b73b40e 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Django.qll
@@ -56,7 +56,9 @@ private module PrivateDjango {
class DjangoResponseSetItemCall extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
DjangoResponseSetItemCall() { this.getFunction() = headerSetItemCall() }
- override DataFlow::Node getAnInput() { result = this.getArg([0, 1]) }
+ override DataFlow::Node getNameArg() { result = this.getArg(0) }
+
+ override DataFlow::Node getValueArg() { result = this.getArg(1) }
}
class DjangoResponseDefinition extends DataFlow::Node, HeaderDeclaration::Range {
@@ -67,9 +69,11 @@ private module PrivateDjango {
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
}
- override DataFlow::Node getAnInput() {
- result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
+ override DataFlow::Node getNameArg() {
+ result.asExpr() = this.asExpr().(Subscript).getIndex()
}
+
+ override DataFlow::Node getValueArg() { result = headerInput }
}
}
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
index a62c38bb060..9c66d9a4601 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Flask.qll
@@ -54,20 +54,31 @@ module ExperimentalFlask {
headerInput.asCfgNode() = this.asCfgNode().(DefinitionNode).getValue()
}
- override DataFlow::Node getAnInput() {
- result.asExpr() in [headerInput.asExpr(), this.asExpr().(Subscript).getIndex()]
- }
+ override DataFlow::Node getNameArg() { result.asExpr() = this.asExpr().(Subscript).getIndex() }
+
+ override DataFlow::Node getValueArg() { result = headerInput }
}
private class FlaskMakeResponseExtend extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
- FlaskMakeResponseExtend() { this.getFunction() = headerInstanceCall() }
+ KeyValuePair item;
- override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ FlaskMakeResponseExtend() {
+ this.getFunction() = headerInstanceCall() and
+ item = this.getArg(_).asExpr().(Dict).getAnItem()
+ }
+
+ override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
+
+ override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
}
private class FlaskResponse extends DataFlow::CallCfgNode, HeaderDeclaration::Range {
+ KeyValuePair item;
+
FlaskResponse() { this = Flask::Response::classRef().getACall() }
- override DataFlow::Node getAnInput() { result = this.getArgByName("headers") }
+ override DataFlow::Node getNameArg() { result.asExpr() = item.getKey() }
+
+ override DataFlow::Node getValueArg() { result.asExpr() = item.getValue() }
}
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll b/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
index d37fefe2af8..dce67c258fb 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Werkzeug.qll
@@ -24,7 +24,9 @@ private module Werkzeug {
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "add"
}
- override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ override DataFlow::Node getNameArg() { result = this.getArg(0) }
+
+ override DataFlow::Node getValueArg() { result = this.getArg(1) }
}
}
}
diff --git a/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll b/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
index d31a7d5ac9d..4ba70cd37a2 100644
--- a/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
+++ b/python/ql/src/experimental/semmle/python/security/injection/HTTPHeaders.qll
@@ -13,6 +13,8 @@ class HeaderInjectionFlowConfig extends TaintTracking::Configuration {
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node sink) {
- sink = any(HeaderDeclaration headerDeclaration).getAnInput()
+ exists(HeaderDeclaration headerDeclaration |
+ sink in [headerDeclaration.getNameArg(), headerDeclaration.getValueArg()]
+ )
}
}
From 246302453f2ada56acb16dc88baf64d446c9706e Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 6 Sep 2021 13:46:18 +0100
Subject: [PATCH 020/361] C++: Add CleartextTransmission query.
---
.../CWE/CWE-311/CleartextStorage.inc.qhelp | 2 +-
.../CWE/CWE-311/CleartextTransmission.qhelp | 5 +++
.../CWE/CWE-311/CleartextTransmission.ql | 33 +++++++++++++++++++
3 files changed, 39 insertions(+), 1 deletion(-)
create mode 100644 cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.qhelp
create mode 100644 cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextStorage.inc.qhelp b/cpp/ql/src/Security/CWE/CWE-311/CleartextStorage.inc.qhelp
index eb9a6f8adce..47540e6cf3a 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextStorage.inc.qhelp
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextStorage.inc.qhelp
@@ -9,7 +9,7 @@ storage.
-
Ensure that sensitive information is always encrypted before being stored, especially before writing to a file.
+
Ensure that sensitive information is always encrypted before being stored or transmitted, especially before writing to a file.
It may be wise to encrypt information before it is put into a buffer that may be readable in memory.
In general, decrypt sensitive information only at the point where it is necessary for it to be used in
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.qhelp b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.qhelp
new file mode 100644
index 00000000000..d715abca84c
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.qhelp
@@ -0,0 +1,5 @@
+
+
+
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
new file mode 100644
index 00000000000..25a173e4362
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -0,0 +1,33 @@
+/**
+ * @name Cleartext transmission of sensitive information
+ * @description Transmitting sensitive information across a network in
+ * cleartext can expose it to an attacker.
+ * @kind problem
+ * @problem.severity warning
+ * @security-severity 7.5 TODO
+ * @precision high
+ * @id cpp/cleartext-transmission
+ * @tags security
+ * external/cwe/cwe-319
+ */
+
+import cpp
+import semmle.code.cpp.security.SensitiveExprs
+import semmle.code.cpp.security.FileWrite
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+
+// TODO: network send?
+
+/**
+ * TODO
+ */
+class NetworkRecv extends FunctionCall {
+ NetworkRecv() { this.getTarget().hasGlobalName("recv") }
+
+ Expr getData() { result = this.getArgument(1) }
+}
+
+from NetworkRecv recv, SensitiveExpr e
+where DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(recv.(NetworkRecv).getData()))
+select recv, e
From cd5a5347fcdaee8455158dd5f7bb909a8d425626 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 6 Sep 2021 13:42:59 +0100
Subject: [PATCH 021/361] C++: Add basic test.
---
.../tests/CleartextTransmission.expected | 2 +
.../semmle/tests/CleartextTransmission.qlref | 1 +
.../CWE/CWE-311/semmle/tests/test3.cpp | 61 +++++++++++++++++++
3 files changed, 64 insertions(+)
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
new file mode 100644
index 00000000000..70c9b6bfbfd
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -0,0 +1,2 @@
+| test3.cpp:39:3:39:6 | call to recv | test3.cpp:39:15:39:22 | password |
+| test3.cpp:47:3:47:6 | call to recv | test3.cpp:47:15:47:22 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.qlref
new file mode 100644
index 00000000000..bb3fc66f1f1
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-311/CleartextTransmission.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
new file mode 100644
index 00000000000..5eafd2dcb70
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -0,0 +1,61 @@
+
+typedef unsigned long size_t;
+
+size_t strlen(const char *s);
+
+void send(int a, const void *buf, size_t bufLen, int d);
+void recv(int a, void *buf, size_t bufLen, int d);
+
+void LogonUserA(int a, int b, const char *password, int d, int e, int f);
+
+int val();
+
+void test_send(const char *password1, const char *password2, const char *password_hash, const char *message)
+{
+ {
+ LogonUserA(val(), val(), password1, val(), val(), val()); // proof `password` is plaintext
+
+ send(val(), password1, strlen(password1), val()); // BAD: `password` is sent plaintext (certainly) [NOT DETECTED]
+ }
+
+ {
+ send(val(), password2, strlen(password2), val()); // BAD: `password` is sent plaintext (probably) [NOT DETECTED]
+ }
+
+ {
+ send(val(), password_hash, strlen(password_hash), val()); // GOOD: `password` is sent encrypted
+ }
+
+ {
+ send(val(), message, strlen(message), val()); // GOOD: `message` is not a password
+ }
+}
+
+void test_receive()
+{
+ {
+ char password[256];
+
+ recv(val(), password, 256, val()); // BAD: `password` is received plaintext (certainly)
+
+ LogonUserA(val(), val(), password, val(), val(), val()); // (proof `password` is plaintext)
+ }
+
+ {
+ char password[256];
+
+ recv(val(), password, 256, val()); // BAD: `password` is received plaintext (probably)
+ }
+
+ {
+ char password_hash[256];
+
+ recv(val(), password_hash, 256, val()); // GOOD: `password` is received encrypted
+ }
+
+ {
+ char message[256];
+
+ recv(val(), message, 256, val()); // GOOD: `message` is not a password
+ }
+}
From 190bc2f0da608d8d75efcc13fff76ef9a1da149a Mon Sep 17 00:00:00 2001
From: Jorge <46056498+jorgectf@users.noreply.github.com>
Date: Tue, 7 Sep 2021 19:42:37 +0200
Subject: [PATCH 022/361] Apply suggestions from code review
Co-authored-by: Taus
---
python/ql/src/experimental/semmle/python/Concepts.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index d5e99205bb1..1b42420b4c1 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -185,7 +185,7 @@ class HeaderDeclaration extends DataFlow::Node {
DataFlow::Node getNameArg() { result = range.getNameArg() }
/**
- * Gets the argument containing the header name.
+ * Gets the argument containing the header value.
*/
DataFlow::Node getValueArg() { result = range.getValueArg() }
}
From 352eab0eca16c46532102e6889e45e4d3222326a Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Tue, 7 Sep 2021 19:44:25 +0200
Subject: [PATCH 023/361] Fix `HeaderDeclaration` class' comment
---
python/ql/src/experimental/semmle/python/Concepts.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index 1b42420b4c1..4cffcff949c 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -171,8 +171,8 @@ module HeaderDeclaration {
/**
* A data-flow node that collects functions setting HTTP Headers.
*
- * Extend this class to model new APIs. If you want to refine existing API models,
- * extend `HeaderDeclaration` instead.
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `HeaderDeclaration::Range` instead.
*/
class HeaderDeclaration extends DataFlow::Node {
HeaderDeclaration::Range range;
From 9b198c6d0ae8fa1a3d9a7e03499c24585bf5e0c8 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 18 Aug 2021 16:52:04 +0200
Subject: [PATCH 024/361] Python: Add some module initialization tests
---
.../dataflow/module-initialization/base.py | 1 +
.../dataflow/module-initialization/m1.py | 10 ++++++
.../dataflow/module-initialization/test.py | 33 +++++++++++++++++++
.../module-initialization/testOnce.py | 33 +++++++++++++++++++
.../test/experimental/dataflow/validTest.py | 3 ++
5 files changed, 80 insertions(+)
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/base.py
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/m1.py
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/test.py
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/testOnce.py
diff --git a/python/ql/test/experimental/dataflow/module-initialization/base.py b/python/ql/test/experimental/dataflow/module-initialization/base.py
new file mode 100644
index 00000000000..611e0a87735
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/base.py
@@ -0,0 +1 @@
+foo = 3
diff --git a/python/ql/test/experimental/dataflow/module-initialization/m1.py b/python/ql/test/experimental/dataflow/module-initialization/m1.py
new file mode 100644
index 00000000000..653dfb91997
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/m1.py
@@ -0,0 +1,10 @@
+# constant
+foo = 42
+
+import base
+
+def passOn(x):
+ return x
+
+# depends on other constant
+bar = passOn(base.foo)
diff --git a/python/ql/test/experimental/dataflow/module-initialization/test.py b/python/ql/test/experimental/dataflow/module-initialization/test.py
new file mode 100644
index 00000000000..83531bdd92f
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/test.py
@@ -0,0 +1,33 @@
+# These are defined so that we can evaluate the test code.
+NONSOURCE = "not a source"
+SOURCE = "source"
+
+
+def is_source(x):
+ return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
+
+
+def SINK(x):
+ if is_source(x):
+ print("OK")
+ else:
+ print("Unexpected flow", x)
+
+
+def SINK_F(x):
+ if is_source(x):
+ print("Unexpected flow", x)
+ else:
+ print("OK")
+
+import base
+
+base.foo = 42
+
+import m1
+
+def test_const():
+ SINK(m1.foo)
+
+def test_overwritten():
+ SINK(m1.bar)
diff --git a/python/ql/test/experimental/dataflow/module-initialization/testOnce.py b/python/ql/test/experimental/dataflow/module-initialization/testOnce.py
new file mode 100644
index 00000000000..663f8acb997
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/testOnce.py
@@ -0,0 +1,33 @@
+# These are defined so that we can evaluate the test code.
+NONSOURCE = "not a source"
+SOURCE = "source"
+
+
+def is_source(x):
+ return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
+
+
+def SINK(x):
+ if is_source(x):
+ print("OK")
+ else:
+ print("Unexpected flow", x)
+
+
+def SINK_F(x):
+ if is_source(x):
+ print("Unexpected flow", x)
+ else:
+ print("OK")
+
+import m1
+
+import base
+
+base.foo = 42
+
+def test_const():
+ SINK(m1.foo)
+
+def test_unoverwritten():
+ SINK_F(m1.bar)
diff --git a/python/ql/test/experimental/dataflow/validTest.py b/python/ql/test/experimental/dataflow/validTest.py
index 2367344580b..080bb6bf24f 100644
--- a/python/ql/test/experimental/dataflow/validTest.py
+++ b/python/ql/test/experimental/dataflow/validTest.py
@@ -56,3 +56,6 @@ if __name__ == "__main__":
check_tests_valid("variable-capture.in")
check_tests_valid("variable-capture.nonlocal")
check_tests_valid("variable-capture.dict")
+ # The below fails when trying to import modules
+ # check_tests_valid("module-initialization.test")
+ # check_tests_valid("module-initialization.testOnce")
From a9c409403c91efb1671e91cd3774267fdbadb289 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 8 Sep 2021 14:44:36 +0200
Subject: [PATCH 025/361] Python: more tests and comments
---
.../dataflow/new/internal/DataFlowPrivate.qll | 3 ++
.../module-initialization/localFlow.expected | 45 +++++++++++++++++++
.../module-initialization/localFlow.ql | 20 +++++++++
.../module-initialization/multiphase.py | 43 ++++++++++++++++++
.../test/experimental/dataflow/validTest.py | 1 +
5 files changed, 112 insertions(+)
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
create mode 100644 python/ql/test/experimental/dataflow/module-initialization/multiphase.py
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index 074289b00fd..9a6d4e572c0 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -245,6 +245,9 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
/**
* Holds if there is an Essa flow step from `nodeFrom` to `nodeTo` that does not switch between
* local and global SSA variables.
+ *
+ * This predicate is currently empty, since `EssaFlow::essaFlowStep` never goes between `EssaNode`s.
+ * (It only starts in an `EssaNode` in a single case, namely `defToFirstUse` which ends in a `CfgNode`.)
*/
private predicate localEssaStep(EssaNode nodeFrom, EssaNode nodeTo) {
EssaFlow::essaFlowStep(nodeFrom, nodeTo) and
diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected b/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
new file mode 100644
index 00000000000..686c3d33017
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
@@ -0,0 +1,45 @@
+| base.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | base.py:1:1:1:3 | GSSA Variable foo |
+| m1.py:2:7:2:8 | ControlFlowNode for IntegerLiteral | m1.py:2:1:2:3 | GSSA Variable foo |
+| m1.py:4:8:4:11 | ControlFlowNode for ImportExpr | m1.py:4:8:4:11 | GSSA Variable base |
+| m1.py:4:8:4:11 | GSSA Variable base | m1.py:10:14:10:17 | ControlFlowNode for base |
+| m1.py:6:1:6:14 | ControlFlowNode for FunctionExpr | m1.py:6:5:6:10 | GSSA Variable passOn |
+| m1.py:6:5:6:10 | GSSA Variable passOn | m1.py:10:7:10:12 | ControlFlowNode for passOn |
+| m1.py:10:7:10:22 | ControlFlowNode for passOn() | m1.py:10:1:10:3 | GSSA Variable bar |
+| multiphase.py:0:0:0:0 | GSSA Variable __file__ | multiphase.py:4:50:4:57 | ControlFlowNode for __file__ |
+| multiphase.py:0:0:0:0 | GSSA Variable expects | multiphase.py:37:2:37:8 | ControlFlowNode for expects |
+| multiphase.py:1:8:1:10 | ControlFlowNode for ImportExpr | multiphase.py:1:8:1:10 | GSSA Variable sys |
+| multiphase.py:1:8:1:10 | GSSA Variable sys | multiphase.py:4:1:4:3 | ControlFlowNode for sys |
+| multiphase.py:2:8:2:9 | ControlFlowNode for ImportExpr | multiphase.py:2:8:2:9 | GSSA Variable os |
+| multiphase.py:2:8:2:9 | GSSA Variable os | multiphase.py:4:17:4:18 | ControlFlowNode for os |
+| multiphase.py:4:17:4:18 | ControlFlowNode for os | multiphase.py:4:33:4:34 | ControlFlowNode for os |
+| multiphase.py:4:17:4:18 | [post read] ControlFlowNode for os | multiphase.py:4:33:4:34 | ControlFlowNode for os |
+| multiphase.py:8:1:8:9 | GSSA Variable NONSOURCE | multiphase.py:34:7:34:15 | ControlFlowNode for NONSOURCE |
+| multiphase.py:8:13:8:26 | ControlFlowNode for Str | multiphase.py:8:1:8:9 | GSSA Variable NONSOURCE |
+| multiphase.py:9:10:9:17 | ControlFlowNode for Str | multiphase.py:9:1:9:6 | GSSA Variable SOURCE |
+| multiphase.py:12:1:12:17 | ControlFlowNode for FunctionExpr | multiphase.py:12:5:12:13 | GSSA Variable is_source |
+| multiphase.py:16:1:16:12 | ControlFlowNode for FunctionExpr | multiphase.py:16:5:16:8 | GSSA Variable SINK |
+| multiphase.py:23:1:23:14 | ControlFlowNode for FunctionExpr | multiphase.py:23:5:23:10 | GSSA Variable SINK_F |
+| multiphase.py:29:1:29:14 | ControlFlowNode for FunctionExpr | multiphase.py:29:5:29:11 | GSSA Variable set_foo |
+| multiphase.py:29:5:29:11 | GSSA Variable set_foo | multiphase.py:35:1:35:7 | ControlFlowNode for set_foo |
+| multiphase.py:34:7:34:15 | ControlFlowNode for NONSOURCE | multiphase.py:34:1:34:3 | GSSA Variable foo |
+| multiphase.py:37:2:37:11 | ControlFlowNode for expects()() | multiphase.py:38:5:38:15 | GSSA Variable test_phases |
+| test.py:2:13:2:26 | ControlFlowNode for Str | test.py:2:1:2:9 | GSSA Variable NONSOURCE |
+| test.py:3:10:3:17 | ControlFlowNode for Str | test.py:3:1:3:6 | GSSA Variable SOURCE |
+| test.py:6:1:6:17 | ControlFlowNode for FunctionExpr | test.py:6:5:6:13 | GSSA Variable is_source |
+| test.py:10:1:10:12 | ControlFlowNode for FunctionExpr | test.py:10:5:10:8 | GSSA Variable SINK |
+| test.py:17:1:17:14 | ControlFlowNode for FunctionExpr | test.py:17:5:17:10 | GSSA Variable SINK_F |
+| test.py:23:8:23:11 | ControlFlowNode for ImportExpr | test.py:23:8:23:11 | GSSA Variable base |
+| test.py:23:8:23:11 | GSSA Variable base | test.py:25:1:25:4 | ControlFlowNode for base |
+| test.py:27:8:27:9 | ControlFlowNode for ImportExpr | test.py:27:8:27:9 | GSSA Variable m1 |
+| test.py:29:1:29:17 | ControlFlowNode for FunctionExpr | test.py:29:5:29:14 | GSSA Variable test_const |
+| test.py:32:1:32:23 | ControlFlowNode for FunctionExpr | test.py:32:5:32:20 | GSSA Variable test_overwritten |
+| testOnce.py:2:13:2:26 | ControlFlowNode for Str | testOnce.py:2:1:2:9 | GSSA Variable NONSOURCE |
+| testOnce.py:3:10:3:17 | ControlFlowNode for Str | testOnce.py:3:1:3:6 | GSSA Variable SOURCE |
+| testOnce.py:6:1:6:17 | ControlFlowNode for FunctionExpr | testOnce.py:6:5:6:13 | GSSA Variable is_source |
+| testOnce.py:10:1:10:12 | ControlFlowNode for FunctionExpr | testOnce.py:10:5:10:8 | GSSA Variable SINK |
+| testOnce.py:17:1:17:14 | ControlFlowNode for FunctionExpr | testOnce.py:17:5:17:10 | GSSA Variable SINK_F |
+| testOnce.py:23:8:23:9 | ControlFlowNode for ImportExpr | testOnce.py:23:8:23:9 | GSSA Variable m1 |
+| testOnce.py:25:8:25:11 | ControlFlowNode for ImportExpr | testOnce.py:25:8:25:11 | GSSA Variable base |
+| testOnce.py:25:8:25:11 | GSSA Variable base | testOnce.py:27:1:27:4 | ControlFlowNode for base |
+| testOnce.py:29:1:29:17 | ControlFlowNode for FunctionExpr | testOnce.py:29:5:29:14 | GSSA Variable test_const |
+| testOnce.py:32:1:32:25 | ControlFlowNode for FunctionExpr | testOnce.py:32:5:32:22 | GSSA Variable test_unoverwritten |
diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
new file mode 100644
index 00000000000..281881c0de8
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
@@ -0,0 +1,20 @@
+// This query should be more focused yet.
+import python
+import semmle.python.dataflow.new.DataFlow
+
+pragma[inline]
+predicate inCodebase(DataFlow::Node node) { exists(node.getLocation().getFile().getRelativePath()) }
+
+predicate isTopLevel(DataFlow::Node node) { node.getScope() instanceof Module }
+
+predicate inFocus(DataFlow::Node node) {
+ isTopLevel(node) and
+ inCodebase(node)
+}
+
+from DataFlow::Node nodeFrom, DataFlow::Node nodeTo
+where
+ inFocus(nodeFrom) and
+ inFocus(nodeTo) and
+ DataFlow::localFlowStep(nodeFrom, nodeTo)
+select nodeFrom, nodeTo
diff --git a/python/ql/test/experimental/dataflow/module-initialization/multiphase.py b/python/ql/test/experimental/dataflow/module-initialization/multiphase.py
new file mode 100644
index 00000000000..ad41e7e8bfa
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/module-initialization/multiphase.py
@@ -0,0 +1,43 @@
+import sys
+import os
+
+sys.path.append(os.path.dirname(os.path.dirname((__file__))))
+from testlib import *
+
+# These are defined so that we can evaluate the test code.
+NONSOURCE = "not a source"
+SOURCE = "source"
+
+
+def is_source(x):
+ return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
+
+
+def SINK(x):
+ if is_source(x):
+ print("OK")
+ else:
+ print("Unexpected flow", x)
+
+
+def SINK_F(x):
+ if is_source(x):
+ print("Unexpected flow", x)
+ else:
+ print("OK")
+
+def set_foo():
+ global foo
+ print(foo)
+ foo = SOURCE
+
+foo = NONSOURCE
+set_foo()
+
+@expects(2)
+def test_phases():
+ global foo
+ SINK(foo)
+ foo = NONSOURCE
+ set_foo()
+ SINK(foo)
diff --git a/python/ql/test/experimental/dataflow/validTest.py b/python/ql/test/experimental/dataflow/validTest.py
index 080bb6bf24f..aaa0e792f2c 100644
--- a/python/ql/test/experimental/dataflow/validTest.py
+++ b/python/ql/test/experimental/dataflow/validTest.py
@@ -56,6 +56,7 @@ if __name__ == "__main__":
check_tests_valid("variable-capture.in")
check_tests_valid("variable-capture.nonlocal")
check_tests_valid("variable-capture.dict")
+ check_tests_valid("module-initialization.multiphase")
# The below fails when trying to import modules
# check_tests_valid("module-initialization.test")
# check_tests_valid("module-initialization.testOnce")
From e27b3162e5890238014ac8a1cbf6630d4bd2f776 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 9 Sep 2021 12:43:08 +0200
Subject: [PATCH 026/361] Python: rewrite `simpleLocalFlowStep` to take into
account the split between import time and runtime.
---
.../dataflow/new/internal/DataFlowPrivate.qll | 66 ++++++++++++-------
.../dataflow/fieldflow/allLocalFlow.expected | 9 +++
.../dataflow/fieldflow/globalStep.expected | 18 +++++
.../module-initialization/localFlow.expected | 13 ++++
.../module-initialization/localFlow.ql | 1 +
5 files changed, 84 insertions(+), 23 deletions(-)
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index 9a6d4e572c0..e047ee048a9 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -152,6 +152,7 @@ class DataFlowExpr = Expr;
* Flow comes from definitions, uses and refinements.
*/
// TODO: Consider constraining `nodeFrom` and `nodeTo` to be in the same scope.
+// If they have different enclosing callables, we get consistency errors.
module EssaFlow {
predicate essaFlowStep(Node nodeFrom, Node nodeTo) {
// Definition
@@ -225,41 +226,64 @@ module EssaFlow {
//--------
/**
* This is the local flow predicate that is used as a building block in global
- * data flow. It is a strict subset of the `localFlowStep` predicate, as it
- * excludes SSA flow through instance fields.
+ * data flow.
+ *
+ * Local flow can happen either at import time, when the module is initialised
+ * or at runtime when callables in the module are called.
*/
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
- // If there is ESSA-flow out of a node `node`, we want flow
+ // If there is local flow out of a node `node`, we want flow
// both out of `node` and any post-update node of `node`.
exists(Node node |
- EssaFlow::essaFlowStep(node, nodeTo) and
nodeFrom = update(node) and
(
- not node instanceof EssaNode or
- not nodeTo instanceof EssaNode or
- localEssaStep(node, nodeTo)
+ importTimeLocalFlowStep(node, nodeTo) or
+ runtimeLocalFlowStep(node, nodeTo)
)
)
}
/**
- * Holds if there is an Essa flow step from `nodeFrom` to `nodeTo` that does not switch between
- * local and global SSA variables.
- *
- * This predicate is currently empty, since `EssaFlow::essaFlowStep` never goes between `EssaNode`s.
- * (It only starts in an `EssaNode` in a single case, namely `defToFirstUse` which ends in a `CfgNode`.)
+ * Holds if `node` is found at the top level of a module.
*/
-private predicate localEssaStep(EssaNode nodeFrom, EssaNode nodeTo) {
- EssaFlow::essaFlowStep(nodeFrom, nodeTo) and
+pragma[inline]
+predicate isTopLevel(Node node) { node.getScope() instanceof Module }
+
+/** Holds if there is local flow from `nodeFrom` to `nodeTo` at import time. */
+predicate importTimeLocalFlowStep(Node nodeFrom, Node nodeTo) {
+ // As a proxy for whether statements can be executed at import time,
+ // we check if they appear at the top level.
+ // This will miss statements inside functions called from the top level.
+ isTopLevel(nodeFrom) and
+ isTopLevel(nodeTo) and
(
- nodeFrom.getVar() instanceof GlobalSsaVariable and
- nodeTo.getVar() instanceof GlobalSsaVariable
+ EssaFlow::essaFlowStep(nodeFrom, nodeTo)
or
- not nodeFrom.getVar() instanceof GlobalSsaVariable and
- not nodeTo.getVar() instanceof GlobalSsaVariable
+ exists(SsaVariable def |
+ def = any(SsaVariable var).getAnUltimateDefinition() and
+ def.getDefinition() = nodeFrom.asCfgNode() and
+ def.getVariable() = nodeTo.(ModuleVariableNode).getVariable()
+ )
)
}
+/** Holds if there is local flow from `nodeFrom` to `nodeTo` at runtime. */
+predicate runtimeLocalFlowStep(Node nodeFrom, Node nodeTo) {
+ // Anything not at the top level can be executed at runtime.
+ not isTopLevel(nodeFrom) and
+ not isTopLevel(nodeTo) and
+ EssaFlow::essaFlowStep(nodeFrom, nodeTo)
+}
+
+/** `ModuleVariable`s are accessed via jump steps at runtime. */
+predicate runtimeJumpStep(Node nodeFrom, Node nodeTo) {
+ // Module variable read
+ nodeFrom.(ModuleVariableNode).getARead() = nodeTo
+ or
+ // Module variable write
+ nodeFrom = nodeTo.(ModuleVariableNode).getAWrite()
+}
+
/**
* Holds if `result` is either `node`, or the post-update node for `node`.
*/
@@ -860,11 +884,7 @@ string ppReprType(DataFlowType t) { none() }
* taken into account.
*/
predicate jumpStep(Node nodeFrom, Node nodeTo) {
- // Module variable read
- nodeFrom.(ModuleVariableNode).getARead() = nodeTo
- or
- // Module variable write
- nodeFrom = nodeTo.(ModuleVariableNode).getAWrite()
+ runtimeJumpStep(nodeFrom, nodeTo)
or
// Read of module attribute:
exists(AttrRead r, ModuleValue mv |
diff --git a/python/ql/test/experimental/dataflow/fieldflow/allLocalFlow.expected b/python/ql/test/experimental/dataflow/fieldflow/allLocalFlow.expected
index e288b7fd157..8f6d203f6af 100644
--- a/python/ql/test/experimental/dataflow/fieldflow/allLocalFlow.expected
+++ b/python/ql/test/experimental/dataflow/fieldflow/allLocalFlow.expected
@@ -2,6 +2,7 @@
| examples.py:0:0:0:0 | GSSA Variable SOURCE | examples.py:27:15:27:20 | ControlFlowNode for SOURCE |
| examples.py:0:0:0:0 | GSSA Variable object | examples.py:6:13:6:18 | ControlFlowNode for object |
| examples.py:6:1:6:20 | ControlFlowNode for ClassExpr | examples.py:6:7:6:11 | GSSA Variable MyObj |
+| examples.py:6:7:6:11 | ControlFlowNode for MyObj | examples.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module examples |
| examples.py:6:7:6:11 | GSSA Variable MyObj | examples.py:25:9:25:13 | ControlFlowNode for MyObj |
| examples.py:6:13:6:18 | ControlFlowNode for object | examples.py:11:17:11:22 | ControlFlowNode for object |
| examples.py:7:5:7:28 | ControlFlowNode for FunctionExpr | examples.py:7:9:7:16 | SSA variable __init__ |
@@ -67,8 +68,10 @@
| examples.py:55:5:55:5 | SSA variable a | examples.py:56:12:56:12 | ControlFlowNode for a |
| examples.py:55:9:55:15 | ControlFlowNode for Attribute | examples.py:55:5:55:5 | SSA variable a |
| test.py:2:13:2:26 | ControlFlowNode for Str | test.py:2:1:2:9 | GSSA Variable NONSOURCE |
+| test.py:3:1:3:6 | ControlFlowNode for SOURCE | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test |
| test.py:3:10:3:17 | ControlFlowNode for Str | test.py:3:1:3:6 | GSSA Variable SOURCE |
| test.py:6:1:6:17 | ControlFlowNode for FunctionExpr | test.py:6:5:6:13 | GSSA Variable is_source |
+| test.py:6:5:6:13 | ControlFlowNode for is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
| test.py:6:15:6:15 | ControlFlowNode for x | test.py:6:15:6:15 | SSA variable x |
| test.py:6:15:6:15 | SSA variable x | test.py:7:12:7:12 | ControlFlowNode for x |
| test.py:7:12:7:12 | ControlFlowNode for x | test.py:7:29:7:29 | ControlFlowNode for x |
@@ -77,17 +80,20 @@
| test.py:7:58:7:58 | ControlFlowNode for x | test.py:7:71:7:71 | ControlFlowNode for x |
| test.py:10:1:10:12 | ControlFlowNode for FunctionExpr | test.py:10:5:10:8 | GSSA Variable SINK |
| test.py:10:1:10:12 | GSSA Variable is_source | test.py:11:8:11:16 | ControlFlowNode for is_source |
+| test.py:10:5:10:8 | ControlFlowNode for SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
| test.py:10:10:10:10 | ControlFlowNode for x | test.py:10:10:10:10 | SSA variable x |
| test.py:10:10:10:10 | SSA variable x | test.py:11:18:11:18 | ControlFlowNode for x |
| test.py:11:18:11:18 | ControlFlowNode for x | test.py:14:34:14:34 | ControlFlowNode for x |
| test.py:11:18:11:18 | [post arg] ControlFlowNode for x | test.py:14:34:14:34 | ControlFlowNode for x |
| test.py:17:1:17:14 | ControlFlowNode for FunctionExpr | test.py:17:5:17:10 | GSSA Variable SINK_F |
| test.py:17:1:17:14 | GSSA Variable is_source | test.py:18:8:18:16 | ControlFlowNode for is_source |
+| test.py:17:5:17:10 | ControlFlowNode for SINK_F | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module test |
| test.py:17:12:17:12 | ControlFlowNode for x | test.py:17:12:17:12 | SSA variable x |
| test.py:17:12:17:12 | SSA variable x | test.py:18:18:18:18 | ControlFlowNode for x |
| test.py:18:18:18:18 | ControlFlowNode for x | test.py:19:34:19:34 | ControlFlowNode for x |
| test.py:18:18:18:18 | [post arg] ControlFlowNode for x | test.py:19:34:19:34 | ControlFlowNode for x |
| test.py:25:1:25:20 | ControlFlowNode for ClassExpr | test.py:25:7:25:11 | GSSA Variable MyObj |
+| test.py:25:7:25:11 | ControlFlowNode for MyObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module test |
| test.py:25:13:25:18 | ControlFlowNode for object | test.py:33:17:33:22 | ControlFlowNode for object |
| test.py:26:5:26:28 | ControlFlowNode for FunctionExpr | test.py:26:9:26:16 | SSA variable __init__ |
| test.py:26:18:26:21 | ControlFlowNode for self | test.py:26:18:26:21 | SSA variable self |
@@ -100,6 +106,7 @@
| test.py:29:22:29:24 | ControlFlowNode for foo | test.py:29:22:29:24 | SSA variable foo |
| test.py:29:22:29:24 | SSA variable foo | test.py:30:20:30:22 | ControlFlowNode for foo |
| test.py:33:1:33:24 | ControlFlowNode for ClassExpr | test.py:33:7:33:15 | GSSA Variable NestedObj |
+| test.py:33:7:33:15 | ControlFlowNode for NestedObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable NestedObj in Module test |
| test.py:34:5:34:23 | ControlFlowNode for FunctionExpr | test.py:34:9:34:16 | SSA variable __init__ |
| test.py:34:5:34:23 | GSSA Variable MyObj | test.py:35:20:35:24 | ControlFlowNode for MyObj |
| test.py:34:18:34:21 | ControlFlowNode for self | test.py:34:18:34:21 | SSA variable self |
@@ -109,6 +116,7 @@
| test.py:37:16:37:19 | SSA variable self | test.py:38:16:38:19 | ControlFlowNode for self |
| test.py:41:1:41:19 | ControlFlowNode for FunctionExpr | test.py:41:5:41:10 | GSSA Variable setFoo |
| test.py:41:1:41:19 | GSSA Variable SINK_F | test.py:42:5:42:10 | ControlFlowNode for SINK_F |
+| test.py:41:5:41:10 | ControlFlowNode for setFoo | test.py:0:0:0:0 | ModuleVariableNode for Global Variable setFoo in Module test |
| test.py:41:12:41:14 | ControlFlowNode for obj | test.py:41:12:41:14 | SSA variable obj |
| test.py:41:12:41:14 | SSA variable obj | test.py:42:12:42:14 | ControlFlowNode for obj |
| test.py:41:17:41:17 | ControlFlowNode for x | test.py:41:17:41:17 | SSA variable x |
@@ -166,6 +174,7 @@
| test.py:86:11:86:27 | ControlFlowNode for MyObj() | test.py:86:5:86:7 | SSA variable obj |
| test.py:90:1:90:30 | ControlFlowNode for FunctionExpr | test.py:90:5:90:26 | GSSA Variable fields_with_local_flow |
| test.py:90:1:90:30 | GSSA Variable MyObj | test.py:91:11:91:15 | ControlFlowNode for MyObj |
+| test.py:90:5:90:26 | ControlFlowNode for fields_with_local_flow | test.py:0:0:0:0 | ModuleVariableNode for Global Variable fields_with_local_flow in Module test |
| test.py:90:28:90:28 | ControlFlowNode for x | test.py:90:28:90:28 | SSA variable x |
| test.py:90:28:90:28 | SSA variable x | test.py:91:17:91:17 | ControlFlowNode for x |
| test.py:91:5:91:7 | SSA variable obj | test.py:92:9:92:11 | ControlFlowNode for obj |
diff --git a/python/ql/test/experimental/dataflow/fieldflow/globalStep.expected b/python/ql/test/experimental/dataflow/fieldflow/globalStep.expected
index 1dde97a8d04..9634fcc303b 100644
--- a/python/ql/test/experimental/dataflow/fieldflow/globalStep.expected
+++ b/python/ql/test/experimental/dataflow/fieldflow/globalStep.expected
@@ -42,6 +42,8 @@
| examples.py:6:1:6:20 | ControlFlowNode for ClassExpr | examples.py:25:9:25:13 | ControlFlowNode for MyObj |
| examples.py:6:1:6:20 | ControlFlowNode for ClassExpr | examples.py:49:7:49:11 | ControlFlowNode for MyObj |
| examples.py:6:1:6:20 | ControlFlowNode for ClassExpr | examples.py:49:7:49:11 | ControlFlowNode for MyObj |
+| examples.py:6:7:6:11 | ControlFlowNode for MyObj | examples.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module examples |
+| examples.py:6:7:6:11 | ControlFlowNode for MyObj | examples.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module examples |
| examples.py:6:7:6:11 | GSSA Variable MyObj | examples.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module examples |
| examples.py:6:7:6:11 | GSSA Variable MyObj | examples.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module examples |
| examples.py:6:7:6:11 | GSSA Variable MyObj | examples.py:25:9:25:13 | ControlFlowNode for MyObj |
@@ -534,12 +536,16 @@
| test.py:0:0:0:0 | ModuleVariableNode for Global Variable setFoo in Module test | test.py:49:5:49:10 | ControlFlowNode for setFoo |
| test.py:2:13:2:26 | ControlFlowNode for Str | test.py:2:1:2:9 | GSSA Variable NONSOURCE |
| test.py:2:13:2:26 | ControlFlowNode for Str | test.py:2:1:2:9 | GSSA Variable NONSOURCE |
+| test.py:3:1:3:6 | ControlFlowNode for SOURCE | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test |
+| test.py:3:1:3:6 | ControlFlowNode for SOURCE | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test |
| test.py:3:1:3:6 | GSSA Variable SOURCE | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test |
| test.py:3:1:3:6 | GSSA Variable SOURCE | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module test |
| test.py:3:10:3:17 | ControlFlowNode for Str | test.py:3:1:3:6 | GSSA Variable SOURCE |
| test.py:3:10:3:17 | ControlFlowNode for Str | test.py:3:1:3:6 | GSSA Variable SOURCE |
| test.py:6:1:6:17 | ControlFlowNode for FunctionExpr | test.py:6:5:6:13 | GSSA Variable is_source |
| test.py:6:1:6:17 | ControlFlowNode for FunctionExpr | test.py:6:5:6:13 | GSSA Variable is_source |
+| test.py:6:5:6:13 | ControlFlowNode for is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
+| test.py:6:5:6:13 | ControlFlowNode for is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
| test.py:6:5:6:13 | GSSA Variable is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
| test.py:6:5:6:13 | GSSA Variable is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
| test.py:6:15:6:15 | ControlFlowNode for x | test.py:6:15:6:15 | SSA variable x |
@@ -634,6 +640,8 @@
| test.py:10:1:10:12 | ControlFlowNode for FunctionExpr | test.py:10:5:10:8 | GSSA Variable SINK |
| test.py:10:1:10:12 | GSSA Variable is_source | test.py:11:8:11:16 | ControlFlowNode for is_source |
| test.py:10:1:10:12 | GSSA Variable is_source | test.py:11:8:11:16 | ControlFlowNode for is_source |
+| test.py:10:5:10:8 | ControlFlowNode for SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
+| test.py:10:5:10:8 | ControlFlowNode for SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
| test.py:10:5:10:8 | GSSA Variable SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
| test.py:10:5:10:8 | GSSA Variable SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
| test.py:10:10:10:10 | ControlFlowNode for x | test.py:10:10:10:10 | SSA variable x |
@@ -684,6 +692,8 @@
| test.py:17:1:17:14 | ControlFlowNode for FunctionExpr | test.py:17:5:17:10 | GSSA Variable SINK_F |
| test.py:17:1:17:14 | GSSA Variable is_source | test.py:18:8:18:16 | ControlFlowNode for is_source |
| test.py:17:1:17:14 | GSSA Variable is_source | test.py:18:8:18:16 | ControlFlowNode for is_source |
+| test.py:17:5:17:10 | ControlFlowNode for SINK_F | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module test |
+| test.py:17:5:17:10 | ControlFlowNode for SINK_F | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module test |
| test.py:17:5:17:10 | GSSA Variable SINK_F | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module test |
| test.py:17:5:17:10 | GSSA Variable SINK_F | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module test |
| test.py:17:12:17:12 | ControlFlowNode for x | test.py:17:12:17:12 | SSA variable x |
@@ -722,6 +732,8 @@
| test.py:19:34:19:34 | [post arg] ControlFlowNode for x | test.py:42:12:42:18 | [post arg] ControlFlowNode for Attribute |
| test.py:25:1:25:20 | ControlFlowNode for ClassExpr | test.py:25:7:25:11 | GSSA Variable MyObj |
| test.py:25:1:25:20 | ControlFlowNode for ClassExpr | test.py:25:7:25:11 | GSSA Variable MyObj |
+| test.py:25:7:25:11 | ControlFlowNode for MyObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module test |
+| test.py:25:7:25:11 | ControlFlowNode for MyObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module test |
| test.py:25:7:25:11 | GSSA Variable MyObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module test |
| test.py:25:7:25:11 | GSSA Variable MyObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable MyObj in Module test |
| test.py:25:13:25:18 | ControlFlowNode for object | test.py:33:17:33:22 | ControlFlowNode for object |
@@ -805,6 +817,8 @@
| test.py:30:20:30:22 | ControlFlowNode for foo | test.py:30:9:30:12 | [post store] ControlFlowNode for self [Attribute foo] |
| test.py:33:1:33:24 | ControlFlowNode for ClassExpr | test.py:33:7:33:15 | GSSA Variable NestedObj |
| test.py:33:1:33:24 | ControlFlowNode for ClassExpr | test.py:33:7:33:15 | GSSA Variable NestedObj |
+| test.py:33:7:33:15 | ControlFlowNode for NestedObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable NestedObj in Module test |
+| test.py:33:7:33:15 | ControlFlowNode for NestedObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable NestedObj in Module test |
| test.py:33:7:33:15 | GSSA Variable NestedObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable NestedObj in Module test |
| test.py:33:7:33:15 | GSSA Variable NestedObj | test.py:0:0:0:0 | ModuleVariableNode for Global Variable NestedObj in Module test |
| test.py:34:5:34:23 | ControlFlowNode for FunctionExpr | test.py:34:9:34:16 | SSA variable __init__ |
@@ -865,6 +879,8 @@
| test.py:41:1:41:19 | ControlFlowNode for FunctionExpr | test.py:41:5:41:10 | GSSA Variable setFoo |
| test.py:41:1:41:19 | GSSA Variable SINK_F | test.py:42:5:42:10 | ControlFlowNode for SINK_F |
| test.py:41:1:41:19 | GSSA Variable SINK_F | test.py:42:5:42:10 | ControlFlowNode for SINK_F |
+| test.py:41:5:41:10 | ControlFlowNode for setFoo | test.py:0:0:0:0 | ModuleVariableNode for Global Variable setFoo in Module test |
+| test.py:41:5:41:10 | ControlFlowNode for setFoo | test.py:0:0:0:0 | ModuleVariableNode for Global Variable setFoo in Module test |
| test.py:41:5:41:10 | GSSA Variable setFoo | test.py:0:0:0:0 | ModuleVariableNode for Global Variable setFoo in Module test |
| test.py:41:5:41:10 | GSSA Variable setFoo | test.py:0:0:0:0 | ModuleVariableNode for Global Variable setFoo in Module test |
| test.py:41:12:41:14 | ControlFlowNode for obj | test.py:41:12:41:14 | SSA variable obj |
@@ -1191,6 +1207,8 @@
| test.py:90:1:90:30 | ControlFlowNode for FunctionExpr | test.py:90:5:90:26 | GSSA Variable fields_with_local_flow |
| test.py:90:1:90:30 | GSSA Variable MyObj | test.py:91:11:91:15 | ControlFlowNode for MyObj |
| test.py:90:1:90:30 | GSSA Variable MyObj | test.py:91:11:91:15 | ControlFlowNode for MyObj |
+| test.py:90:5:90:26 | ControlFlowNode for fields_with_local_flow | test.py:0:0:0:0 | ModuleVariableNode for Global Variable fields_with_local_flow in Module test |
+| test.py:90:5:90:26 | ControlFlowNode for fields_with_local_flow | test.py:0:0:0:0 | ModuleVariableNode for Global Variable fields_with_local_flow in Module test |
| test.py:90:5:90:26 | GSSA Variable fields_with_local_flow | test.py:0:0:0:0 | ModuleVariableNode for Global Variable fields_with_local_flow in Module test |
| test.py:90:5:90:26 | GSSA Variable fields_with_local_flow | test.py:0:0:0:0 | ModuleVariableNode for Global Variable fields_with_local_flow in Module test |
| test.py:90:28:90:28 | ControlFlowNode for x | test.py:90:28:90:28 | SSA variable x |
diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected b/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
index 686c3d33017..bfa46534af0 100644
--- a/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
+++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
@@ -13,32 +13,45 @@
| multiphase.py:2:8:2:9 | GSSA Variable os | multiphase.py:4:17:4:18 | ControlFlowNode for os |
| multiphase.py:4:17:4:18 | ControlFlowNode for os | multiphase.py:4:33:4:34 | ControlFlowNode for os |
| multiphase.py:4:17:4:18 | [post read] ControlFlowNode for os | multiphase.py:4:33:4:34 | ControlFlowNode for os |
+| multiphase.py:8:1:8:9 | ControlFlowNode for NONSOURCE | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable NONSOURCE in Module multiphase |
| multiphase.py:8:1:8:9 | GSSA Variable NONSOURCE | multiphase.py:34:7:34:15 | ControlFlowNode for NONSOURCE |
| multiphase.py:8:13:8:26 | ControlFlowNode for Str | multiphase.py:8:1:8:9 | GSSA Variable NONSOURCE |
+| multiphase.py:9:1:9:6 | ControlFlowNode for SOURCE | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module multiphase |
| multiphase.py:9:10:9:17 | ControlFlowNode for Str | multiphase.py:9:1:9:6 | GSSA Variable SOURCE |
| multiphase.py:12:1:12:17 | ControlFlowNode for FunctionExpr | multiphase.py:12:5:12:13 | GSSA Variable is_source |
+| multiphase.py:12:5:12:13 | ControlFlowNode for is_source | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module multiphase |
| multiphase.py:16:1:16:12 | ControlFlowNode for FunctionExpr | multiphase.py:16:5:16:8 | GSSA Variable SINK |
+| multiphase.py:16:5:16:8 | ControlFlowNode for SINK | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module multiphase |
| multiphase.py:23:1:23:14 | ControlFlowNode for FunctionExpr | multiphase.py:23:5:23:10 | GSSA Variable SINK_F |
| multiphase.py:29:1:29:14 | ControlFlowNode for FunctionExpr | multiphase.py:29:5:29:11 | GSSA Variable set_foo |
+| multiphase.py:29:5:29:11 | ControlFlowNode for set_foo | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable set_foo in Module multiphase |
| multiphase.py:29:5:29:11 | GSSA Variable set_foo | multiphase.py:35:1:35:7 | ControlFlowNode for set_foo |
+| multiphase.py:34:1:34:3 | ControlFlowNode for foo | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable foo in Module multiphase |
| multiphase.py:34:7:34:15 | ControlFlowNode for NONSOURCE | multiphase.py:34:1:34:3 | GSSA Variable foo |
| multiphase.py:37:2:37:11 | ControlFlowNode for expects()() | multiphase.py:38:5:38:15 | GSSA Variable test_phases |
| test.py:2:13:2:26 | ControlFlowNode for Str | test.py:2:1:2:9 | GSSA Variable NONSOURCE |
| test.py:3:10:3:17 | ControlFlowNode for Str | test.py:3:1:3:6 | GSSA Variable SOURCE |
| test.py:6:1:6:17 | ControlFlowNode for FunctionExpr | test.py:6:5:6:13 | GSSA Variable is_source |
+| test.py:6:5:6:13 | ControlFlowNode for is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
| test.py:10:1:10:12 | ControlFlowNode for FunctionExpr | test.py:10:5:10:8 | GSSA Variable SINK |
+| test.py:10:5:10:8 | ControlFlowNode for SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
| test.py:17:1:17:14 | ControlFlowNode for FunctionExpr | test.py:17:5:17:10 | GSSA Variable SINK_F |
| test.py:23:8:23:11 | ControlFlowNode for ImportExpr | test.py:23:8:23:11 | GSSA Variable base |
| test.py:23:8:23:11 | GSSA Variable base | test.py:25:1:25:4 | ControlFlowNode for base |
| test.py:27:8:27:9 | ControlFlowNode for ImportExpr | test.py:27:8:27:9 | GSSA Variable m1 |
+| test.py:27:8:27:9 | ControlFlowNode for m1 | test.py:0:0:0:0 | ModuleVariableNode for Global Variable m1 in Module test |
| test.py:29:1:29:17 | ControlFlowNode for FunctionExpr | test.py:29:5:29:14 | GSSA Variable test_const |
| test.py:32:1:32:23 | ControlFlowNode for FunctionExpr | test.py:32:5:32:20 | GSSA Variable test_overwritten |
| testOnce.py:2:13:2:26 | ControlFlowNode for Str | testOnce.py:2:1:2:9 | GSSA Variable NONSOURCE |
| testOnce.py:3:10:3:17 | ControlFlowNode for Str | testOnce.py:3:1:3:6 | GSSA Variable SOURCE |
| testOnce.py:6:1:6:17 | ControlFlowNode for FunctionExpr | testOnce.py:6:5:6:13 | GSSA Variable is_source |
+| testOnce.py:6:5:6:13 | ControlFlowNode for is_source | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module testOnce |
| testOnce.py:10:1:10:12 | ControlFlowNode for FunctionExpr | testOnce.py:10:5:10:8 | GSSA Variable SINK |
+| testOnce.py:10:5:10:8 | ControlFlowNode for SINK | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module testOnce |
| testOnce.py:17:1:17:14 | ControlFlowNode for FunctionExpr | testOnce.py:17:5:17:10 | GSSA Variable SINK_F |
+| testOnce.py:17:5:17:10 | ControlFlowNode for SINK_F | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module testOnce |
| testOnce.py:23:8:23:9 | ControlFlowNode for ImportExpr | testOnce.py:23:8:23:9 | GSSA Variable m1 |
+| testOnce.py:23:8:23:9 | ControlFlowNode for m1 | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable m1 in Module testOnce |
| testOnce.py:25:8:25:11 | ControlFlowNode for ImportExpr | testOnce.py:25:8:25:11 | GSSA Variable base |
| testOnce.py:25:8:25:11 | GSSA Variable base | testOnce.py:27:1:27:4 | ControlFlowNode for base |
| testOnce.py:29:1:29:17 | ControlFlowNode for FunctionExpr | testOnce.py:29:5:29:14 | GSSA Variable test_const |
diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
index 281881c0de8..f4b587d6d89 100644
--- a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
+++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
@@ -5,6 +5,7 @@ import semmle.python.dataflow.new.DataFlow
pragma[inline]
predicate inCodebase(DataFlow::Node node) { exists(node.getLocation().getFile().getRelativePath()) }
+pragma[inline]
predicate isTopLevel(DataFlow::Node node) { node.getScope() instanceof Module }
predicate inFocus(DataFlow::Node node) {
From 6c5596d17ee77ae920076194bf2495e9efe78a37 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 9 Sep 2021 13:45:44 +0200
Subject: [PATCH 027/361] Python: rewrite test
---
.../module-initialization/localFlow.expected | 58 -------------------
.../module-initialization/localFlow.ql | 47 ++++++++++-----
.../module-initialization/multiphase.py | 35 ++++++-----
3 files changed, 50 insertions(+), 90 deletions(-)
diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected b/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
index bfa46534af0..e69de29bb2d 100644
--- a/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
+++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.expected
@@ -1,58 +0,0 @@
-| base.py:1:7:1:7 | ControlFlowNode for IntegerLiteral | base.py:1:1:1:3 | GSSA Variable foo |
-| m1.py:2:7:2:8 | ControlFlowNode for IntegerLiteral | m1.py:2:1:2:3 | GSSA Variable foo |
-| m1.py:4:8:4:11 | ControlFlowNode for ImportExpr | m1.py:4:8:4:11 | GSSA Variable base |
-| m1.py:4:8:4:11 | GSSA Variable base | m1.py:10:14:10:17 | ControlFlowNode for base |
-| m1.py:6:1:6:14 | ControlFlowNode for FunctionExpr | m1.py:6:5:6:10 | GSSA Variable passOn |
-| m1.py:6:5:6:10 | GSSA Variable passOn | m1.py:10:7:10:12 | ControlFlowNode for passOn |
-| m1.py:10:7:10:22 | ControlFlowNode for passOn() | m1.py:10:1:10:3 | GSSA Variable bar |
-| multiphase.py:0:0:0:0 | GSSA Variable __file__ | multiphase.py:4:50:4:57 | ControlFlowNode for __file__ |
-| multiphase.py:0:0:0:0 | GSSA Variable expects | multiphase.py:37:2:37:8 | ControlFlowNode for expects |
-| multiphase.py:1:8:1:10 | ControlFlowNode for ImportExpr | multiphase.py:1:8:1:10 | GSSA Variable sys |
-| multiphase.py:1:8:1:10 | GSSA Variable sys | multiphase.py:4:1:4:3 | ControlFlowNode for sys |
-| multiphase.py:2:8:2:9 | ControlFlowNode for ImportExpr | multiphase.py:2:8:2:9 | GSSA Variable os |
-| multiphase.py:2:8:2:9 | GSSA Variable os | multiphase.py:4:17:4:18 | ControlFlowNode for os |
-| multiphase.py:4:17:4:18 | ControlFlowNode for os | multiphase.py:4:33:4:34 | ControlFlowNode for os |
-| multiphase.py:4:17:4:18 | [post read] ControlFlowNode for os | multiphase.py:4:33:4:34 | ControlFlowNode for os |
-| multiphase.py:8:1:8:9 | ControlFlowNode for NONSOURCE | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable NONSOURCE in Module multiphase |
-| multiphase.py:8:1:8:9 | GSSA Variable NONSOURCE | multiphase.py:34:7:34:15 | ControlFlowNode for NONSOURCE |
-| multiphase.py:8:13:8:26 | ControlFlowNode for Str | multiphase.py:8:1:8:9 | GSSA Variable NONSOURCE |
-| multiphase.py:9:1:9:6 | ControlFlowNode for SOURCE | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable SOURCE in Module multiphase |
-| multiphase.py:9:10:9:17 | ControlFlowNode for Str | multiphase.py:9:1:9:6 | GSSA Variable SOURCE |
-| multiphase.py:12:1:12:17 | ControlFlowNode for FunctionExpr | multiphase.py:12:5:12:13 | GSSA Variable is_source |
-| multiphase.py:12:5:12:13 | ControlFlowNode for is_source | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module multiphase |
-| multiphase.py:16:1:16:12 | ControlFlowNode for FunctionExpr | multiphase.py:16:5:16:8 | GSSA Variable SINK |
-| multiphase.py:16:5:16:8 | ControlFlowNode for SINK | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module multiphase |
-| multiphase.py:23:1:23:14 | ControlFlowNode for FunctionExpr | multiphase.py:23:5:23:10 | GSSA Variable SINK_F |
-| multiphase.py:29:1:29:14 | ControlFlowNode for FunctionExpr | multiphase.py:29:5:29:11 | GSSA Variable set_foo |
-| multiphase.py:29:5:29:11 | ControlFlowNode for set_foo | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable set_foo in Module multiphase |
-| multiphase.py:29:5:29:11 | GSSA Variable set_foo | multiphase.py:35:1:35:7 | ControlFlowNode for set_foo |
-| multiphase.py:34:1:34:3 | ControlFlowNode for foo | multiphase.py:0:0:0:0 | ModuleVariableNode for Global Variable foo in Module multiphase |
-| multiphase.py:34:7:34:15 | ControlFlowNode for NONSOURCE | multiphase.py:34:1:34:3 | GSSA Variable foo |
-| multiphase.py:37:2:37:11 | ControlFlowNode for expects()() | multiphase.py:38:5:38:15 | GSSA Variable test_phases |
-| test.py:2:13:2:26 | ControlFlowNode for Str | test.py:2:1:2:9 | GSSA Variable NONSOURCE |
-| test.py:3:10:3:17 | ControlFlowNode for Str | test.py:3:1:3:6 | GSSA Variable SOURCE |
-| test.py:6:1:6:17 | ControlFlowNode for FunctionExpr | test.py:6:5:6:13 | GSSA Variable is_source |
-| test.py:6:5:6:13 | ControlFlowNode for is_source | test.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module test |
-| test.py:10:1:10:12 | ControlFlowNode for FunctionExpr | test.py:10:5:10:8 | GSSA Variable SINK |
-| test.py:10:5:10:8 | ControlFlowNode for SINK | test.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module test |
-| test.py:17:1:17:14 | ControlFlowNode for FunctionExpr | test.py:17:5:17:10 | GSSA Variable SINK_F |
-| test.py:23:8:23:11 | ControlFlowNode for ImportExpr | test.py:23:8:23:11 | GSSA Variable base |
-| test.py:23:8:23:11 | GSSA Variable base | test.py:25:1:25:4 | ControlFlowNode for base |
-| test.py:27:8:27:9 | ControlFlowNode for ImportExpr | test.py:27:8:27:9 | GSSA Variable m1 |
-| test.py:27:8:27:9 | ControlFlowNode for m1 | test.py:0:0:0:0 | ModuleVariableNode for Global Variable m1 in Module test |
-| test.py:29:1:29:17 | ControlFlowNode for FunctionExpr | test.py:29:5:29:14 | GSSA Variable test_const |
-| test.py:32:1:32:23 | ControlFlowNode for FunctionExpr | test.py:32:5:32:20 | GSSA Variable test_overwritten |
-| testOnce.py:2:13:2:26 | ControlFlowNode for Str | testOnce.py:2:1:2:9 | GSSA Variable NONSOURCE |
-| testOnce.py:3:10:3:17 | ControlFlowNode for Str | testOnce.py:3:1:3:6 | GSSA Variable SOURCE |
-| testOnce.py:6:1:6:17 | ControlFlowNode for FunctionExpr | testOnce.py:6:5:6:13 | GSSA Variable is_source |
-| testOnce.py:6:5:6:13 | ControlFlowNode for is_source | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable is_source in Module testOnce |
-| testOnce.py:10:1:10:12 | ControlFlowNode for FunctionExpr | testOnce.py:10:5:10:8 | GSSA Variable SINK |
-| testOnce.py:10:5:10:8 | ControlFlowNode for SINK | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK in Module testOnce |
-| testOnce.py:17:1:17:14 | ControlFlowNode for FunctionExpr | testOnce.py:17:5:17:10 | GSSA Variable SINK_F |
-| testOnce.py:17:5:17:10 | ControlFlowNode for SINK_F | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable SINK_F in Module testOnce |
-| testOnce.py:23:8:23:9 | ControlFlowNode for ImportExpr | testOnce.py:23:8:23:9 | GSSA Variable m1 |
-| testOnce.py:23:8:23:9 | ControlFlowNode for m1 | testOnce.py:0:0:0:0 | ModuleVariableNode for Global Variable m1 in Module testOnce |
-| testOnce.py:25:8:25:11 | ControlFlowNode for ImportExpr | testOnce.py:25:8:25:11 | GSSA Variable base |
-| testOnce.py:25:8:25:11 | GSSA Variable base | testOnce.py:27:1:27:4 | ControlFlowNode for base |
-| testOnce.py:29:1:29:17 | ControlFlowNode for FunctionExpr | testOnce.py:29:5:29:14 | GSSA Variable test_const |
-| testOnce.py:32:1:32:25 | ControlFlowNode for FunctionExpr | testOnce.py:32:5:32:22 | GSSA Variable test_unoverwritten |
diff --git a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
index f4b587d6d89..4c334607876 100644
--- a/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
+++ b/python/ql/test/experimental/dataflow/module-initialization/localFlow.ql
@@ -1,21 +1,40 @@
// This query should be more focused yet.
import python
-import semmle.python.dataflow.new.DataFlow
+import experimental.dataflow.TestUtil.FlowTest
+private import semmle.python.dataflow.new.internal.PrintNode
+private import semmle.python.dataflow.new.internal.DataFlowPrivate as DP
-pragma[inline]
-predicate inCodebase(DataFlow::Node node) { exists(node.getLocation().getFile().getRelativePath()) }
+class ImportTimeLocalFlowTest extends InlineExpectationsTest {
+ ImportTimeLocalFlowTest() { this = "ImportTimeLocalFlowTest" }
-pragma[inline]
-predicate isTopLevel(DataFlow::Node node) { node.getScope() instanceof Module }
+ override string getARelevantTag() { result = "importTimeFlow" }
-predicate inFocus(DataFlow::Node node) {
- isTopLevel(node) and
- inCodebase(node)
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(DataFlow::Node nodeFrom, DataFlow::ModuleVariableNode nodeTo |
+ DP::importTimeLocalFlowStep(nodeFrom, nodeTo)
+ |
+ nodeFrom.getLocation().getFile().getBaseName() = "multiphase.py" and
+ location = nodeFrom.getLocation() and
+ tag = "importTimeFlow" and
+ value = "\"" + prettyNode(nodeTo).replaceAll("\"", "'") + "\"" and
+ element = nodeTo.toString()
+ )
+ }
}
-from DataFlow::Node nodeFrom, DataFlow::Node nodeTo
-where
- inFocus(nodeFrom) and
- inFocus(nodeTo) and
- DataFlow::localFlowStep(nodeFrom, nodeTo)
-select nodeFrom, nodeTo
+class RuntimeLocalFlowTest extends FlowTest {
+ RuntimeLocalFlowTest() { this = "RuntimeLocalFlowTest" }
+
+ override string flowTag() { result = "runtimFlow" }
+
+ override predicate relevantFlow(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
+ nodeFrom.getLocation().getFile().getBaseName() = "multiphase.py" and
+ // results are displayed next to `nodeTo`, so we need a line to write on
+ nodeTo.getLocation().getStartLine() > 0 and
+ (
+ nodeFrom instanceof DataFlow::ModuleVariableNode or
+ nodeTo instanceof DataFlow::ModuleVariableNode
+ ) and
+ DP::runtimeJumpStep(nodeFrom, nodeTo)
+ }
+}
diff --git a/python/ql/test/experimental/dataflow/module-initialization/multiphase.py b/python/ql/test/experimental/dataflow/module-initialization/multiphase.py
index ad41e7e8bfa..afb167d2099 100644
--- a/python/ql/test/experimental/dataflow/module-initialization/multiphase.py
+++ b/python/ql/test/experimental/dataflow/module-initialization/multiphase.py
@@ -5,39 +5,38 @@ sys.path.append(os.path.dirname(os.path.dirname((__file__))))
from testlib import *
# These are defined so that we can evaluate the test code.
-NONSOURCE = "not a source"
-SOURCE = "source"
+NONSOURCE = "not a source" #$ importTimeFlow="ModuleVariableNode for Global Variable NONSOURCE in Module multiphase"
+SOURCE = "source" #$ importTimeFlow="ModuleVariableNode for Global Variable SOURCE in Module multiphase"
-def is_source(x):
+def is_source(x): #$ importTimeFlow="ModuleVariableNode for Global Variable is_source in Module multiphase"
return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
-def SINK(x):
- if is_source(x):
- print("OK")
+def SINK(x): #$ importTimeFlow="ModuleVariableNode for Global Variable SINK in Module multiphase"
+ if is_source(x): #$ runtimFlow="ModuleVariableNode for Global Variable is_source in Module multiphase, l:-17 -> is_source"
+ print("OK") #$ runtimFlow="ModuleVariableNode for Global Variable print in Module multiphase, l:-18 -> print"
else:
- print("Unexpected flow", x)
+ print("Unexpected flow", x) #$ runtimFlow="ModuleVariableNode for Global Variable print in Module multiphase, l:-20 -> print"
def SINK_F(x):
- if is_source(x):
- print("Unexpected flow", x)
+ if is_source(x): #$ runtimFlow="ModuleVariableNode for Global Variable is_source in Module multiphase, l:-24 -> is_source"
+ print("Unexpected flow", x) #$ runtimFlow="ModuleVariableNode for Global Variable print in Module multiphase, l:-25 -> print"
else:
- print("OK")
+ print("OK") #$ runtimFlow="ModuleVariableNode for Global Variable print in Module multiphase, l:-27 -> print"
-def set_foo():
+def set_foo(): #$ importTimeFlow="ModuleVariableNode for Global Variable set_foo in Module multiphase"
global foo
- print(foo)
- foo = SOURCE
+ foo = SOURCE #$ runtimFlow="ModuleVariableNode for Global Variable SOURCE in Module multiphase, l:-31 -> SOURCE" MISSING:importTimeFlow="ModuleVariableNode for Global Variable foo in Module multiphase"
-foo = NONSOURCE
+foo = NONSOURCE #$ importTimeFlow="ModuleVariableNode for Global Variable foo in Module multiphase"
set_foo()
@expects(2)
def test_phases():
global foo
- SINK(foo)
- foo = NONSOURCE
- set_foo()
- SINK(foo)
+ SINK(foo) #$ runtimFlow="ModuleVariableNode for Global Variable SINK in Module multiphase, l:-39 -> SINK" runtimFlow="ModuleVariableNode for Global Variable foo in Module multiphase, l:-39 -> foo"
+ foo = NONSOURCE #$ runtimFlow="ModuleVariableNode for Global Variable NONSOURCE in Module multiphase, l:-40 -> NONSOURCE"
+ set_foo() #$ runtimFlow="ModuleVariableNode for Global Variable set_foo in Module multiphase, l:-41 -> set_foo"
+ SINK(foo) #$ runtimFlow="ModuleVariableNode for Global Variable SINK in Module multiphase, l:-42 -> SINK" runtimFlow="ModuleVariableNode for Global Variable foo in Module multiphase, l:-42 -> foo"
From 29ad3bf7f814fbcaa054aaa144c53dea9bd362ae Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 6 Sep 2021 18:08:54 +0100
Subject: [PATCH 028/361] C++: Test dataflow and other slightly more complex
cases.
---
.../tests/CleartextTransmission.expected | 5 +-
.../CWE/CWE-311/semmle/tests/test3.cpp | 93 +++++++++++++++++--
2 files changed, 90 insertions(+), 8 deletions(-)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index 70c9b6bfbfd..0dd4d240c15 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -1,2 +1,3 @@
-| test3.cpp:39:3:39:6 | call to recv | test3.cpp:39:15:39:22 | password |
-| test3.cpp:47:3:47:6 | call to recv | test3.cpp:47:15:47:22 | password |
+| test3.cpp:41:3:41:6 | call to recv | test3.cpp:41:15:41:22 | password |
+| test3.cpp:49:3:49:6 | call to recv | test3.cpp:49:15:49:22 | password |
+| test3.cpp:77:3:77:6 | call to recv | test3.cpp:75:15:75:22 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index 5eafd2dcb70..99fb6e9e640 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -1,10 +1,12 @@
typedef unsigned long size_t;
+#define STDIN_FILENO (0)
size_t strlen(const char *s);
-void send(int a, const void *buf, size_t bufLen, int d);
-void recv(int a, void *buf, size_t bufLen, int d);
+void send(int fd, const void *buf, size_t bufLen, int d);
+void recv(int fd, void *buf, size_t bufLen, int d);
+void read(int fd, void *buf, size_t bufLen);
void LogonUserA(int a, int b, const char *password, int d, int e, int f);
@@ -13,17 +15,17 @@ int val();
void test_send(const char *password1, const char *password2, const char *password_hash, const char *message)
{
{
- LogonUserA(val(), val(), password1, val(), val(), val()); // proof `password` is plaintext
+ LogonUserA(val(), val(), password1, val(), val(), val()); // proof `password1` is plaintext
- send(val(), password1, strlen(password1), val()); // BAD: `password` is sent plaintext (certainly) [NOT DETECTED]
+ send(val(), password1, strlen(password1), val()); // BAD: `password1` is sent plaintext (certainly) [NOT DETECTED]
}
{
- send(val(), password2, strlen(password2), val()); // BAD: `password` is sent plaintext (probably) [NOT DETECTED]
+ send(val(), password2, strlen(password2), val()); // BAD: `password2` is sent plaintext (probably) [NOT DETECTED]
}
{
- send(val(), password_hash, strlen(password_hash), val()); // GOOD: `password` is sent encrypted
+ send(val(), password_hash, strlen(password_hash), val()); // GOOD: `password_hash` is sent encrypted
}
{
@@ -59,3 +61,82 @@ void test_receive()
recv(val(), message, 256, val()); // GOOD: `message` is not a password
}
}
+
+void test_dataflow(const char *password1)
+{
+ {
+ const char *ptr = password1;
+
+ send(val(), ptr, strlen(ptr), val()); // BAD: `password` is sent plaintext [NOT DETECTED]
+ }
+
+ {
+ char password[256];
+ char *ptr = password;
+
+ recv(val(), ptr, 256, val()); // BAD: `password` is received plaintext
+ }
+
+ {
+ char buffer[256];
+
+ recv(val(), buffer, 256, val()); // BAD: `password` is received plaintext [NOT DETECTED]
+
+ char *password = buffer;
+ }
+}
+
+void test_read()
+{
+ {
+ char password[256];
+ int fd = val();
+
+ read(fd, password, 256); // BAD: `password` is received plaintext [NOT DETECTED]
+ }
+
+ {
+ char password[256];
+ int fd = STDIN_FILENO;
+
+ read(fd, password, 256); // GOOD: `password` is received from stdin, not a network socket
+ }
+}
+
+void my_recv(char *buffer, size_t bufferSize)
+{
+ recv(val(), buffer, bufferSize, val());
+}
+
+const char *id(const char *buffer)
+{
+ return buffer;
+}
+
+char *global_password;
+
+char *get_global_str()
+{
+ return global_password;
+}
+
+void test_interprocedural(const char *password1)
+{
+ {
+ char password[256];
+
+ my_recv(password, 256); // BAD: `password` is received plaintext [NOT DETECTED]
+ }
+
+ {
+ const char *ptr = id(password1);
+
+ send(val(), ptr, strlen(ptr), val()); // BAD: `password1` is sent plaintext [NOT DETECTED]
+ }
+
+ {
+ char *data = get_global_str();
+
+ send(val(), data, strlen(data), val()); // BAD: `global_password` is sent plaintext [NOT DETECTED]
+ }
+}
From 1707d67adb4934636cdd3abc7a5d88b9715c6360 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 6 Sep 2021 17:39:10 +0100
Subject: [PATCH 029/361] C++: Support 'send' as well.
---
.../CWE/CWE-311/CleartextTransmission.ql | 39 ++++++++++++++-----
.../tests/CleartextTransmission.expected | 3 ++
.../CWE/CWE-311/semmle/tests/test3.cpp | 6 +--
3 files changed, 35 insertions(+), 13 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 25a173e4362..5e1796b1874 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -17,17 +17,36 @@ import semmle.code.cpp.security.FileWrite
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
-// TODO: network send?
-
/**
- * TODO
+ * A function call that sends or receives data over a network.
*/
-class NetworkRecv extends FunctionCall {
- NetworkRecv() { this.getTarget().hasGlobalName("recv") }
-
- Expr getData() { result = this.getArgument(1) }
+abstract class NetworkSendRecv extends FunctionCall {
+ /**
+ * Gets the expression for the buffer to be sent from / received into.
+ */
+ abstract Expr getDataExpr();
}
-from NetworkRecv recv, SensitiveExpr e
-where DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(recv.(NetworkRecv).getData()))
-select recv, e
+/**
+ * A function call that sends data over a network.
+ *
+ * note: functions such as `read` may be reading from a network source or a file. We could attempt to determine which, and sort results into `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In practice it probably isn't very important which query reports a result as long as its reported exactly once.
+ */
+class NetworkSend extends NetworkSendRecv {
+ NetworkSend() { this.getTarget().hasGlobalName("send") }
+
+ override Expr getDataExpr() { result = this.getArgument(1) }
+}
+
+/**
+ * A function call that receives data over a network.
+ */
+class NetworkRecv extends NetworkSendRecv {
+ NetworkRecv() { this.getTarget().hasGlobalName("recv") }
+
+ override Expr getDataExpr() { result = this.getArgument(1) }
+}
+
+from NetworkSendRecv transmission, SensitiveExpr e
+where DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(transmission.getDataExpr()))
+select transmission, e
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index 0dd4d240c15..c68fabc02dc 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -1,3 +1,6 @@
+| test3.cpp:20:3:20:6 | call to send | test3.cpp:20:15:20:23 | password1 |
+| test3.cpp:24:3:24:6 | call to send | test3.cpp:24:15:24:23 | password2 |
| test3.cpp:41:3:41:6 | call to recv | test3.cpp:41:15:41:22 | password |
| test3.cpp:49:3:49:6 | call to recv | test3.cpp:49:15:49:22 | password |
+| test3.cpp:70:3:70:6 | call to send | test3.cpp:68:21:68:29 | password1 |
| test3.cpp:77:3:77:6 | call to recv | test3.cpp:75:15:75:22 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index 99fb6e9e640..68f863b9048 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -17,11 +17,11 @@ void test_send(const char *password1, const char *password2, const char *passwor
{
LogonUserA(val(), val(), password1, val(), val(), val()); // proof `password1` is plaintext
- send(val(), password1, strlen(password1), val()); // BAD: `password1` is sent plaintext (certainly) [NOT DETECTED]
+ send(val(), password1, strlen(password1), val()); // BAD: `password1` is sent plaintext (certainly)
}
{
- send(val(), password2, strlen(password2), val()); // BAD: `password2` is sent plaintext (probably) [NOT DETECTED]
+ send(val(), password2, strlen(password2), val()); // BAD: `password2` is sent plaintext (probably)
}
{
@@ -67,7 +67,7 @@ void test_dataflow(const char *password1)
{
const char *ptr = password1;
- send(val(), ptr, strlen(ptr), val()); // BAD: `password` is sent plaintext [NOT DETECTED]
+ send(val(), ptr, strlen(ptr), val()); // BAD: `password` is sent plaintext
}
{
From 3ba9e806357a0ce0e59817a0c208807d1f024e95 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 7 Sep 2021 13:09:01 +0100
Subject: [PATCH 030/361] C++: Support various functions / variants.
---
.../Security/CWE/CWE-311/CleartextTransmission.ql | 12 ++++++++++--
.../semmle/tests/CleartextTransmission.expected | 2 ++
.../Security/CWE/CWE-311/semmle/tests/test3.cpp | 4 ++--
3 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 5e1796b1874..b9a87f2dd32 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -33,7 +33,10 @@ abstract class NetworkSendRecv extends FunctionCall {
* note: functions such as `read` may be reading from a network source or a file. We could attempt to determine which, and sort results into `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In practice it probably isn't very important which query reports a result as long as its reported exactly once.
*/
class NetworkSend extends NetworkSendRecv {
- NetworkSend() { this.getTarget().hasGlobalName("send") }
+ NetworkSend() {
+ this.getTarget()
+ .hasGlobalName(["send", "sendto", "sendmsg", "write", "writev", "pwritev", "pwritev2"])
+ }
override Expr getDataExpr() { result = this.getArgument(1) }
}
@@ -42,7 +45,12 @@ class NetworkSend extends NetworkSendRecv {
* A function call that receives data over a network.
*/
class NetworkRecv extends NetworkSendRecv {
- NetworkRecv() { this.getTarget().hasGlobalName("recv") }
+ NetworkRecv() {
+ this.getTarget()
+ .hasGlobalName([
+ "recv", "recvfrom", "recvmsg", "read", "pread", "readv", "preadv", "preadv2"
+ ])
+ }
override Expr getDataExpr() { result = this.getArgument(1) }
}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index c68fabc02dc..7cea9ccb588 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -4,3 +4,5 @@
| test3.cpp:49:3:49:6 | call to recv | test3.cpp:49:15:49:22 | password |
| test3.cpp:70:3:70:6 | call to send | test3.cpp:68:21:68:29 | password1 |
| test3.cpp:77:3:77:6 | call to recv | test3.cpp:75:15:75:22 | password |
+| test3.cpp:95:3:95:6 | call to read | test3.cpp:95:12:95:19 | password |
+| test3.cpp:102:3:102:6 | call to read | test3.cpp:102:12:102:19 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index 68f863b9048..e291e46cbcf 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -92,14 +92,14 @@ void test_read()
char password[256];
int fd = val();
- read(fd, password, 256); // BAD: `password` is received plaintext [NOT DETECTED]
+ read(fd, password, 256); // BAD: `password` is received plaintext
}
{
char password[256];
int fd = STDIN_FILENO;
- read(fd, password, 256); // GOOD: `password` is received from stdin, not a network socket
+ read(fd, password, 256); // GOOD: `password` is received from stdin, not a network socket [FALSE POSITIVE]
}
}
From e696eaaa2f2fa91d75a1e65a62ae2b3e6b97271b Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 7 Sep 2021 13:49:22 +0100
Subject: [PATCH 031/361] C++: Fix false positives involving STDIN_FILENO.
---
.../CWE/CWE-311/CleartextTransmission.ql | 16 +++++++++++++++-
.../semmle/tests/CleartextTransmission.expected | 1 -
.../Security/CWE/CWE-311/semmle/tests/test3.cpp | 2 +-
3 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index b9a87f2dd32..2a53ae68024 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -21,6 +21,12 @@ import semmle.code.cpp.valuenumbering.GlobalValueNumbering
* A function call that sends or receives data over a network.
*/
abstract class NetworkSendRecv extends FunctionCall {
+ /**
+ * Gets the expression for the socket or similar object used for sending or
+ * receiving data.
+ */
+ abstract Expr getSocketExpr();
+
/**
* Gets the expression for the buffer to be sent from / received into.
*/
@@ -38,6 +44,8 @@ class NetworkSend extends NetworkSendRecv {
.hasGlobalName(["send", "sendto", "sendmsg", "write", "writev", "pwritev", "pwritev2"])
}
+ override Expr getSocketExpr() { result = this.getArgument(0) }
+
override Expr getDataExpr() { result = this.getArgument(1) }
}
@@ -52,9 +60,15 @@ class NetworkRecv extends NetworkSendRecv {
])
}
+ override Expr getSocketExpr() { result = this.getArgument(0) }
+
override Expr getDataExpr() { result = this.getArgument(1) }
}
from NetworkSendRecv transmission, SensitiveExpr e
-where DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(transmission.getDataExpr()))
+where
+ DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(transmission.getDataExpr())) and
+ not exists(Zero zero |
+ DataFlow::localFlow(DataFlow::exprNode(zero), DataFlow::exprNode(transmission.getSocketExpr()))
+ )
select transmission, e
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index 7cea9ccb588..55c09e5820b 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -5,4 +5,3 @@
| test3.cpp:70:3:70:6 | call to send | test3.cpp:68:21:68:29 | password1 |
| test3.cpp:77:3:77:6 | call to recv | test3.cpp:75:15:75:22 | password |
| test3.cpp:95:3:95:6 | call to read | test3.cpp:95:12:95:19 | password |
-| test3.cpp:102:3:102:6 | call to read | test3.cpp:102:12:102:19 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index e291e46cbcf..02ccc90396b 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -99,7 +99,7 @@ void test_read()
char password[256];
int fd = STDIN_FILENO;
- read(fd, password, 256); // GOOD: `password` is received from stdin, not a network socket [FALSE POSITIVE]
+ read(fd, password, 256); // GOOD: `password` is received from stdin, not a network socket
}
}
From f58177f292a3b83215a94aa1ccf4dfb29ad8a15d Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 8 Sep 2021 13:32:33 +0100
Subject: [PATCH 032/361] C++: Full dataflow version.
---
.../CWE/CWE-311/CleartextTransmission.ql | 35 ++++++++++++++-----
.../tests/CleartextTransmission.expected | 17 +++++----
.../CWE/CWE-311/semmle/tests/test3.cpp | 6 ++--
3 files changed, 40 insertions(+), 18 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 2a53ae68024..9923fbe7c87 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -5,7 +5,7 @@
* @kind problem
* @problem.severity warning
* @security-severity 7.5 TODO
- * @precision high
+ * @precision high TODO
* @id cpp/cleartext-transmission
* @tags security
* external/cwe/cwe-319
@@ -13,9 +13,7 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
-import semmle.code.cpp.security.FileWrite
import semmle.code.cpp.dataflow.DataFlow
-import semmle.code.cpp.valuenumbering.GlobalValueNumbering
/**
* A function call that sends or receives data over a network.
@@ -65,10 +63,31 @@ class NetworkRecv extends NetworkSendRecv {
override Expr getDataExpr() { result = this.getArgument(1) }
}
-from NetworkSendRecv transmission, SensitiveExpr e
+/**
+ * Taint flow from a sensitive expression to a network operation with data
+ * tainted by that expression.
+ */
+class SensitiveSendRecvConfiguration extends DataFlow::Configuration {
+ SensitiveSendRecvConfiguration() { this = "SensitiveSendRecvConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveExpr }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(NetworkSendRecv transmission |
+ sink.asExpr() = transmission.getDataExpr() and
+ not exists(Zero zero |
+ DataFlow::localFlow(DataFlow::exprNode(zero),
+ DataFlow::exprNode(transmission.getSocketExpr()))
+ )
+ )
+ }
+}
+
+from SensitiveSendRecvConfiguration config1, Expr source, Expr sink
where
- DataFlow::localFlow(DataFlow::exprNode(e), DataFlow::exprNode(transmission.getDataExpr())) and
- not exists(Zero zero |
- DataFlow::localFlow(DataFlow::exprNode(zero), DataFlow::exprNode(transmission.getSocketExpr()))
+ exists(DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode |
+ config1.hasFlowPath(sourceNode, sinkNode) and
+ source = sourceNode.getNode().asExpr() and
+ sink = sinkNode.getNode().asExpr()
)
-select transmission, e
+select sink, source
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index 55c09e5820b..9f3e9900a0b 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -1,7 +1,10 @@
-| test3.cpp:20:3:20:6 | call to send | test3.cpp:20:15:20:23 | password1 |
-| test3.cpp:24:3:24:6 | call to send | test3.cpp:24:15:24:23 | password2 |
-| test3.cpp:41:3:41:6 | call to recv | test3.cpp:41:15:41:22 | password |
-| test3.cpp:49:3:49:6 | call to recv | test3.cpp:49:15:49:22 | password |
-| test3.cpp:70:3:70:6 | call to send | test3.cpp:68:21:68:29 | password1 |
-| test3.cpp:77:3:77:6 | call to recv | test3.cpp:75:15:75:22 | password |
-| test3.cpp:95:3:95:6 | call to read | test3.cpp:95:12:95:19 | password |
+| test3.cpp:20:15:20:23 | password1 | test3.cpp:20:15:20:23 | password1 |
+| test3.cpp:24:15:24:23 | password2 | test3.cpp:24:15:24:23 | password2 |
+| test3.cpp:41:15:41:22 | password | test3.cpp:41:15:41:22 | password |
+| test3.cpp:49:15:49:22 | password | test3.cpp:49:15:49:22 | password |
+| test3.cpp:70:15:70:17 | ptr | test3.cpp:68:21:68:29 | password1 |
+| test3.cpp:77:15:77:17 | ptr | test3.cpp:75:15:75:22 | password |
+| test3.cpp:95:12:95:19 | password | test3.cpp:95:12:95:19 | password |
+| test3.cpp:108:14:108:19 | buffer | test3.cpp:128:11:128:18 | password |
+| test3.cpp:134:15:134:17 | ptr | test3.cpp:132:24:132:32 | password1 |
+| test3.cpp:140:15:140:18 | data | test3.cpp:120:9:120:23 | global_password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index 02ccc90396b..b949667af0c 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -125,18 +125,18 @@ void test_interprocedural(const char *password1)
{
char password[256];
- my_recv(password, 256); // BAD: `password` is received plaintext [NOT DETECTED]
+ my_recv(password, 256); // BAD: `password` is received plaintext [detected on line 108]
}
{
const char *ptr = id(password1);
- send(val(), ptr, strlen(ptr), val()); // BAD: `password1` is sent plaintext [NOT DETECTED]
+ send(val(), ptr, strlen(ptr), val()); // BAD: `password1` is sent plaintext
}
{
char *data = get_global_str();
- send(val(), data, strlen(data), val()); // BAD: `global_password` is sent plaintext [NOT DETECTED]
+ send(val(), data, strlen(data), val()); // BAD: `global_password` is sent plaintext
}
}
From ee7ccd79368a5cbd8bd7cb5d49baac185a0182cf Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Sep 2021 11:11:32 +0100
Subject: [PATCH 033/361] C++: Upgrade to path problem.
---
.../CWE/CWE-311/CleartextTransmission.ql | 25 +++++++---
.../tests/CleartextTransmission.expected | 49 +++++++++++++++----
2 files changed, 56 insertions(+), 18 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 9923fbe7c87..6fd6910211e 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -2,7 +2,7 @@
* @name Cleartext transmission of sensitive information
* @description Transmitting sensitive information across a network in
* cleartext can expose it to an attacker.
- * @kind problem
+ * @kind path-problem
* @problem.severity warning
* @security-severity 7.5 TODO
* @precision high TODO
@@ -14,6 +14,7 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.dataflow.DataFlow
+import DataFlow::PathGraph
/**
* A function call that sends or receives data over a network.
@@ -83,11 +84,19 @@ class SensitiveSendRecvConfiguration extends DataFlow::Configuration {
}
}
-from SensitiveSendRecvConfiguration config1, Expr source, Expr sink
+from
+ SensitiveSendRecvConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink,
+ NetworkSendRecv transmission, string msg
where
- exists(DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode |
- config1.hasFlowPath(sourceNode, sinkNode) and
- source = sourceNode.getNode().asExpr() and
- sink = sinkNode.getNode().asExpr()
- )
-select sink, source
+ config.hasFlowPath(source, sink) and
+ sink.getNode().asExpr() = transmission.getDataExpr() and
+ if transmission instanceof NetworkSend
+ then
+ msg =
+ "This operation transmits '" + sink.toString() +
+ "', which may contain unencrypted sensitive data from $@"
+ else
+ msg =
+ "This operation receives into '" + sink.toString() +
+ "', which may put unencrypted sensitive data into $@"
+select transmission, source, sink, msg, source, source.getNode().asExpr().toString()
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index 9f3e9900a0b..bf997ee45a2 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -1,10 +1,39 @@
-| test3.cpp:20:15:20:23 | password1 | test3.cpp:20:15:20:23 | password1 |
-| test3.cpp:24:15:24:23 | password2 | test3.cpp:24:15:24:23 | password2 |
-| test3.cpp:41:15:41:22 | password | test3.cpp:41:15:41:22 | password |
-| test3.cpp:49:15:49:22 | password | test3.cpp:49:15:49:22 | password |
-| test3.cpp:70:15:70:17 | ptr | test3.cpp:68:21:68:29 | password1 |
-| test3.cpp:77:15:77:17 | ptr | test3.cpp:75:15:75:22 | password |
-| test3.cpp:95:12:95:19 | password | test3.cpp:95:12:95:19 | password |
-| test3.cpp:108:14:108:19 | buffer | test3.cpp:128:11:128:18 | password |
-| test3.cpp:134:15:134:17 | ptr | test3.cpp:132:24:132:32 | password1 |
-| test3.cpp:140:15:140:18 | data | test3.cpp:120:9:120:23 | global_password |
+edges
+| test3.cpp:68:21:68:29 | password1 | test3.cpp:70:15:70:17 | ptr |
+| test3.cpp:75:15:75:22 | password | test3.cpp:77:15:77:17 | ptr |
+| test3.cpp:106:20:106:25 | buffer | test3.cpp:108:14:108:19 | buffer |
+| test3.cpp:120:9:120:23 | global_password | test3.cpp:138:16:138:29 | call to get_global_str |
+| test3.cpp:128:11:128:18 | password | test3.cpp:106:20:106:25 | buffer |
+| test3.cpp:132:21:132:22 | call to id | test3.cpp:134:15:134:17 | ptr |
+| test3.cpp:132:24:132:32 | password1 | test3.cpp:132:21:132:22 | call to id |
+| test3.cpp:138:16:138:29 | call to get_global_str | test3.cpp:140:15:140:18 | data |
+nodes
+| test3.cpp:20:15:20:23 | password1 | semmle.label | password1 |
+| test3.cpp:24:15:24:23 | password2 | semmle.label | password2 |
+| test3.cpp:41:15:41:22 | password | semmle.label | password |
+| test3.cpp:49:15:49:22 | password | semmle.label | password |
+| test3.cpp:68:21:68:29 | password1 | semmle.label | password1 |
+| test3.cpp:70:15:70:17 | ptr | semmle.label | ptr |
+| test3.cpp:75:15:75:22 | password | semmle.label | password |
+| test3.cpp:77:15:77:17 | ptr | semmle.label | ptr |
+| test3.cpp:95:12:95:19 | password | semmle.label | password |
+| test3.cpp:106:20:106:25 | buffer | semmle.label | buffer |
+| test3.cpp:108:14:108:19 | buffer | semmle.label | buffer |
+| test3.cpp:120:9:120:23 | global_password | semmle.label | global_password |
+| test3.cpp:128:11:128:18 | password | semmle.label | password |
+| test3.cpp:132:21:132:22 | call to id | semmle.label | call to id |
+| test3.cpp:132:24:132:32 | password1 | semmle.label | password1 |
+| test3.cpp:134:15:134:17 | ptr | semmle.label | ptr |
+| test3.cpp:138:16:138:29 | call to get_global_str | semmle.label | call to get_global_str |
+| test3.cpp:140:15:140:18 | data | semmle.label | data |
+#select
+| test3.cpp:20:3:20:6 | call to send | test3.cpp:20:15:20:23 | password1 | test3.cpp:20:15:20:23 | password1 | This operation transmits 'password1', which may contain unencrypted sensitive data from $@ | test3.cpp:20:15:20:23 | password1 | password1 |
+| test3.cpp:24:3:24:6 | call to send | test3.cpp:24:15:24:23 | password2 | test3.cpp:24:15:24:23 | password2 | This operation transmits 'password2', which may contain unencrypted sensitive data from $@ | test3.cpp:24:15:24:23 | password2 | password2 |
+| test3.cpp:41:3:41:6 | call to recv | test3.cpp:41:15:41:22 | password | test3.cpp:41:15:41:22 | password | This operation receives into 'password', which may put unencrypted sensitive data into $@ | test3.cpp:41:15:41:22 | password | password |
+| test3.cpp:49:3:49:6 | call to recv | test3.cpp:49:15:49:22 | password | test3.cpp:49:15:49:22 | password | This operation receives into 'password', which may put unencrypted sensitive data into $@ | test3.cpp:49:15:49:22 | password | password |
+| test3.cpp:70:3:70:6 | call to send | test3.cpp:68:21:68:29 | password1 | test3.cpp:70:15:70:17 | ptr | This operation transmits 'ptr', which may contain unencrypted sensitive data from $@ | test3.cpp:68:21:68:29 | password1 | password1 |
+| test3.cpp:77:3:77:6 | call to recv | test3.cpp:75:15:75:22 | password | test3.cpp:77:15:77:17 | ptr | This operation receives into 'ptr', which may put unencrypted sensitive data into $@ | test3.cpp:75:15:75:22 | password | password |
+| test3.cpp:95:3:95:6 | call to read | test3.cpp:95:12:95:19 | password | test3.cpp:95:12:95:19 | password | This operation receives into 'password', which may put unencrypted sensitive data into $@ | test3.cpp:95:12:95:19 | password | password |
+| test3.cpp:108:2:108:5 | call to recv | test3.cpp:128:11:128:18 | password | test3.cpp:108:14:108:19 | buffer | This operation receives into 'buffer', which may put unencrypted sensitive data into $@ | test3.cpp:128:11:128:18 | password | password |
+| test3.cpp:134:3:134:6 | call to send | test3.cpp:132:24:132:32 | password1 | test3.cpp:134:15:134:17 | ptr | This operation transmits 'ptr', which may contain unencrypted sensitive data from $@ | test3.cpp:132:24:132:32 | password1 | password1 |
+| test3.cpp:140:3:140:6 | call to send | test3.cpp:120:9:120:23 | global_password | test3.cpp:140:15:140:18 | data | This operation transmits 'data', which may contain unencrypted sensitive data from $@ | test3.cpp:120:9:120:23 | global_password | global_password |
From 0e8064dbf9e6fa328fa9112a669ce92951c065bd Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Sep 2021 14:53:13 +0100
Subject: [PATCH 034/361] C++: Add a test demonstrating taint.
---
.../Security/CWE/CWE-311/semmle/tests/test3.cpp | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index b949667af0c..1e35220f854 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -140,3 +140,16 @@ void test_interprocedural(const char *password1)
send(val(), data, strlen(data), val()); // BAD: `global_password` is sent plaintext
}
}
+
+char *strncpy(char *s1, const char *s2, size_t n);
+
+void test_taint(const char *password)
+{
+ {
+ char buffer[16];
+
+ strncpy(buffer, password, 16);
+ buffer[15] = 0;
+ send(val(), buffer, 16, val()); // BAD: `password` is (partially) sent plaintext [NOT DETECTED]
+ }
+}
From 67c6b35845b116c7baf54ed7dd399cbcc13a30d1 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Sep 2021 14:44:30 +0100
Subject: [PATCH 035/361] C++: We get many more real world results using taint
tracking.
---
cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql | 4 ++--
.../CWE/CWE-311/semmle/tests/CleartextTransmission.expected | 4 ++++
.../query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp | 2 +-
3 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 6fd6910211e..123c7437990 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -13,7 +13,7 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
-import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.dataflow.TaintTracking
import DataFlow::PathGraph
/**
@@ -68,7 +68,7 @@ class NetworkRecv extends NetworkSendRecv {
* Taint flow from a sensitive expression to a network operation with data
* tainted by that expression.
*/
-class SensitiveSendRecvConfiguration extends DataFlow::Configuration {
+class SensitiveSendRecvConfiguration extends TaintTracking::Configuration {
SensitiveSendRecvConfiguration() { this = "SensitiveSendRecvConfiguration" }
override predicate isSource(DataFlow::Node source) { source.asExpr() instanceof SensitiveExpr }
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index bf997ee45a2..ae2333ba8c9 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -7,6 +7,7 @@ edges
| test3.cpp:132:21:132:22 | call to id | test3.cpp:134:15:134:17 | ptr |
| test3.cpp:132:24:132:32 | password1 | test3.cpp:132:21:132:22 | call to id |
| test3.cpp:138:16:138:29 | call to get_global_str | test3.cpp:140:15:140:18 | data |
+| test3.cpp:151:19:151:26 | password | test3.cpp:153:15:153:20 | buffer |
nodes
| test3.cpp:20:15:20:23 | password1 | semmle.label | password1 |
| test3.cpp:24:15:24:23 | password2 | semmle.label | password2 |
@@ -26,6 +27,8 @@ nodes
| test3.cpp:134:15:134:17 | ptr | semmle.label | ptr |
| test3.cpp:138:16:138:29 | call to get_global_str | semmle.label | call to get_global_str |
| test3.cpp:140:15:140:18 | data | semmle.label | data |
+| test3.cpp:151:19:151:26 | password | semmle.label | password |
+| test3.cpp:153:15:153:20 | buffer | semmle.label | buffer |
#select
| test3.cpp:20:3:20:6 | call to send | test3.cpp:20:15:20:23 | password1 | test3.cpp:20:15:20:23 | password1 | This operation transmits 'password1', which may contain unencrypted sensitive data from $@ | test3.cpp:20:15:20:23 | password1 | password1 |
| test3.cpp:24:3:24:6 | call to send | test3.cpp:24:15:24:23 | password2 | test3.cpp:24:15:24:23 | password2 | This operation transmits 'password2', which may contain unencrypted sensitive data from $@ | test3.cpp:24:15:24:23 | password2 | password2 |
@@ -37,3 +40,4 @@ nodes
| test3.cpp:108:2:108:5 | call to recv | test3.cpp:128:11:128:18 | password | test3.cpp:108:14:108:19 | buffer | This operation receives into 'buffer', which may put unencrypted sensitive data into $@ | test3.cpp:128:11:128:18 | password | password |
| test3.cpp:134:3:134:6 | call to send | test3.cpp:132:24:132:32 | password1 | test3.cpp:134:15:134:17 | ptr | This operation transmits 'ptr', which may contain unencrypted sensitive data from $@ | test3.cpp:132:24:132:32 | password1 | password1 |
| test3.cpp:140:3:140:6 | call to send | test3.cpp:120:9:120:23 | global_password | test3.cpp:140:15:140:18 | data | This operation transmits 'data', which may contain unencrypted sensitive data from $@ | test3.cpp:120:9:120:23 | global_password | global_password |
+| test3.cpp:153:3:153:6 | call to send | test3.cpp:151:19:151:26 | password | test3.cpp:153:15:153:20 | buffer | This operation transmits 'buffer', which may contain unencrypted sensitive data from $@ | test3.cpp:151:19:151:26 | password | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
index 1e35220f854..010ed2c8062 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/test3.cpp
@@ -150,6 +150,6 @@ void test_taint(const char *password)
strncpy(buffer, password, 16);
buffer[15] = 0;
- send(val(), buffer, 16, val()); // BAD: `password` is (partially) sent plaintext [NOT DETECTED]
+ send(val(), buffer, 16, val()); // BAD: `password` is (partially) sent plaintext
}
}
From 8f152a5bfb2cc624db4644f608144eb09ff87ce8 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 13 Sep 2021 16:50:00 +0200
Subject: [PATCH 036/361] Python: Port regex concepts and adapt PolyRedos
---
python/ql/lib/semmle/python/Concepts.qll | 46 ++++++
.../lib/semmle/python/frameworks/Stdlib.qll | 152 ++++++++++++++++++
.../PolynomialReDoSCustomizations.qll | 136 +---------------
3 files changed, 199 insertions(+), 135 deletions(-)
diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll
index 5517347e692..95bf56d11df 100644
--- a/python/ql/lib/semmle/python/Concepts.qll
+++ b/python/ql/lib/semmle/python/Concepts.qll
@@ -355,6 +355,41 @@ module SqlExecution {
}
}
+/**
+ * A data-flow node that executes a regular expression.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `RegexExecution::Range` instead.
+ */
+class RegexExecution extends DataFlow::Node {
+ RegexExecution::Range range;
+
+ RegexExecution() { this = range }
+
+ /** Gets the data flow node for the regex being compiled by this node. */
+ DataFlow::Node getRegexNode() { result = range.getRegexNode() }
+
+ /** Gets a dataflow node for the string to be searched or matched against. */
+ DataFlow::Node getString() { result = range.getString() }
+}
+
+/** Provides classes for modeling new regular-expression execution APIs. */
+module RegexExecution {
+ /**
+ * A data-flow node that executes a regular expression.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `RegexExecution` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /** Gets the data flow node for the regex being compiled by this node. */
+ abstract DataFlow::Node getRegexNode();
+
+ /** Gets a dataflow node for the string to be searched or matched against. */
+ abstract DataFlow::Node getString();
+ }
+}
+
/**
* A data-flow node that escapes meta-characters, which could be used to prevent
* injection attacks.
@@ -411,6 +446,9 @@ module Escaping {
/** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
string getHtmlKind() { result = "html" }
+
+ /** Gets the escape-kind for escaping a string so it can safely be included in HTML. */
+ string getRegexKind() { result = "regex" }
// TODO: If adding an XML kind, update the modeling of the `MarkupSafe` PyPI package.
//
// Technically it claims to escape for both HTML and XML, but for now we don't have
@@ -427,6 +465,14 @@ class HtmlEscaping extends Escaping {
HtmlEscaping() { range.getKind() = Escaping::getHtmlKind() }
}
+/**
+ * An escape of a string so it can be safely included in
+ * the body of a regex.
+ */
+class RegexEscaping extends Escaping {
+ RegexEscaping() { range.getKind() = Escaping::getRegexKind() }
+}
+
/** Provides classes for modeling HTTP-related APIs. */
module HTTP {
import semmle.python.web.HttpConstants
diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll
index 539f0dcabb0..d85e2408453 100644
--- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll
@@ -1497,6 +1497,158 @@ private module StdlibPrivate {
}
}
+// ---------------------------------------------------------------------------
+// re
+// ---------------------------------------------------------------------------
+/**
+ * List of methods in the `re` module immediately executing a regular expression.
+ *
+ * See https://docs.python.org/3/library/re.html#module-contents
+ */
+private class RegexExecutionMethod extends string {
+ RegexExecutionMethod() {
+ this in ["match", "fullmatch", "search", "split", "findall", "finditer", "sub", "subn"]
+ }
+}
+
+/** Gets the index of the argument representing the string to be searched by a regex. */
+int stringArg(RegexExecutionMethod method) {
+ method in ["match", "fullmatch", "search", "split", "findall", "finditer"] and
+ result = 1
+ or
+ method in ["sub", "subn"] and
+ result = 2
+}
+
+/**
+ * A a call to a method from the `re` module immediately executing a regular expression.
+ *
+ * See `RegexExecutionMethods`
+ */
+private class DirectRegex extends DataFlow::CallCfgNode, RegexExecution::Range {
+ RegexExecutionMethod method;
+
+ DirectRegex() { this = API::moduleImport("re").getMember(method).getACall() }
+
+ override DataFlow::Node getRegexNode() {
+ result in [this.getArg(0), this.getArgByName("pattern")]
+ }
+
+ override DataFlow::Node getString() {
+ result in [this.getArg(stringArg(method)), this.getArgByName("string")]
+ }
+}
+
+/** Helper module for tracking compiled regexes. */
+private module CompiledRegexes {
+ private import semmle.python.dataflow.new.DataFlow2
+ private import semmle.python.RegexTreeView
+
+ // TODO: This module should be refactored once API graphs are more expressinve.
+ /** A configuration for finding uses of compiled regexes. */
+ class RegexDefinitionConfiguration extends DataFlow2::Configuration {
+ RegexDefinitionConfiguration() { this = "RegexDefinitionConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RegexDefinitonSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof RegexDefinitionSink }
+ }
+
+ /** A regex compilation. */
+ class RegexDefinitonSource extends DataFlow::CallCfgNode {
+ DataFlow::Node regexNode;
+
+ RegexDefinitonSource() {
+ this = API::moduleImport("re").getMember("compile").getACall() and
+ regexNode in [this.getArg(0), this.getArgByName("pattern")]
+ }
+
+ /** Gets the data flow node for the regex being compiled by this node. */
+ DataFlow::Node getRegexNode() { result = regexNode }
+ }
+
+ /** A use of a compiled regex. */
+ class RegexDefinitionSink extends DataFlow::Node {
+ RegexExecutionMethod method;
+ DataFlow::CallCfgNode executingCall;
+
+ RegexDefinitionSink() {
+ executingCall =
+ API::moduleImport("re").getMember("compile").getReturn().getMember(method).getACall() and
+ this = executingCall.getFunction().(DataFlow::AttrRead).getObject()
+ }
+
+ /** Gets the method used to execute the regex. */
+ RegexExecutionMethod getMethod() { result = method }
+
+ /** Gets the data flow node for the executing call. */
+ DataFlow::CallCfgNode getExecutingCall() { result = executingCall }
+ }
+}
+
+private import CompiledRegexes
+
+/**
+ * A call on compiled regular expression (obtained via `re.compile`) executing a
+ * regular expression.
+ *
+ * Given the following example:
+ *
+ * ```py
+ * pattern = re.compile(input)
+ * pattern.match(s)
+ * ```
+ *
+ * This class will identify that `re.compile` compiles `input` and afterwards
+ * executes `re`'s `match`. As a result, `this` will refer to `pattern.match(s)`
+ * and `this.getRegexNode()` will return the node for `input` (`re.compile`'s first argument).
+ *
+ *
+ * See `RegexExecutionMethods`
+ *
+ * See https://docs.python.org/3/library/re.html#regular-expression-objects
+ */
+private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution {
+ DataFlow::Node regexNode;
+ RegexExecutionMethod method;
+
+ CompiledRegex() {
+ exists(
+ RegexDefinitionConfiguration conf, RegexDefinitonSource source, RegexDefinitionSink sink
+ |
+ conf.hasFlow(source, sink) and
+ regexNode = source.getRegexNode() and
+ method = sink.getMethod() and
+ this = sink.getExecutingCall()
+ )
+ }
+
+ override DataFlow::Node getRegexNode() { result = regexNode }
+
+ override DataFlow::Node getString() {
+ result in [this.getArg(stringArg(method) - 1), this.getArgByName("string")]
+ }
+}
+
+/**
+ * A call to 're.escape'.
+ * See https://docs.python.org/3/library/re.html#re.escape
+ */
+private class ReEscapeCall extends Escaping::Range, DataFlow::CallCfgNode {
+ DataFlow::Node regexNode;
+
+ ReEscapeCall() {
+ this = API::moduleImport("re").getMember("escape").getACall() and
+ regexNode in [this.getArg(0), this.getArgByName("pattern")]
+ }
+
+ override DataFlow::Node getAnInput() { result = regexNode }
+
+ override DataFlow::Node getOutput() { result = this }
+
+ override string getKind() { result = Escaping::getRegexKind() }
+}
+
// ---------------------------------------------------------------------------
// OTHER
// ---------------------------------------------------------------------------
diff --git a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
index cbaf3b982e9..33d88c5d0ed 100644
--- a/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/PolynomialReDoSCustomizations.qll
@@ -60,7 +60,7 @@ module PolynomialReDoS {
RegExpTerm t;
RegexExecutionAsSink() {
- exists(CompiledRegexes::RegexExecution re |
+ exists(RegexExecution re |
re.getRegexNode().asExpr() = t.getRegex() and
this = re.getString()
) and
@@ -76,137 +76,3 @@ module PolynomialReDoS {
*/
class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
}
-
-/** Helper module for tracking compiled regexes. */
-private module CompiledRegexes {
- // TODO: This module should be refactored and merged with the experimental work done on detecting
- // regex injections, such that this can be expressed from just using a concept.
- /** A configuration for finding uses of compiled regexes. */
- class RegexDefinitionConfiguration extends DataFlow2::Configuration {
- RegexDefinitionConfiguration() { this = "RegexDefinitionConfiguration" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RegexDefinitonSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof RegexDefinitionSink }
- }
-
- /** A regex compilation. */
- class RegexDefinitonSource extends DataFlow::CallCfgNode {
- DataFlow::Node regexNode;
-
- RegexDefinitonSource() {
- this = API::moduleImport("re").getMember("compile").getACall() and
- regexNode in [this.getArg(0), this.getArgByName("pattern")]
- }
-
- /** Gets the regex that is being compiled by this node. */
- RegExpTerm getRegExp() { result.getRegex() = regexNode.asExpr() and result.isRootTerm() }
-
- /** Gets the data flow node for the regex being compiled by this node. */
- DataFlow::Node getRegexNode() { result = regexNode }
- }
-
- /** A use of a compiled regex. */
- class RegexDefinitionSink extends DataFlow::Node {
- RegexExecutionMethod method;
- DataFlow::CallCfgNode executingCall;
-
- RegexDefinitionSink() {
- exists(DataFlow::AttrRead reMethod |
- executingCall.getFunction() = reMethod and
- reMethod.getAttributeName() = method and
- this = reMethod.getObject()
- )
- }
-
- /** Gets the method used to execute the regex. */
- RegexExecutionMethod getMethod() { result = method }
-
- /** Gets the data flow node for the executing call. */
- DataFlow::CallCfgNode getExecutingCall() { result = executingCall }
- }
-
- /** A data flow node executing a regex. */
- abstract class RegexExecution extends DataFlow::Node {
- /** Gets the data flow node for the regex being compiled by this node. */
- abstract DataFlow::Node getRegexNode();
-
- /** Gets a dataflow node for the string to be searched or matched against. */
- abstract DataFlow::Node getString();
- }
-
- private class RegexExecutionMethod extends string {
- RegexExecutionMethod() {
- this in ["match", "fullmatch", "search", "split", "findall", "finditer", "sub", "subn"]
- }
- }
-
- /** Gets the index of the argument representing the string to be searched by a regex. */
- int stringArg(RegexExecutionMethod method) {
- method in ["match", "fullmatch", "search", "split", "findall", "finditer"] and
- result = 1
- or
- method in ["sub", "subn"] and
- result = 2
- }
-
- /**
- * A class to find `re` methods immediately executing an expression.
- *
- * See `RegexExecutionMethods`
- */
- class DirectRegex extends DataFlow::CallCfgNode, RegexExecution {
- RegexExecutionMethod method;
-
- DirectRegex() { this = API::moduleImport("re").getMember(method).getACall() }
-
- override DataFlow::Node getRegexNode() {
- result in [this.getArg(0), this.getArgByName("pattern")]
- }
-
- override DataFlow::Node getString() {
- result in [this.getArg(stringArg(method)), this.getArgByName("string")]
- }
- }
-
- /**
- * A class to find `re` methods immediately executing a compiled expression by `re.compile`.
- *
- * Given the following example:
- *
- * ```py
- * pattern = re.compile(input)
- * pattern.match(s)
- * ```
- *
- * This class will identify that `re.compile` compiles `input` and afterwards
- * executes `re`'s `match`. As a result, `this` will refer to `pattern.match(s)`
- * and `this.getRegexNode()` will return the node for `input` (`re.compile`'s first argument)
- *
- *
- * See `RegexExecutionMethods`
- *
- * See https://docs.python.org/3/library/re.html#regular-expression-objects
- */
- private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution {
- DataFlow::Node regexNode;
- RegexExecutionMethod method;
-
- CompiledRegex() {
- exists(
- RegexDefinitionConfiguration conf, RegexDefinitonSource source, RegexDefinitionSink sink
- |
- conf.hasFlow(source, sink) and
- regexNode = source.getRegexNode() and
- method = sink.getMethod() and
- this = sink.getExecutingCall()
- )
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
-
- override DataFlow::Node getString() {
- result in [this.getArg(stringArg(method) - 1), this.getArgByName("string")]
- }
- }
-}
From a30f697537ad96945d75581a3d0fee3fcc9e5fdc Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 11:53:40 +0200
Subject: [PATCH 037/361] Python: Add `getName` to `RegexExecution` concept
---
python/ql/lib/semmle/python/Concepts.qll | 12 ++++++++++++
python/ql/lib/semmle/python/frameworks/Stdlib.qll | 13 +++++++++----
2 files changed, 21 insertions(+), 4 deletions(-)
diff --git a/python/ql/lib/semmle/python/Concepts.qll b/python/ql/lib/semmle/python/Concepts.qll
index 95bf56d11df..66caaa3c877 100644
--- a/python/ql/lib/semmle/python/Concepts.qll
+++ b/python/ql/lib/semmle/python/Concepts.qll
@@ -371,6 +371,12 @@ class RegexExecution extends DataFlow::Node {
/** Gets a dataflow node for the string to be searched or matched against. */
DataFlow::Node getString() { result = range.getString() }
+
+ /**
+ * Gets the name of this regex execution, typically the name of an executing method.
+ * This is used for nice alert messages and should include the module if possible.
+ */
+ string getName() { result = range.getName() }
}
/** Provides classes for modeling new regular-expression execution APIs. */
@@ -387,6 +393,12 @@ module RegexExecution {
/** Gets a dataflow node for the string to be searched or matched against. */
abstract DataFlow::Node getString();
+
+ /**
+ * Gets the name of this regex execution, typically the name of an executing method.
+ * This is used for nice alert messages and should include the module if possible.
+ */
+ abstract string getName();
}
}
diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll
index d85e2408453..76c2f144e09 100644
--- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll
@@ -1537,16 +1537,19 @@ private class DirectRegex extends DataFlow::CallCfgNode, RegexExecution::Range {
override DataFlow::Node getString() {
result in [this.getArg(stringArg(method)), this.getArgByName("string")]
}
+
+ override string getName() { result = "re." + method }
}
/** Helper module for tracking compiled regexes. */
private module CompiledRegexes {
- private import semmle.python.dataflow.new.DataFlow2
+ private import semmle.python.dataflow.new.DataFlow4
private import semmle.python.RegexTreeView
- // TODO: This module should be refactored once API graphs are more expressinve.
+ // TODO: This module should be refactored once API graphs are more expressive.
+ // For now it uses data flow, so we pick the verion with least change of collision (4) .
/** A configuration for finding uses of compiled regexes. */
- class RegexDefinitionConfiguration extends DataFlow2::Configuration {
+ class RegexDefinitionConfiguration extends DataFlow4::Configuration {
RegexDefinitionConfiguration() { this = "RegexDefinitionConfiguration" }
override predicate isSource(DataFlow::Node source) { source instanceof RegexDefinitonSource }
@@ -1608,7 +1611,7 @@ private import CompiledRegexes
*
* See https://docs.python.org/3/library/re.html#regular-expression-objects
*/
-private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution {
+private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution::Range {
DataFlow::Node regexNode;
RegexExecutionMethod method;
@@ -1628,6 +1631,8 @@ private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution {
override DataFlow::Node getString() {
result in [this.getArg(stringArg(method) - 1), this.getArgByName("string")]
}
+
+ override string getName() { result = "re." + method }
}
/**
From 3d5192d6d3048fc2e25ef444820fe9eae36207c8 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 11:54:11 +0200
Subject: [PATCH 038/361] Python: Fix typos
---
.../python/security/dataflow/ReflectedXSSCustomizations.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll
index 0e5410a8be2..93363d3409a 100644
--- a/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll
+++ b/python/ql/lib/semmle/python/security/dataflow/ReflectedXSSCustomizations.qll
@@ -59,7 +59,7 @@ module ReflectedXSS {
class HtmlEscapingAsSanitizer extends Sanitizer {
HtmlEscapingAsSanitizer() {
// TODO: For now, since there is not an `isSanitizingStep` member-predicate part of a
- // `TaintTracking::Configuration`, we use treat the output is a taint-sanitizer. This
+ // `TaintTracking::Configuration`, we treat the output as a taint-sanitizer. This
// is slightly imprecise, which you can see in the `m_unsafe + SAFE` test-case in
// python/ql/test/library-tests/frameworks/markupsafe/taint_test.py
//
From 6c82daef3d48c57fdc06ae9f1cea0563e9db3109 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 11:54:59 +0200
Subject: [PATCH 039/361] Python: Move Regexinjection out of experimental and
fix up structure
---
.../security/injection/RegexInjection.qll | 37 +++++++++++++
.../RegexInjectionCustomizations.qll | 55 +++++++++++++++++++
.../src/Security/CWE-730/RegexInjection.qhelp | 45 +++++++++++++++
.../ql/src/Security/CWE-730/RegexInjection.ql | 28 ++++++++++
python/ql/src/Security/CWE-730/re_bad.py | 15 +++++
python/ql/src/Security/CWE-730/re_good.py | 17 ++++++
6 files changed, 197 insertions(+)
create mode 100644 python/ql/lib/semmle/python/security/injection/RegexInjection.qll
create mode 100644 python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
create mode 100644 python/ql/src/Security/CWE-730/RegexInjection.qhelp
create mode 100644 python/ql/src/Security/CWE-730/RegexInjection.ql
create mode 100644 python/ql/src/Security/CWE-730/re_bad.py
create mode 100644 python/ql/src/Security/CWE-730/re_good.py
diff --git a/python/ql/lib/semmle/python/security/injection/RegexInjection.qll b/python/ql/lib/semmle/python/security/injection/RegexInjection.qll
new file mode 100644
index 00000000000..80601bd638f
--- /dev/null
+++ b/python/ql/lib/semmle/python/security/injection/RegexInjection.qll
@@ -0,0 +1,37 @@
+/**
+ * Provides a taint-tracking configuration for detecting regular expression injection
+ * vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `RegexInjection::Configuration` is needed, otherwise
+ * `RegexInjectionCustomizations` should be imported instead.
+ */
+
+private import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting regular expression injection
+ * vulnerabilities.
+ */
+module RegexInjection {
+ import RegexInjectionCustomizations::RegexInjection
+
+ /**
+ * A taint-tracking configuration for detecting "reflected server-side cross-site scripting" vulnerabilities.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "RegexInjection" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+ }
+}
diff --git a/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
new file mode 100644
index 00000000000..824ec4e7151
--- /dev/null
+++ b/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
@@ -0,0 +1,55 @@
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "regular expression injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+
+private import python
+private import semmle.python.Concepts
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.dataflow.new.RemoteFlowSources
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "regular expression injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+module RegexInjection {
+ /**
+ * A data flow source for "regular expression injection" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A sink for "regular expression injection" vulnerabilities is the execution of a regular expression.
+ * If you have a custom way to execute regular expressions, you can extend `RegexExecution::Range`.
+ */
+ class Sink extends RegexExecution { }
+
+ /**
+ * A sanitizer for "regular expression injection" vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "regular expression injection" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * A regex escaping, considered as a sanitizer.
+ */
+ class RegexEscapingAsSanitizer extends Sanitizer {
+ RegexEscapingAsSanitizer() {
+ // Due to use-use flow, we want the output rather than an input
+ // (so the input can still flow to other sinks).
+ this = any(RegexEscaping esc).getOutput()
+ }
+ }
+}
diff --git a/python/ql/src/Security/CWE-730/RegexInjection.qhelp b/python/ql/src/Security/CWE-730/RegexInjection.qhelp
new file mode 100644
index 00000000000..f19f0744469
--- /dev/null
+++ b/python/ql/src/Security/CWE-730/RegexInjection.qhelp
@@ -0,0 +1,45 @@
+
+
+
+
+Constructing a regular expression with unsanitized user input is dangerous as a malicious user may
+be able to modify the meaning of the expression. In particular, such a user may be able to provide
+a regular expression fragment that takes exponential time in the worst case, and use that to
+perform a Denial of Service attack.
+
+
+
+
+
+Before embedding user input into a regular expression, use a sanitization function such as
+re.escape to escape meta-characters that have a special meaning regarding
+regular expressions' syntax.
+
+
+
+
+
+The following examples are based on a simple Flask web server environment.
+
+
+The following example shows a HTTP request parameter that is used to construct a regular expression
+without sanitizing it first:
+
+
+
+Instead, the request parameter should be sanitized first, for example using the function
+re.escape. This ensures that the user cannot insert characters which have a
+special meaning in regular expressions.
+
+
+
diff --git a/python/ql/src/Security/CWE-730/RegexInjection.ql b/python/ql/src/Security/CWE-730/RegexInjection.ql
new file mode 100644
index 00000000000..363740fe121
--- /dev/null
+++ b/python/ql/src/Security/CWE-730/RegexInjection.ql
@@ -0,0 +1,28 @@
+/**
+ * @name Regular expression injection
+ * @description User input should not be used in regular expressions without first being escaped,
+ * otherwise a malicious user may be able to inject an expression that could require
+ * exponential time on certain inputs.
+ * @kind path-problem
+ * @problem.severity error
+ * @id py/regex-injection
+ * @tags security
+ * external/cwe/cwe-730
+ * external/cwe/cwe-400
+ */
+
+// determine precision above
+import python
+import semmle.python.security.injection.RegexInjection
+import DataFlow::PathGraph
+
+from
+ RegexInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
+ RegexInjection::Sink regexInjectionSink
+where
+ config.hasFlowPath(source, sink) and
+ regexInjectionSink = sink.getNode()
+select sink.getNode(), source, sink,
+ "$@ regular expression is constructed from a $@ and executed by $@.",
+ regexInjectionSink.getRegexNode(), "This", source.getNode(), "user-provided value",
+ regexInjectionSink, regexInjectionSink.getName()
diff --git a/python/ql/src/Security/CWE-730/re_bad.py b/python/ql/src/Security/CWE-730/re_bad.py
new file mode 100644
index 00000000000..3befaba9a01
--- /dev/null
+++ b/python/ql/src/Security/CWE-730/re_bad.py
@@ -0,0 +1,15 @@
+from flask import request, Flask
+import re
+
+
+@app.route("/direct")
+def direct():
+ unsafe_pattern = request.args["pattern"]
+ re.search(unsafe_pattern, "")
+
+
+@app.route("/compile")
+def compile():
+ unsafe_pattern = request.args["pattern"]
+ compiled_pattern = re.compile(unsafe_pattern)
+ compiled_pattern.search("")
diff --git a/python/ql/src/Security/CWE-730/re_good.py b/python/ql/src/Security/CWE-730/re_good.py
new file mode 100644
index 00000000000..cdc9a7ac158
--- /dev/null
+++ b/python/ql/src/Security/CWE-730/re_good.py
@@ -0,0 +1,17 @@
+from flask import request, Flask
+import re
+
+
+@app.route("/direct")
+def direct():
+ unsafe_pattern = request.args['pattern']
+ safe_pattern = re.escape(unsafe_pattern)
+ re.search(safe_pattern, "")
+
+
+@app.route("/compile")
+def compile():
+ unsafe_pattern = request.args['pattern']
+ safe_pattern = re.escape(unsafe_pattern)
+ compiled_pattern = re.compile(safe_pattern)
+ compiled_pattern.search("")
From abbd1d1dc568d7ed52b0172344466b07d7440df3 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 13:08:21 +0200
Subject: [PATCH 040/361] Python: Fix errors introduced during port testing on
a database helps..
---
.../injection/RegexInjectionCustomizations.qll | 9 ++++++++-
python/ql/src/Security/CWE-730/RegexInjection.ql | 10 +++++-----
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll b/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
index 824ec4e7151..a44b4c5c19e 100644
--- a/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
+++ b/python/ql/lib/semmle/python/security/injection/RegexInjectionCustomizations.qll
@@ -25,7 +25,14 @@ module RegexInjection {
* A sink for "regular expression injection" vulnerabilities is the execution of a regular expression.
* If you have a custom way to execute regular expressions, you can extend `RegexExecution::Range`.
*/
- class Sink extends RegexExecution { }
+ class Sink extends DataFlow::Node {
+ RegexExecution regexExecution;
+
+ Sink() { this = regexExecution.getRegexNode() }
+
+ /** Gets the call that executes the regular expression marked by this sink. */
+ RegexExecution getRegexExecution() { result = regexExecution }
+ }
/**
* A sanitizer for "regular expression injection" vulnerabilities.
diff --git a/python/ql/src/Security/CWE-730/RegexInjection.ql b/python/ql/src/Security/CWE-730/RegexInjection.ql
index 363740fe121..8e0f530973f 100644
--- a/python/ql/src/Security/CWE-730/RegexInjection.ql
+++ b/python/ql/src/Security/CWE-730/RegexInjection.ql
@@ -13,16 +13,16 @@
// determine precision above
import python
+private import semmle.python.Concepts
import semmle.python.security.injection.RegexInjection
import DataFlow::PathGraph
from
RegexInjection::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink,
- RegexInjection::Sink regexInjectionSink
+ RegexExecution regexExecution
where
config.hasFlowPath(source, sink) and
- regexInjectionSink = sink.getNode()
+ regexExecution = sink.getNode().(RegexInjection::Sink).getRegexExecution()
select sink.getNode(), source, sink,
- "$@ regular expression is constructed from a $@ and executed by $@.",
- regexInjectionSink.getRegexNode(), "This", source.getNode(), "user-provided value",
- regexInjectionSink, regexInjectionSink.getName()
+ "$@ regular expression is constructed from a $@ and executed by $@.", sink.getNode(), "This",
+ source.getNode(), "user-provided value", regexExecution, regexExecution.getName()
From 36e27f2aa445899d15e62dfa65b0899809b9b51d Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 13:14:16 +0200
Subject: [PATCH 041/361] Python: Remove promoted code: - queries
(`py/regex-injection`) - concepts (RegexExecution, RegexEscape) - library
models (Stdlib::Re)
---
.../Security/CWE-730/RegexInjection.qhelp | 45 ----------
.../Security/CWE-730/RegexInjection.ql | 29 ------
.../experimental/Security/CWE-730/re_bad.py | 15 ----
.../experimental/Security/CWE-730/re_good.py | 17 ----
.../experimental/semmle/python/Concepts.qll | 67 --------------
.../semmle/python/frameworks/Stdlib.qll | 88 -------------------
.../security/injection/RegexInjection.qll | 53 -----------
7 files changed, 314 deletions(-)
delete mode 100644 python/ql/src/experimental/Security/CWE-730/RegexInjection.qhelp
delete mode 100644 python/ql/src/experimental/Security/CWE-730/RegexInjection.ql
delete mode 100644 python/ql/src/experimental/Security/CWE-730/re_bad.py
delete mode 100644 python/ql/src/experimental/Security/CWE-730/re_good.py
delete mode 100644 python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll
diff --git a/python/ql/src/experimental/Security/CWE-730/RegexInjection.qhelp b/python/ql/src/experimental/Security/CWE-730/RegexInjection.qhelp
deleted file mode 100644
index f19f0744469..00000000000
--- a/python/ql/src/experimental/Security/CWE-730/RegexInjection.qhelp
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-Constructing a regular expression with unsanitized user input is dangerous as a malicious user may
-be able to modify the meaning of the expression. In particular, such a user may be able to provide
-a regular expression fragment that takes exponential time in the worst case, and use that to
-perform a Denial of Service attack.
-
-
-
-
-
-Before embedding user input into a regular expression, use a sanitization function such as
-re.escape to escape meta-characters that have a special meaning regarding
-regular expressions' syntax.
-
-
-
-
-
-The following examples are based on a simple Flask web server environment.
-
-
-The following example shows a HTTP request parameter that is used to construct a regular expression
-without sanitizing it first:
-
-
-
-Instead, the request parameter should be sanitized first, for example using the function
-re.escape. This ensures that the user cannot insert characters which have a
-special meaning in regular expressions.
-
-
-
diff --git a/python/ql/src/experimental/Security/CWE-730/RegexInjection.ql b/python/ql/src/experimental/Security/CWE-730/RegexInjection.ql
deleted file mode 100644
index 7725f636eb0..00000000000
--- a/python/ql/src/experimental/Security/CWE-730/RegexInjection.ql
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * @name Regular expression injection
- * @description User input should not be used in regular expressions without first being escaped,
- * otherwise a malicious user may be able to inject an expression that could require
- * exponential time on certain inputs.
- * @kind path-problem
- * @problem.severity error
- * @id py/regex-injection
- * @tags security
- * external/cwe/cwe-730
- * external/cwe/cwe-400
- */
-
-// determine precision above
-import python
-import experimental.semmle.python.security.injection.RegexInjection
-import DataFlow::PathGraph
-
-from
- RegexInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink,
- RegexInjectionSink regexInjectionSink, Attribute methodAttribute
-where
- config.hasFlowPath(source, sink) and
- regexInjectionSink = sink.getNode() and
- methodAttribute = regexInjectionSink.getRegexMethod()
-select sink.getNode(), source, sink,
- "$@ regular expression is constructed from a $@ and executed by $@.", sink.getNode(), "This",
- source.getNode(), "user-provided value", methodAttribute,
- regexInjectionSink.getRegexModule() + "." + methodAttribute.getName()
diff --git a/python/ql/src/experimental/Security/CWE-730/re_bad.py b/python/ql/src/experimental/Security/CWE-730/re_bad.py
deleted file mode 100644
index 3befaba9a01..00000000000
--- a/python/ql/src/experimental/Security/CWE-730/re_bad.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from flask import request, Flask
-import re
-
-
-@app.route("/direct")
-def direct():
- unsafe_pattern = request.args["pattern"]
- re.search(unsafe_pattern, "")
-
-
-@app.route("/compile")
-def compile():
- unsafe_pattern = request.args["pattern"]
- compiled_pattern = re.compile(unsafe_pattern)
- compiled_pattern.search("")
diff --git a/python/ql/src/experimental/Security/CWE-730/re_good.py b/python/ql/src/experimental/Security/CWE-730/re_good.py
deleted file mode 100644
index cdc9a7ac158..00000000000
--- a/python/ql/src/experimental/Security/CWE-730/re_good.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from flask import request, Flask
-import re
-
-
-@app.route("/direct")
-def direct():
- unsafe_pattern = request.args['pattern']
- safe_pattern = re.escape(unsafe_pattern)
- re.search(safe_pattern, "")
-
-
-@app.route("/compile")
-def compile():
- unsafe_pattern = request.args['pattern']
- safe_pattern = re.escape(unsafe_pattern)
- compiled_pattern = re.compile(safe_pattern)
- compiled_pattern.search("")
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index f87caa88497..cff74235f24 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -14,73 +14,6 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
-/** Provides classes for modeling Regular Expression-related APIs. */
-module RegexExecution {
- /**
- * A data-flow node that executes a regular expression.
- *
- * Extend this class to model new APIs. If you want to refine existing API models,
- * extend `RegexExecution` instead.
- */
- abstract class Range extends DataFlow::Node {
- /**
- * Gets the argument containing the executed expression.
- */
- abstract DataFlow::Node getRegexNode();
-
- /**
- * Gets the library used to execute the regular expression.
- */
- abstract string getRegexModule();
- }
-}
-
-/**
- * A data-flow node that executes a regular expression.
- *
- * Extend this class to refine existing API models. If you want to model new APIs,
- * extend `RegexExecution::Range` instead.
- */
-class RegexExecution extends DataFlow::Node {
- RegexExecution::Range range;
-
- RegexExecution() { this = range }
-
- DataFlow::Node getRegexNode() { result = range.getRegexNode() }
-
- string getRegexModule() { result = range.getRegexModule() }
-}
-
-/** Provides classes for modeling Regular Expression escape-related APIs. */
-module RegexEscape {
- /**
- * A data-flow node that escapes a regular expression.
- *
- * Extend this class to model new APIs. If you want to refine existing API models,
- * extend `RegexEscape` instead.
- */
- abstract class Range extends DataFlow::Node {
- /**
- * Gets the argument containing the escaped expression.
- */
- abstract DataFlow::Node getRegexNode();
- }
-}
-
-/**
- * A data-flow node that escapes a regular expression.
- *
- * Extend this class to refine existing API models. If you want to model new APIs,
- * extend `RegexEscape::Range` instead.
- */
-class RegexEscape extends DataFlow::Node {
- RegexEscape::Range range;
-
- RegexEscape() { this = range }
-
- DataFlow::Node getRegexNode() { result = range.getRegexNode() }
-}
-
/** Provides classes for modeling LDAP query execution-related APIs. */
module LDAPQuery {
/**
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll b/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
index b3b70f43394..420caf0d73b 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll
@@ -9,91 +9,3 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.dataflow.new.RemoteFlowSources
private import experimental.semmle.python.Concepts
private import semmle.python.ApiGraphs
-
-/**
- * Provides models for Python's `re` library.
- *
- * See https://docs.python.org/3/library/re.html
- */
-private module Re {
- /**
- * List of `re` methods immediately executing an expression.
- *
- * See https://docs.python.org/3/library/re.html#module-contents
- */
- private class RegexExecutionMethods extends string {
- RegexExecutionMethods() {
- this in ["match", "fullmatch", "search", "split", "findall", "finditer", "sub", "subn"]
- }
- }
-
- /**
- * A class to find `re` methods immediately executing an expression.
- *
- * See `RegexExecutionMethods`
- */
- private class DirectRegex extends DataFlow::CallCfgNode, RegexExecution::Range {
- DataFlow::Node regexNode;
-
- DirectRegex() {
- this = API::moduleImport("re").getMember(any(RegexExecutionMethods m)).getACall() and
- regexNode = this.getArg(0)
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
-
- override string getRegexModule() { result = "re" }
- }
-
- /**
- * A class to find `re` methods immediately executing a compiled expression by `re.compile`.
- *
- * Given the following example:
- *
- * ```py
- * pattern = re.compile(input)
- * pattern.match(s)
- * ```
- *
- * This class will identify that `re.compile` compiles `input` and afterwards
- * executes `re`'s `match`. As a result, `this` will refer to `pattern.match(s)`
- * and `this.getRegexNode()` will return the node for `input` (`re.compile`'s first argument)
- *
- *
- * See `RegexExecutionMethods`
- *
- * See https://docs.python.org/3/library/re.html#regular-expression-objects
- */
- private class CompiledRegex extends DataFlow::MethodCallNode, RegexExecution::Range {
- DataFlow::Node regexNode;
-
- CompiledRegex() {
- exists(DataFlow::MethodCallNode patternCall |
- patternCall = API::moduleImport("re").getMember("compile").getACall() and
- patternCall.flowsTo(this.getObject()) and
- this.getMethodName() instanceof RegexExecutionMethods and
- regexNode = patternCall.getArg(0)
- )
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
-
- override string getRegexModule() { result = "re" }
- }
-
- /**
- * A class to find `re` methods escaping an expression.
- *
- * See https://docs.python.org/3/library/re.html#re.escape
- */
- class ReEscape extends DataFlow::CallCfgNode, RegexEscape::Range {
- DataFlow::Node regexNode;
-
- ReEscape() {
- this = API::moduleImport("re").getMember("escape").getACall() and
- regexNode = this.getArg(0)
- }
-
- override DataFlow::Node getRegexNode() { result = regexNode }
- }
-}
diff --git a/python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll b/python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll
deleted file mode 100644
index 7b7b08cacab..00000000000
--- a/python/ql/src/experimental/semmle/python/security/injection/RegexInjection.qll
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Provides a taint-tracking configuration for detecting regular expression injection
- * vulnerabilities.
- */
-
-import python
-import experimental.semmle.python.Concepts
-import semmle.python.dataflow.new.DataFlow
-import semmle.python.dataflow.new.TaintTracking
-import semmle.python.dataflow.new.RemoteFlowSources
-
-/**
- * A class to find methods executing regular expressions.
- *
- * See `RegexExecution`
- */
-class RegexInjectionSink extends DataFlow::Node {
- string regexModule;
- Attribute regexMethod;
-
- RegexInjectionSink() {
- exists(RegexExecution reExec |
- this = reExec.getRegexNode() and
- regexModule = reExec.getRegexModule() and
- regexMethod = reExec.(DataFlow::CallCfgNode).getFunction().asExpr().(Attribute)
- )
- }
-
- /**
- * Gets the argument containing the executed expression.
- */
- string getRegexModule() { result = regexModule }
-
- /**
- * Gets the method used to execute the regular expression.
- */
- Attribute getRegexMethod() { result = regexMethod }
-}
-
-/**
- * A taint-tracking configuration for detecting regular expression injections.
- */
-class RegexInjectionFlowConfig extends TaintTracking::Configuration {
- RegexInjectionFlowConfig() { this = "RegexInjectionFlowConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof RegexInjectionSink }
-
- override predicate isSanitizer(DataFlow::Node sanitizer) {
- sanitizer = any(RegexEscape reEscape).getRegexNode()
- }
-}
From 1c7982b3199f769b19a4085ee4e0efa5442ff28f Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 13:29:21 +0200
Subject: [PATCH 042/361] Python: Move query tests over
---
.../query-tests/Security/CWE-730/RegexInjection.qlref | 1 -
.../CWE-730-RegexInjection}/RegexInjection.expected | 6 +++---
.../Security/CWE-730-RegexInjection/RegexInjection.qlref | 1 +
.../Security/CWE-730-RegexInjection}/re_bad.py | 0
.../Security/CWE-730-RegexInjection}/re_good.py | 0
5 files changed, 4 insertions(+), 4 deletions(-)
delete mode 100644 python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.qlref
rename python/ql/test/{experimental/query-tests/Security/CWE-730 => query-tests/Security/CWE-730-RegexInjection}/RegexInjection.expected (93%)
create mode 100644 python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.qlref
rename python/ql/test/{experimental/query-tests/Security/CWE-730 => query-tests/Security/CWE-730-RegexInjection}/re_bad.py (100%)
rename python/ql/test/{experimental/query-tests/Security/CWE-730 => query-tests/Security/CWE-730-RegexInjection}/re_good.py (100%)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.qlref b/python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.qlref
deleted file mode 100644
index c0c506c4707..00000000000
--- a/python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.qlref
+++ /dev/null
@@ -1 +0,0 @@
-experimental/Security/CWE-730/RegexInjection.ql
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.expected b/python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.expected
similarity index 93%
rename from python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.expected
rename to python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.expected
index 07a01b5f9dc..598b60ae38a 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-730/RegexInjection.expected
+++ b/python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.expected
@@ -23,6 +23,6 @@ nodes
| re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | semmle.label | ControlFlowNode for unsafe_pattern |
subpaths
#select
-| re_bad.py:14:15:14:28 | ControlFlowNode for unsafe_pattern | re_bad.py:13:22:13:28 | ControlFlowNode for request | re_bad.py:14:15:14:28 | ControlFlowNode for unsafe_pattern | $@ regular expression is constructed from a $@ and executed by $@. | re_bad.py:14:15:14:28 | ControlFlowNode for unsafe_pattern | This | re_bad.py:13:22:13:28 | ControlFlowNode for request | user-provided value | re_bad.py:14:5:14:13 | Attribute | re.search |
-| re_bad.py:25:35:25:48 | ControlFlowNode for unsafe_pattern | re_bad.py:24:22:24:28 | ControlFlowNode for request | re_bad.py:25:35:25:48 | ControlFlowNode for unsafe_pattern | $@ regular expression is constructed from a $@ and executed by $@. | re_bad.py:25:35:25:48 | ControlFlowNode for unsafe_pattern | This | re_bad.py:24:22:24:28 | ControlFlowNode for request | user-provided value | re_bad.py:26:5:26:27 | Attribute | re.search |
-| re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | re_bad.py:36:22:36:28 | ControlFlowNode for request | re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | $@ regular expression is constructed from a $@ and executed by $@. | re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | This | re_bad.py:36:22:36:28 | ControlFlowNode for request | user-provided value | re_bad.py:37:5:37:37 | Attribute | re.search |
+| re_bad.py:14:15:14:28 | ControlFlowNode for unsafe_pattern | re_bad.py:13:22:13:28 | ControlFlowNode for request | re_bad.py:14:15:14:28 | ControlFlowNode for unsafe_pattern | $@ regular expression is constructed from a $@ and executed by $@. | re_bad.py:14:15:14:28 | ControlFlowNode for unsafe_pattern | This | re_bad.py:13:22:13:28 | ControlFlowNode for request | user-provided value | re_bad.py:14:5:14:33 | ControlFlowNode for Attribute() | re.search |
+| re_bad.py:25:35:25:48 | ControlFlowNode for unsafe_pattern | re_bad.py:24:22:24:28 | ControlFlowNode for request | re_bad.py:25:35:25:48 | ControlFlowNode for unsafe_pattern | $@ regular expression is constructed from a $@ and executed by $@. | re_bad.py:25:35:25:48 | ControlFlowNode for unsafe_pattern | This | re_bad.py:24:22:24:28 | ControlFlowNode for request | user-provided value | re_bad.py:26:5:26:31 | ControlFlowNode for Attribute() | re.search |
+| re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | re_bad.py:36:22:36:28 | ControlFlowNode for request | re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | $@ regular expression is constructed from a $@ and executed by $@. | re_bad.py:37:16:37:29 | ControlFlowNode for unsafe_pattern | This | re_bad.py:36:22:36:28 | ControlFlowNode for request | user-provided value | re_bad.py:37:5:37:41 | ControlFlowNode for Attribute() | re.search |
diff --git a/python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.qlref b/python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.qlref
new file mode 100644
index 00000000000..53f8be2a625
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-730-RegexInjection/RegexInjection.qlref
@@ -0,0 +1 @@
+Security/CWE-730/RegexInjection.ql
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-730/re_bad.py b/python/ql/test/query-tests/Security/CWE-730-RegexInjection/re_bad.py
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-730/re_bad.py
rename to python/ql/test/query-tests/Security/CWE-730-RegexInjection/re_bad.py
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-730/re_good.py b/python/ql/test/query-tests/Security/CWE-730-RegexInjection/re_good.py
similarity index 100%
rename from python/ql/test/experimental/query-tests/Security/CWE-730/re_good.py
rename to python/ql/test/query-tests/Security/CWE-730-RegexInjection/re_good.py
From c2d203772628a86c23e516aff02e6000eab8fa2a Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 14 Sep 2021 13:45:51 +0200
Subject: [PATCH 043/361] Python: Add change note and set precision
---
python/change-notes/2021-09-14-promote-regex-injection.md | 2 ++
python/ql/src/Security/CWE-730/RegexInjection.ql | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 python/change-notes/2021-09-14-promote-regex-injection.md
diff --git a/python/change-notes/2021-09-14-promote-regex-injection.md b/python/change-notes/2021-09-14-promote-regex-injection.md
new file mode 100644
index 00000000000..5443f2f03ad
--- /dev/null
+++ b/python/change-notes/2021-09-14-promote-regex-injection.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* The externally contributed query `py/regex-injection` has been promoted out of the experimental area. Results from this query are now available by default.
diff --git a/python/ql/src/Security/CWE-730/RegexInjection.ql b/python/ql/src/Security/CWE-730/RegexInjection.ql
index 8e0f530973f..0dfb5b00d52 100644
--- a/python/ql/src/Security/CWE-730/RegexInjection.ql
+++ b/python/ql/src/Security/CWE-730/RegexInjection.ql
@@ -5,13 +5,13 @@
* exponential time on certain inputs.
* @kind path-problem
* @problem.severity error
+ * @precision high
* @id py/regex-injection
* @tags security
* external/cwe/cwe-730
* external/cwe/cwe-400
*/
-// determine precision above
import python
private import semmle.python.Concepts
import semmle.python.security.injection.RegexInjection
From 9e63aa9d8430c214c5c8bb6f7080c291be18a814 Mon Sep 17 00:00:00 2001
From: haby0
Date: Tue, 14 Sep 2021 21:12:49 +0800
Subject: [PATCH 044/361] Update query
---
.../CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql | 9 +++++++++
.../CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll | 8 ++------
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
index 7866fbd681c..1c96a10d531 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
@@ -13,6 +13,7 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TaintTracking
+import semmle.python.ApiGraphs
import ClientSuppliedIpUsedInSecurityCheckLib
import DataFlow::PathGraph
@@ -30,6 +31,14 @@ class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configura
sink instanceof ClientSuppliedIpUsedInSecurityCheckSink
}
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(DataFlow::CallCfgNode ccn |
+ ccn = API::moduleImport("netaddr").getMember("IPAddress").getACall() and
+ ccn.getArg(0) = pred and
+ ccn = succ
+ )
+ }
+
override predicate isSanitizer(DataFlow::Node node) {
exists(Subscript ss |
not ss.getIndex().(IntegerLiteral).getText() = "0" and
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
index 8113fd17c9d..b003a188b1a 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
@@ -123,14 +123,10 @@ private class CompareSink extends ClientSuppliedIpUsedInSecurityCheckSink {
(
compare.getLeft() = this.asExpr()
or
- compare.getComparator(0) = this.asExpr()
+ compare.getComparator(0) = this.asExpr() and
+ not compare.getLeft().(StrConst).getText() in ["%", ","]
)
)
- or
- exists(Call call |
- call.getFunc().(Attribute).getName() = "add" and
- call.getArg(0) = this.asExpr()
- )
}
}
From c60eded2de54ceb614feccb64be8db23d5065cce Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 15 Sep 2021 11:07:43 +0800
Subject: [PATCH 045/361] Fix conflicting
---
.../Security/CWE-117/LogInjection.qhelp | 49 ++++++++++
.../Security/CWE-117/LogInjection.ql | 20 ++++
.../Security/CWE-117/LogInjectionBad.py | 44 +++++++++
.../Security/CWE-117/LogInjectionGood.py | 25 +++++
.../experimental/semmle/python/Concepts.qll | 30 ++++++
.../experimental/semmle/python/Frameworks.qll | 2 +-
.../semmle/python/frameworks/Log.qll | 92 +++++++++++++++++++
.../security/injection/LogInjection.qll | 24 +++++
.../Security/CWE-117/LogInjection.expected | 27 ++++++
.../Security/CWE-117/LogInjection.qlref | 1 +
.../Security/CWE-117/LogInjectionBad.py | 44 +++++++++
.../Security/CWE-117/LogInjectionGood.py | 25 +++++
12 files changed, 382 insertions(+), 1 deletion(-)
create mode 100644 python/ql/src/experimental/Security/CWE-117/LogInjection.qhelp
create mode 100644 python/ql/src/experimental/Security/CWE-117/LogInjection.ql
create mode 100644 python/ql/src/experimental/Security/CWE-117/LogInjectionBad.py
create mode 100644 python/ql/src/experimental/Security/CWE-117/LogInjectionGood.py
create mode 100644 python/ql/src/experimental/semmle/python/frameworks/Log.qll
create mode 100644 python/ql/src/experimental/semmle/python/security/injection/LogInjection.qll
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.expected
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.qlref
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionBad.py
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionGood.py
diff --git a/python/ql/src/experimental/Security/CWE-117/LogInjection.qhelp b/python/ql/src/experimental/Security/CWE-117/LogInjection.qhelp
new file mode 100644
index 00000000000..e5305220997
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-117/LogInjection.qhelp
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
If unsanitized user input is written to a log entry, a malicious user may be able to forge new log entries.
+
+
Forgery can occur if a user provides some input creating the appearance of multiple
+ log entries. This can include unescaped new-line characters, or HTML or other markup.
+
+
+
+
+User input should be suitably sanitized before it is logged.
+
+
+If the log entries are plain text then line breaks should be removed from user input, using for example
+replace(old, new) or similar. Care should also be taken that user input is clearly marked
+in log entries, and that a malicious user cannot cause confusion in other ways.
+
+
+For log entries that will be displayed in HTML, user input should be HTML encoded before being logged, to prevent forgery and
+other forms of HTML injection.
+
+
+
+
+
+In the example, the name provided by the user is recorded using the log output function (logging.info or app.logger.info, etc.).
+In these four cases, the name provided by the user is not provided The processing is recorded. If a malicious user provides Guest%0D%0AUser name: Admin
+as a parameter, the log entry will be divided into two lines, the first line is User name: Guest code>, the second line is User name: Admin.
+
+
+
+
+In a good example, the program uses the replace function to provide parameter processing to the user, and replace \r\n and \n
+with empty characters. To a certain extent, the occurrence of log injection vulnerabilities is reduced.
+
+
+
diff --git a/python/ql/src/experimental/Security/CWE-117/LogInjection.ql b/python/ql/src/experimental/Security/CWE-117/LogInjection.ql
new file mode 100644
index 00000000000..452ef944825
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-117/LogInjection.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Log Injection
+ * @description Building log entries from user-controlled data is vulnerable to
+ * insertion of forged log entries by a malicious user.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id py/log-injection
+ * @tags security
+ * external/cwe/cwe-117
+ */
+
+import python
+import experimental.semmle.python.security.injection.LogInjection
+import DataFlow::PathGraph
+
+from LogInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ flows to log entry.", source.getNode(),
+ "User-provided value"
diff --git a/python/ql/src/experimental/Security/CWE-117/LogInjectionBad.py b/python/ql/src/experimental/Security/CWE-117/LogInjectionBad.py
new file mode 100644
index 00000000000..0c0cbe1a8fe
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-117/LogInjectionBad.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :Log Injection
+"""
+from flask import Flask
+from flask import request
+from django.utils.log import request_logger
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+
+app = Flask(__name__)
+
+@app.route('/bad1')
+def bad1():
+ name = request.args.get('name')
+ app.logger.info('User name: ' + name) # Bad
+ return 'bad1'
+
+@app.route('/bad2')
+def bad2():
+ name = request.args.get('name')
+ logging.info('User name: ' + name) # Bad
+ return 'bad2'
+
+@app.route('/bad3')
+def bad3():
+ name = request.args.get('name')
+ request_logger.warn('User name: ' + name) # Bad
+ return 'bad3'
+
+@app.route('/bad4')
+def bad4():
+ name = request.args.get('name')
+ logtest = logging.getLogger('test')
+ logtest.debug('User name: ' + name) # Bad
+ return 'bad4'
+
+if __name__ == '__main__':
+ app.debug = True
+ handler = logging.FileHandler('log')
+ app.logger.addHandler(handler)
+ app.run()
diff --git a/python/ql/src/experimental/Security/CWE-117/LogInjectionGood.py b/python/ql/src/experimental/Security/CWE-117/LogInjectionGood.py
new file mode 100644
index 00000000000..d9279f2e482
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-117/LogInjectionGood.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :Log Injection
+"""
+from flask import Flask
+from flask import request
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+
+app = Flask(__name__)
+
+@app.route('/good1')
+def good1():
+ name = request.args.get('name')
+ name = name.replace('\r\n','').replace('\n','')
+ logging.info('User name: ' + name) # Good
+ return 'good1'
+
+if __name__ == '__main__':
+ app.debug = True
+ handler = logging.FileHandler('log')
+ app.logger.addHandler(handler)
+ app.run()
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index f87caa88497..60f12f8c02b 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -14,6 +14,36 @@ private import semmle.python.dataflow.new.RemoteFlowSources
private import semmle.python.dataflow.new.TaintTracking
private import experimental.semmle.python.Frameworks
+/** Provides classes for modeling log related APIs. */
+module LogOutput {
+ /**
+ * A data flow node for log output.
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `LogOutput` instead.
+ */
+ abstract class Range extends DataFlow::Node {
+ /**
+ * Get the parameter value of the log output function.
+ */
+ abstract DataFlow::Node getAnInput();
+ }
+}
+
+/**
+ * A data flow node for log output.
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `LogOutput::Range` instead.
+ */
+class LogOutput extends DataFlow::Node {
+ LogOutput::Range range;
+
+ LogOutput() { this = range }
+
+ DataFlow::Node getAnInput() { result = range.getAnInput() }
+}
+
/** Provides classes for modeling Regular Expression-related APIs. */
module RegexExecution {
/**
diff --git a/python/ql/src/experimental/semmle/python/Frameworks.qll b/python/ql/src/experimental/semmle/python/Frameworks.qll
index e8467383824..5b5161499ff 100644
--- a/python/ql/src/experimental/semmle/python/Frameworks.qll
+++ b/python/ql/src/experimental/semmle/python/Frameworks.qll
@@ -1,7 +1,7 @@
/**
* Helper file that imports all framework modeling.
*/
-
private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.LDAP
private import experimental.semmle.python.frameworks.NoSQL
+private import experimental.semmle.python.frameworks.Log
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Log.qll b/python/ql/src/experimental/semmle/python/frameworks/Log.qll
new file mode 100644
index 00000000000..b05d9e52118
--- /dev/null
+++ b/python/ql/src/experimental/semmle/python/frameworks/Log.qll
@@ -0,0 +1,92 @@
+/**
+ * Provides classes modeling security-relevant aspects of the log libraries.
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import experimental.semmle.python.Concepts
+private import semmle.python.frameworks.Flask
+private import semmle.python.ApiGraphs
+
+/**
+ * Provides models for Python's log-related libraries.
+ */
+private module log {
+ /**
+ * Log output method list.
+ *
+ * See https://docs.python.org/3/library/logging.html#logger-objects
+ */
+ private class LogOutputMethods extends string {
+ LogOutputMethods() { this in ["info", "error", "warn", "warning", "debug", "critical"] }
+ }
+
+ /**
+ * The class used to find the log output method of the `logging` module.
+ *
+ * See `LogOutputMethods`
+ */
+ private class LoggingCall extends DataFlow::CallCfgNode, LogOutput::Range {
+ LoggingCall() {
+ this = API::moduleImport("logging").getMember(any(LogOutputMethods m)).getACall()
+ }
+
+ override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ }
+
+ /**
+ * The class used to find log output methods related to the `logging.getLogger` instance.
+ *
+ * See `LogOutputMethods`
+ */
+ private class LoggerCall extends DataFlow::CallCfgNode, LogOutput::Range {
+ LoggerCall() {
+ this =
+ API::moduleImport("logging")
+ .getMember("getLogger")
+ .getReturn()
+ .getMember(any(LogOutputMethods m))
+ .getACall()
+ }
+
+ override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ }
+
+ /**
+ * The class used to find the relevant log output method of the `flask.Flask.logger` instance (flask application).
+ *
+ * See `LogOutputMethods`
+ */
+ private class FlaskLoggingCall extends DataFlow::CallCfgNode, LogOutput::Range {
+ FlaskLoggingCall() {
+ this =
+ Flask::FlaskApp::instance()
+ .getMember("logger")
+ .getMember(any(LogOutputMethods m))
+ .getACall()
+ }
+
+ override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ }
+
+ /**
+ * The class used to find the relevant log output method of the `django.utils.log.request_logger` instance (django application).
+ *
+ * See `LogOutputMethods`
+ */
+ private class DjangoLoggingCall extends DataFlow::CallCfgNode, LogOutput::Range {
+ DjangoLoggingCall() {
+ this =
+ API::moduleImport("django")
+ .getMember("utils")
+ .getMember("log")
+ .getMember("request_logger")
+ .getMember(any(LogOutputMethods m))
+ .getACall()
+ }
+
+ override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ }
+}
diff --git a/python/ql/src/experimental/semmle/python/security/injection/LogInjection.qll b/python/ql/src/experimental/semmle/python/security/injection/LogInjection.qll
new file mode 100644
index 00000000000..4ee9c69a4a9
--- /dev/null
+++ b/python/ql/src/experimental/semmle/python/security/injection/LogInjection.qll
@@ -0,0 +1,24 @@
+import python
+import semmle.python.Concepts
+import experimental.semmle.python.Concepts
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.dataflow.new.RemoteFlowSources
+
+/**
+ * A taint-tracking configuration for tracking untrusted user input used in log entries.
+ */
+class LogInjectionFlowConfig extends TaintTracking::Configuration {
+ LogInjectionFlowConfig() { this = "LogInjectionFlowConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink = any(LogOutput logoutput).getAnInput() }
+
+ override predicate isSanitizer(DataFlow::Node node) {
+ exists(CallNode call |
+ node.asCfgNode() = call.getFunction().(AttrNode).getObject("replace") and
+ call.getArg(0).getNode().(StrConst).getText() in ["\r\n", "\n"]
+ )
+ }
+}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.expected b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.expected
new file mode 100644
index 00000000000..14774193ce5
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.expected
@@ -0,0 +1,27 @@
+edges
+| LogInjectionBad.py:17:12:17:18 | ControlFlowNode for request | LogInjectionBad.py:17:12:17:23 | ControlFlowNode for Attribute |
+| LogInjectionBad.py:17:12:17:23 | ControlFlowNode for Attribute | LogInjectionBad.py:18:21:18:40 | ControlFlowNode for BinaryExpr |
+| LogInjectionBad.py:23:12:23:18 | ControlFlowNode for request | LogInjectionBad.py:23:12:23:23 | ControlFlowNode for Attribute |
+| LogInjectionBad.py:23:12:23:23 | ControlFlowNode for Attribute | LogInjectionBad.py:24:18:24:37 | ControlFlowNode for BinaryExpr |
+| LogInjectionBad.py:29:12:29:18 | ControlFlowNode for request | LogInjectionBad.py:29:12:29:23 | ControlFlowNode for Attribute |
+| LogInjectionBad.py:29:12:29:23 | ControlFlowNode for Attribute | LogInjectionBad.py:30:25:30:44 | ControlFlowNode for BinaryExpr |
+| LogInjectionBad.py:35:12:35:18 | ControlFlowNode for request | LogInjectionBad.py:35:12:35:23 | ControlFlowNode for Attribute |
+| LogInjectionBad.py:35:12:35:23 | ControlFlowNode for Attribute | LogInjectionBad.py:37:19:37:38 | ControlFlowNode for BinaryExpr |
+nodes
+| LogInjectionBad.py:17:12:17:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| LogInjectionBad.py:17:12:17:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| LogInjectionBad.py:18:21:18:40 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| LogInjectionBad.py:23:12:23:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| LogInjectionBad.py:23:12:23:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| LogInjectionBad.py:24:18:24:37 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| LogInjectionBad.py:29:12:29:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| LogInjectionBad.py:29:12:29:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| LogInjectionBad.py:30:25:30:44 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| LogInjectionBad.py:35:12:35:18 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| LogInjectionBad.py:35:12:35:23 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| LogInjectionBad.py:37:19:37:38 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+#select
+| LogInjectionBad.py:18:21:18:40 | ControlFlowNode for BinaryExpr | LogInjectionBad.py:17:12:17:18 | ControlFlowNode for request | LogInjectionBad.py:18:21:18:40 | ControlFlowNode for BinaryExpr | $@ flows to log entry. | LogInjectionBad.py:17:12:17:18 | ControlFlowNode for request | User-provided value |
+| LogInjectionBad.py:24:18:24:37 | ControlFlowNode for BinaryExpr | LogInjectionBad.py:23:12:23:18 | ControlFlowNode for request | LogInjectionBad.py:24:18:24:37 | ControlFlowNode for BinaryExpr | $@ flows to log entry. | LogInjectionBad.py:23:12:23:18 | ControlFlowNode for request | User-provided value |
+| LogInjectionBad.py:30:25:30:44 | ControlFlowNode for BinaryExpr | LogInjectionBad.py:29:12:29:18 | ControlFlowNode for request | LogInjectionBad.py:30:25:30:44 | ControlFlowNode for BinaryExpr | $@ flows to log entry. | LogInjectionBad.py:29:12:29:18 | ControlFlowNode for request | User-provided value |
+| LogInjectionBad.py:37:19:37:38 | ControlFlowNode for BinaryExpr | LogInjectionBad.py:35:12:35:18 | ControlFlowNode for request | LogInjectionBad.py:37:19:37:38 | ControlFlowNode for BinaryExpr | $@ flows to log entry. | LogInjectionBad.py:35:12:35:18 | ControlFlowNode for request | User-provided value |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.qlref b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.qlref
new file mode 100644
index 00000000000..021cc357ac2
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-117/LogInjection.ql
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionBad.py b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionBad.py
new file mode 100644
index 00000000000..0c0cbe1a8fe
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionBad.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :Log Injection
+"""
+from flask import Flask
+from flask import request
+from django.utils.log import request_logger
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+
+app = Flask(__name__)
+
+@app.route('/bad1')
+def bad1():
+ name = request.args.get('name')
+ app.logger.info('User name: ' + name) # Bad
+ return 'bad1'
+
+@app.route('/bad2')
+def bad2():
+ name = request.args.get('name')
+ logging.info('User name: ' + name) # Bad
+ return 'bad2'
+
+@app.route('/bad3')
+def bad3():
+ name = request.args.get('name')
+ request_logger.warn('User name: ' + name) # Bad
+ return 'bad3'
+
+@app.route('/bad4')
+def bad4():
+ name = request.args.get('name')
+ logtest = logging.getLogger('test')
+ logtest.debug('User name: ' + name) # Bad
+ return 'bad4'
+
+if __name__ == '__main__':
+ app.debug = True
+ handler = logging.FileHandler('log')
+ app.logger.addHandler(handler)
+ app.run()
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionGood.py b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionGood.py
new file mode 100644
index 00000000000..d9279f2e482
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-117/LogInjectionGood.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+# -*- coding: UTF-8 -*-
+"""
+@Desc :Log Injection
+"""
+from flask import Flask
+from flask import request
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+
+app = Flask(__name__)
+
+@app.route('/good1')
+def good1():
+ name = request.args.get('name')
+ name = name.replace('\r\n','').replace('\n','')
+ logging.info('User name: ' + name) # Good
+ return 'good1'
+
+if __name__ == '__main__':
+ app.debug = True
+ handler = logging.FileHandler('log')
+ app.logger.addHandler(handler)
+ app.run()
From 02776017055491f0e81143e1f2907991689882ea Mon Sep 17 00:00:00 2001
From: haby0
Date: Thu, 16 Sep 2021 20:59:34 +0800
Subject: [PATCH 046/361] Eliminate false positives caused by `.`
---
.../Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
index b003a188b1a..82da4f5b46f 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
@@ -124,7 +124,7 @@ private class CompareSink extends ClientSuppliedIpUsedInSecurityCheckSink {
compare.getLeft() = this.asExpr()
or
compare.getComparator(0) = this.asExpr() and
- not compare.getLeft().(StrConst).getText() in ["%", ","]
+ not compare.getLeft().(StrConst).getText() in ["%", ",", "."]
)
)
}
From 64685f31dc804e9c34d2693001010358614c308c Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 16 Sep 2021 16:51:43 +0200
Subject: [PATCH 047/361] Python: Add missing qldoc Also do some general
cleanup How was this allowed comitted in the first place?
---
python/ql/lib/semmle/python/RegexTreeView.qll | 52 ++++++++++++-------
1 file changed, 32 insertions(+), 20 deletions(-)
diff --git a/python/ql/lib/semmle/python/RegexTreeView.qll b/python/ql/lib/semmle/python/RegexTreeView.qll
index ad1949e4bc4..3884bed9763 100644
--- a/python/ql/lib/semmle/python/RegexTreeView.qll
+++ b/python/ql/lib/semmle/python/RegexTreeView.qll
@@ -49,6 +49,7 @@ newtype TRegExpParent =
* or another regular expression term.
*/
class RegExpParent extends TRegExpParent {
+ /** Gets a textual representation of this element. */
string toString() { result = "RegExpParent" }
/** Gets the `i`th child term. */
@@ -72,14 +73,18 @@ class RegExpLiteral extends TRegExpLiteral, RegExpParent {
override RegExpTerm getChild(int i) { i = 0 and result.getRegex() = re and result.isRootTerm() }
+ /** Holds if dot, `.`, matches all characters, including newlines. */
predicate isDotAll() { re.getAMode() = "DOTALL" }
+ /** Holds if this regex matching is case-insensitive for this regex. */
predicate isIgnoreCase() { re.getAMode() = "IGNORECASE" }
+ /** Get a string representing all modes for this regex. */
string getFlags() { result = concat(string mode | mode = re.getAMode() | mode, " | ") }
override Regex getRegex() { result = re }
+ /** Gets the primary QL class for this regex. */
string getPrimaryQLClass() { result = "RegExpLiteral" }
}
@@ -246,8 +251,10 @@ class RegExpQuantifier extends RegExpTerm, TRegExpQuantifier {
result.getEnd() = part_end
}
+ /** Hols if this term may match an unlimited number of times. */
predicate mayRepeatForever() { may_repeat_forever = true }
+ /** Gets the qualifier for this term. That is e.g "?" for "a?". */
string getQualifier() { result = re.getText().substring(part_end, end) }
override string getPrimaryQLClass() { result = "RegExpQuantifier" }
@@ -322,8 +329,10 @@ class RegExpRange extends RegExpQuantifier {
RegExpRange() { re.multiples(part_end, end, lower, upper) }
+ /** Gets the string defining the upper bound of this range, if any. */
string getUpper() { result = upper }
+ /** Gets the string defining the lower bound of this range, if any. */
string getLower() { result = lower }
/**
@@ -465,11 +474,13 @@ class RegExpEscape extends RegExpNormalChar {
result = getUnicode()
}
+ /** Holds if this terms name is given by the part following the escape character. */
predicate isIdentityEscape() { not this.getUnescaped() in ["n", "r", "t", "f"] }
override string getPrimaryQLClass() { result = "RegExpEscape" }
- string getUnescaped() { result = this.getText().suffix(1) }
+ /** Gets the part of the term following the escape character. That is e.g. "w" if the term is "\w". */
+ private string getUnescaped() { result = this.getText().suffix(1) }
/**
* Gets the text for this escape. That is e.g. "\w".
@@ -536,15 +547,8 @@ private int toHex(string hex) {
* ```
*/
class RegExpCharacterClassEscape extends RegExpEscape {
- // string value;
- RegExpCharacterClassEscape() {
- // value = re.getText().substring(start + 1, end) and
- // value in ["d", "D", "s", "S", "w", "W"]
- this.getValue() in ["d", "D", "s", "S", "w", "W"]
- }
+ RegExpCharacterClassEscape() { this.getValue() in ["d", "D", "s", "S", "w", "W"] }
- /** Gets the name of the character class; for example, `w` for `\w`. */
- // override string getValue() { result = value }
override RegExpTerm getChild(int i) { none() }
override string getPrimaryQLClass() { result = "RegExpCharacterClassEscape" }
@@ -563,10 +567,13 @@ class RegExpCharacterClassEscape extends RegExpEscape {
class RegExpCharacterClass extends RegExpTerm, TRegExpCharacterClass {
RegExpCharacterClass() { this = TRegExpCharacterClass(re, start, end) }
+ /** Holds if this character class is inverted, matching the opposite of its content. */
predicate isInverted() { re.getChar(start + 1) = "^" }
+ /** Gets the `i`th char inside this charater class. */
string getCharThing(int i) { result = re.getChar(i + start) }
+ /** Holds if this character class can match anything. */
predicate isUniversalClass() {
// [^]
isInverted() and not exists(getAChild())
@@ -620,6 +627,7 @@ class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange {
re.charRange(_, start, lower_end, upper_start, end)
}
+ /** Holds if this range goes from `lo` to `hi`, in effect is `lo-hi`. */
predicate isRange(string lo, string hi) {
lo = re.getText().substring(start, lower_end) and
hi = re.getText().substring(upper_start, end)
@@ -653,8 +661,13 @@ class RegExpCharacterRange extends RegExpTerm, TRegExpCharacterRange {
class RegExpNormalChar extends RegExpTerm, TRegExpNormalChar {
RegExpNormalChar() { this = TRegExpNormalChar(re, start, end) }
+ /**
+ * Holds if this constant represents a valid Unicode character (as opposed
+ * to a surrogate code point that does not correspond to a character by itself.)
+ */
predicate isCharacter() { any() }
+ /** Gets the string representation of the char matched by this term. */
string getValue() { result = re.getText().substring(start, end) }
override RegExpTerm getChild(int i) { none() }
@@ -684,15 +697,15 @@ class RegExpConstant extends RegExpTerm {
qstart <= start and end <= qend
) and
value = this.(RegExpNormalChar).getValue()
- // This will never hold
- // or
- // this = TRegExpSpecialChar(re, start, end) and
- // re.inCharSet(start) and
- // value = this.(RegExpSpecialChar).getChar()
}
+ /**
+ * Holds if this constant represents a valid Unicode character (as opposed
+ * to a surrogate code point that does not correspond to a character by itself.)
+ */
predicate isCharacter() { any() }
+ /** Gets the string matched by this constant term. */
string getValue() { result = value }
override RegExpTerm getChild(int i) { none() }
@@ -731,10 +744,6 @@ class RegExpGroup extends RegExpTerm, TRegExpGroup {
/** Gets the name of this capture group, if any. */
string getName() { result = re.getGroupName(start, end) }
- predicate isCharacter() { any() }
-
- string getValue() { result = re.getText().substring(start, end) }
-
override RegExpTerm getChild(int i) {
result.getRegex() = re and
i = 0 and
@@ -762,8 +771,13 @@ class RegExpSpecialChar extends RegExpTerm, TRegExpSpecialChar {
re.specialCharacter(start, end, char)
}
+ /**
+ * Holds if this constant represents a valid Unicode character (as opposed
+ * to a surrogate code point that does not correspond to a character by itself.)
+ */
predicate isCharacter() { any() }
+ /** Gets the char for this term. */
string getChar() { result = char }
override RegExpTerm getChild(int i) { none() }
@@ -828,8 +842,6 @@ class RegExpCaret extends RegExpSpecialChar {
class RegExpZeroWidthMatch extends RegExpGroup {
RegExpZeroWidthMatch() { re.zeroWidthMatch(start, end) }
- override predicate isCharacter() { any() }
-
override RegExpTerm getChild(int i) { none() }
override string getPrimaryQLClass() { result = "RegExpZeroWidthMatch" }
From b393c6a285f9ff3ffc81c843fb301b4509b93af4 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 16 Sep 2021 19:16:54 +0300
Subject: [PATCH 048/361] Add files via upload
---
.../CWE/CWE-1041/FindWrapperFunctions.cpp | 18 +++
.../CWE/CWE-1041/FindWrapperFunctions.qhelp | 18 +++
.../CWE/CWE-1041/FindWrapperFunctions.ql | 140 ++++++++++++++++++
3 files changed, 176 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.cpp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.qhelp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.cpp
new file mode 100644
index 00000000000..a04107bbadf
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.cpp
@@ -0,0 +1,18 @@
+...
+int myFclose(FILE * fmy)
+{
+ if(!fclose(fmy)) {
+ fmy = NULL;
+ return 0;
+ }
+ return -1;
+}
+...
+ fe = fopen("myFile.txt", "wt");
+ ...
+ fclose(fe); // BAD
+...
+ fe = fopen("myFile.txt", "wt");
+ ...
+ myFclose(fe); // GOOD
+...
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.qhelp
new file mode 100644
index 00000000000..1eb5b3e5e5c
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.qhelp
@@ -0,0 +1,18 @@
+
+
+
+
Finding for function calls for which wrapper functions exist.
+
+
+
+
+
The following example demonstrates fallacious and fixed methods of using wrapper functions.
+
+
+
+
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
new file mode 100644
index 00000000000..1baafd4a171
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
@@ -0,0 +1,140 @@
+/**
+ * @name Find Wrapper Functions
+ * @description --Finding for function calls for which wrapper functions exist.
+ * @kind problem
+ * @id cpp/find-wrapper-functions
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-1041
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+import semmle.code.cpp.commons.Assertions
+
+/**
+ * A function call that is used in error situations (logging, throwing an exception, abnormal termination).
+ */
+class CallUsedToHandleErrors extends FunctionCall {
+ CallUsedToHandleErrors() {
+ // call that is known to not return
+ not exists(this.(ControlFlowNode).getASuccessor())
+ or
+ // call throwing an exception
+ exists(ThrowExpr tex | tex = this.(ControlFlowNode).getASuccessor())
+ or
+ // call logging a message, possibly an error
+ exists(FormattingFunction ff | ff = this.(ControlFlowNode).getASuccessor())
+ or
+ // enabling recursive search
+ exists(CallUsedToHandleErrors fr | getTarget() = fr.getEnclosingFunction())
+ }
+}
+
+/** Holds if the conditions for a call outside the wrapper function are met. */
+predicate conditionsOutsideWrapper(FunctionCall fcp) {
+ fcp.getNumberOfArguments() > 0 and
+ not exists(ConditionalStmt cdtmp | fcp.getEnclosingStmt().getParentStmt*() = cdtmp) and
+ not exists(Loop lptmp | fcp.getEnclosingStmt().getParentStmt*() = lptmp) and
+ not exists(ReturnStmt rttmp | fcp.getEnclosingStmt().getParentStmt*() = rttmp) and
+ not exists(FunctionCall fctmp2 | fcp = fctmp2.getAnArgument().getAChild*()) and
+ not exists(Assignment astmp | fcp = astmp.getRValue().getAChild*()) and
+ not exists(Initializer intmp | fcp = intmp.getExpr().getAChild*()) and
+ not exists(Assertion astmp | fcp = astmp.getAsserted().getAChild*()) and
+ not exists(Operation optmp | fcp = optmp.getAChild*()) and
+ not exists(ArrayExpr aetmp | fcp = aetmp.getAChild*()) and
+ not exists(ExprCall ectmp | fcp = ectmp.getAnArgument().getAChild*())
+}
+
+/** Holds if the conditions for a call within the wrapper function are met. */
+pragma[inline]
+predicate conditionsInsideWrapper(FunctionCall fcp, Function fnp) {
+ not exists(FunctionCall fctmp2 |
+ fctmp2.getEnclosingFunction() = fnp and fcp = fctmp2.getAnArgument().getAChild*()
+ ) and
+ not fcp instanceof CallUsedToHandleErrors and
+ not fcp.getAnArgument().isConstant() and
+ fcp.getEnclosingFunction() = fnp and
+ fnp.getNumberOfParameters() > 0 and
+ // the call arguments must be passed through the arguments of the wrapper function
+ forall(int i | i in [0 .. fcp.getNumberOfArguments() - 1] |
+ fcp.getArgument(i).(VariableAccess).getTarget() = fnp.getAParameter().getAnAccess().getTarget()
+ ) and
+ // there should be no more than one required call inside the wrapper function
+ not exists(FunctionCall fctmp |
+ fctmp.getTarget() = fcp.getTarget() and
+ fctmp.getFile() = fcp.getFile() and
+ fctmp != fcp and
+ fctmp.getEnclosingFunction() = fnp
+ ) and
+ // inside the wrapper function there should be no calls without paths to the desired function
+ not exists(FunctionCall fctmp |
+ fctmp.getEnclosingFunction() = fnp and
+ fctmp.getFile() = fcp.getFile() and
+ fctmp != fcp and
+ (
+ fctmp = fcp.getAPredecessor+()
+ or
+ not exists(FunctionCall fctmp1 |
+ fctmp1 = fcp and
+ (
+ fctmp.getASuccessor+() = fctmp1 or
+ fctmp.getAPredecessor+() = fctmp1
+ )
+ )
+ )
+ )
+}
+
+/** Holds if the conditions for the wrapper function are met. */
+pragma[inline]
+predicate conditionsForWrapper(FunctionCall fcp, Function fnp) {
+ not exists(ExprCall ectmp | fnp = ectmp.getEnclosingFunction()) and
+ not exists(Loop lp | lp.getEnclosingFunction() = fnp) and
+ not exists(SwitchStmt sw | sw.getEnclosingFunction() = fnp) and
+ not fnp instanceof Operator and
+ // inside the wrapper function there should be checks of arguments or the result,
+ // perhaps by means of passing the latter as an argument to some function
+ (
+ exists(IfStmt ifs |
+ ifs.getEnclosingFunction() = fnp and
+ (
+ globalValueNumber(ifs.getCondition().getAChild*()) = globalValueNumber(fcp.getAnArgument()) and
+ ifs.getASuccessor*() = fcp
+ or
+ ifs.getCondition().getAChild() = fcp
+ )
+ )
+ or
+ exists(FunctionCall fctmp |
+ fctmp.getEnclosingFunction() = fnp and
+ globalValueNumber(fctmp.getAnArgument().getAChild*()) = globalValueNumber(fcp)
+ )
+ ) and
+ // inside the wrapper function there must be a function call to handle the error
+ exists(CallUsedToHandleErrors fctmp |
+ fctmp.getEnclosingFunction() = fnp and
+ forall(int i | i in [0 .. fnp.getNumberOfParameters() - 1] |
+ fnp.getParameter(i).getAnAccess().getTarget() =
+ fcp.getAnArgument().(VariableAccess).getTarget() or
+ fnp.getParameter(i).getType() instanceof Class or
+ fnp.getParameter(i).getType().(ReferenceType).getBaseType() instanceof Class or
+ fnp.getParameter(i).getAnAccess().getTarget() =
+ fctmp.getAnArgument().(VariableAccess).getTarget()
+ )
+ )
+}
+
+from FunctionCall fc, Function fn
+where
+ exists(FunctionCall fctmp |
+ conditionsInsideWrapper(fctmp, fn) and
+ conditionsForWrapper(fctmp, fn) and
+ conditionsOutsideWrapper(fc) and
+ fctmp.getTarget() = fc.getTarget() and
+ fc.getEnclosingFunction() != fn and
+ fc.getEnclosingFunction().getMetrics().getNumberOfCalls() > fn.getMetrics().getNumberOfCalls()
+ )
+select fc, "consider changing the call to $@", fn, fn.getName()
From b6bcf9fa44957c5c09073e75db87eae7cfcc8733 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 16 Sep 2021 19:18:19 +0300
Subject: [PATCH 049/361] Add files via upload
---
.../tests/FindWrapperFunctions.expected | 1 +
.../semmle/tests/FindWrapperFunctions.qlref | 1 +
.../CWE/CWE-1041/semmle/tests/test.cpp | 27 +++++++++++++++++++
3 files changed, 29 insertions(+)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.qlref
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/test.cpp
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected
new file mode 100644
index 00000000000..ab377e4b3b0
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected
@@ -0,0 +1 @@
+| test.cpp:23:3:23:8 | call to fclose | consider changing the call to $@ | test.cpp:9:6:9:13 | myFclose | myFclose |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.qlref
new file mode 100644
index 00000000000..22dae13892f
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/test.cpp
new file mode 100644
index 00000000000..4f862a324e5
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/test.cpp
@@ -0,0 +1,27 @@
+#define NULL (0)
+typedef int FILE;
+FILE *fopen(const char *filename, const char *mode);
+int fclose(FILE *stream);
+extern FILE * fe;
+extern int printf(const char *fmt, ...);
+void exit(int status);
+
+void myFclose(FILE * fmy)
+{
+ int i;
+ if(fmy) {
+ i = fclose(fmy);
+ fmy = NULL;
+ printf("close end is code %d",i);
+ if(i!=0) exit(1);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ fe = fopen("myFile.txt", "wt");
+ fclose(fe); // BAD
+ fe = fopen("myFile.txt", "wt");
+ myFclose(fe); // GOOD
+ return 0;
+}
From a3de94e868aa89c16ef36809f6b1a3446bc50eec Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Mon, 13 Sep 2021 15:50:10 +0100
Subject: [PATCH 050/361] C++: Assign precision and severity; medium for now,
since there are FPs in SAMATE Juliet.
---
cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 123c7437990..26cd2d89e11 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -4,8 +4,8 @@
* cleartext can expose it to an attacker.
* @kind path-problem
* @problem.severity warning
- * @security-severity 7.5 TODO
- * @precision high TODO
+ * @security-severity 7.5
+ * @precision medium
* @id cpp/cleartext-transmission
* @tags security
* external/cwe/cwe-319
From 99167539fb0291805b26ec2f9daefe99a1944992 Mon Sep 17 00:00:00 2001
From: haby0
Date: Fri, 17 Sep 2021 17:29:40 +0800
Subject: [PATCH 051/361] Modify sinks
---
.../semmle/python/frameworks/Log.qll | 36 ++++++++++++++++---
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Log.qll b/python/ql/src/experimental/semmle/python/frameworks/Log.qll
index b05d9e52118..6ffc3529daa 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Log.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Log.qll
@@ -20,7 +20,9 @@ private module log {
* See https://docs.python.org/3/library/logging.html#logger-objects
*/
private class LogOutputMethods extends string {
- LogOutputMethods() { this in ["info", "error", "warn", "warning", "debug", "critical"] }
+ LogOutputMethods() {
+ this in ["info", "error", "warn", "warning", "debug", "critical", "exception", "log"]
+ }
}
/**
@@ -33,7 +35,13 @@ private module log {
this = API::moduleImport("logging").getMember(any(LogOutputMethods m)).getACall()
}
- override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ override DataFlow::Node getAnInput() {
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
+ result = this.getArg(0)
+ or
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
+ result = this.getArg(1)
+ }
}
/**
@@ -51,7 +59,13 @@ private module log {
.getACall()
}
- override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ override DataFlow::Node getAnInput() {
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
+ result = this.getArg(0)
+ or
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
+ result = this.getArg(1)
+ }
}
/**
@@ -68,7 +82,13 @@ private module log {
.getACall()
}
- override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ override DataFlow::Node getAnInput() {
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
+ result = this.getArg(0)
+ or
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
+ result = this.getArg(1)
+ }
}
/**
@@ -87,6 +107,12 @@ private module log {
.getACall()
}
- override DataFlow::Node getAnInput() { result = this.getArg(_) }
+ override DataFlow::Node getAnInput() {
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
+ result = this.getArg(0)
+ or
+ this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
+ result = this.getArg(1)
+ }
}
}
From 90bc138049d1717cab61899cc9d23fdddad1e8a6 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 17 Sep 2021 14:08:34 +0100
Subject: [PATCH 052/361] CPP: Fix QLDoc comments.
---
cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 26cd2d89e11..c130c9a49b6 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -35,7 +35,7 @@ abstract class NetworkSendRecv extends FunctionCall {
/**
* A function call that sends data over a network.
*
- * note: functions such as `read` may be reading from a network source or a file. We could attempt to determine which, and sort results into `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In practice it probably isn't very important which query reports a result as long as its reported exactly once.
+ * note: functions such as `write` may be writing to a network source or a file. We could attempt to determine which, and sort results into `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In practice it usually isn't very important which query reports a result as long as its reported exactly once.
*/
class NetworkSend extends NetworkSendRecv {
NetworkSend() {
@@ -76,6 +76,8 @@ class SensitiveSendRecvConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(NetworkSendRecv transmission |
sink.asExpr() = transmission.getDataExpr() and
+
+ // a zero file descriptor is standard input, which is not interesting for this query.
not exists(Zero zero |
DataFlow::localFlow(DataFlow::exprNode(zero),
DataFlow::exprNode(transmission.getSocketExpr()))
From 51243454c890c3c58c0d9ba767dfec8a030ba2ed Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 17 Sep 2021 15:10:55 +0100
Subject: [PATCH 053/361] C++: Change note.
---
cpp/change-notes/2021-06-10-cleartext-transmission.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 cpp/change-notes/2021-06-10-cleartext-transmission.md
diff --git a/cpp/change-notes/2021-06-10-cleartext-transmission.md b/cpp/change-notes/2021-06-10-cleartext-transmission.md
new file mode 100644
index 00000000000..ce6debf1407
--- /dev/null
+++ b/cpp/change-notes/2021-06-10-cleartext-transmission.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* A new query (`cpp/cleartext-transmission`) has been added. This is similar to the `cpp/cleartext-storage-file`, `cpp/cleartext-storage-buffer` and `cpp/cleartext-storage-database` queries but looks for cases where sensitive information is most likely transmitted over a network.
From e7c82d7370e7a6b7d6d5981a306b87d609373cdc Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 17 Sep 2021 16:14:24 +0100
Subject: [PATCH 054/361] C++: Accept subpaths in tests.
---
.../CWE/CWE-311/semmle/tests/CleartextTransmission.expected | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
index ae2333ba8c9..3534c2c7cad 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-311/semmle/tests/CleartextTransmission.expected
@@ -2,9 +2,11 @@ edges
| test3.cpp:68:21:68:29 | password1 | test3.cpp:70:15:70:17 | ptr |
| test3.cpp:75:15:75:22 | password | test3.cpp:77:15:77:17 | ptr |
| test3.cpp:106:20:106:25 | buffer | test3.cpp:108:14:108:19 | buffer |
+| test3.cpp:111:28:111:33 | buffer | test3.cpp:113:9:113:14 | buffer |
| test3.cpp:120:9:120:23 | global_password | test3.cpp:138:16:138:29 | call to get_global_str |
| test3.cpp:128:11:128:18 | password | test3.cpp:106:20:106:25 | buffer |
| test3.cpp:132:21:132:22 | call to id | test3.cpp:134:15:134:17 | ptr |
+| test3.cpp:132:24:132:32 | password1 | test3.cpp:111:28:111:33 | buffer |
| test3.cpp:132:24:132:32 | password1 | test3.cpp:132:21:132:22 | call to id |
| test3.cpp:138:16:138:29 | call to get_global_str | test3.cpp:140:15:140:18 | data |
| test3.cpp:151:19:151:26 | password | test3.cpp:153:15:153:20 | buffer |
@@ -20,6 +22,8 @@ nodes
| test3.cpp:95:12:95:19 | password | semmle.label | password |
| test3.cpp:106:20:106:25 | buffer | semmle.label | buffer |
| test3.cpp:108:14:108:19 | buffer | semmle.label | buffer |
+| test3.cpp:111:28:111:33 | buffer | semmle.label | buffer |
+| test3.cpp:113:9:113:14 | buffer | semmle.label | buffer |
| test3.cpp:120:9:120:23 | global_password | semmle.label | global_password |
| test3.cpp:128:11:128:18 | password | semmle.label | password |
| test3.cpp:132:21:132:22 | call to id | semmle.label | call to id |
@@ -29,6 +33,8 @@ nodes
| test3.cpp:140:15:140:18 | data | semmle.label | data |
| test3.cpp:151:19:151:26 | password | semmle.label | password |
| test3.cpp:153:15:153:20 | buffer | semmle.label | buffer |
+subpaths
+| test3.cpp:132:24:132:32 | password1 | test3.cpp:111:28:111:33 | buffer | test3.cpp:113:9:113:14 | buffer | test3.cpp:132:21:132:22 | call to id |
#select
| test3.cpp:20:3:20:6 | call to send | test3.cpp:20:15:20:23 | password1 | test3.cpp:20:15:20:23 | password1 | This operation transmits 'password1', which may contain unencrypted sensitive data from $@ | test3.cpp:20:15:20:23 | password1 | password1 |
| test3.cpp:24:3:24:6 | call to send | test3.cpp:24:15:24:23 | password2 | test3.cpp:24:15:24:23 | password2 | This operation transmits 'password2', which may contain unencrypted sensitive data from $@ | test3.cpp:24:15:24:23 | password2 | password2 |
From 237a7d34b8c45965d5c8c819b88be3bfdd1e3177 Mon Sep 17 00:00:00 2001
From: Jonas Jensen
Date: Tue, 21 Sep 2021 11:31:13 +0200
Subject: [PATCH 055/361] C++: Exclusion rules for system macros
Unwanted results were reported for our JPL Rule 24 queries. Including
system headers with complex macros could lead to unpredictable alerts
from these rules.
---
.../semmle/code/cpp/commons/Exclusions.qll | 44 +++++++++++++++++--
.../LOC-4/Rule 24/MultipleStmtsPerLine.ql | 6 ++-
.../LOC-4/Rule 24/MultipleVarDeclsPerLine.ql | 6 ++-
3 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll b/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll
index a0dfea20046..6ace4aa49e6 100644
--- a/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll
+++ b/cpp/ql/lib/semmle/code/cpp/commons/Exclusions.qll
@@ -81,8 +81,8 @@ predicate functionContainsPreprocCode(Function f) {
}
/**
- * Holds if `e` is completely or partially from a macro definition, as opposed
- * to being passed in as an argument.
+ * Holds if `e` is completely or partially from a macro invocation `mi`, as
+ * opposed to being passed in as an argument.
*
* In the following example, the call to `f` is from a macro definition,
* while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
@@ -93,8 +93,8 @@ predicate functionContainsPreprocCode(Function f) {
* M(y + 1);
* ```
*/
-predicate isFromMacroDefinition(Element e) {
- exists(MacroInvocation mi, Location eLocation, Location miLocation |
+private predicate isFromMacroInvocation(Element e, MacroInvocation mi) {
+ exists(Location eLocation, Location miLocation |
mi.getAnExpandedElement() = e and
eLocation = e.getLocation() and
miLocation = mi.getLocation() and
@@ -109,3 +109,39 @@ predicate isFromMacroDefinition(Element e) {
eLocation.getEndColumn() >= miLocation.getEndColumn()
)
}
+
+/**
+ * Holds if `e` is completely or partially from a macro definition, as opposed
+ * to being passed in as an argument.
+ *
+ * In the following example, the call to `f` is from a macro definition,
+ * while `y`, `+`, `1`, and `;` are not. This assumes that no identifier apart
+ * from `M` refers to a macro.
+ * ```
+ * #define M(x) f(x)
+ * ...
+ * M(y + 1);
+ * ```
+ */
+predicate isFromMacroDefinition(Element e) {
+ isFromMacroInvocation(e, _)
+}
+
+/**
+ * Holds if `e` is completely or partially from a _system macro_ definition, as
+ * opposed to being passed in as an argument. A system macro is a macro whose
+ * definition is outside the source directory of the database.
+ *
+ * If the system macro is invoked through a non-system macro, then this
+ * predicate does not hold. That's a limitation of how macros are represented
+ * in the database.
+ *
+ * See also `isFromMacroDefinition`.
+ */
+predicate isFromSystemMacroDefinition(Element e) {
+ exists(MacroInvocation mi |
+ isFromMacroInvocation(e, mi) and
+ // Has no relative path in the database, meaning it's a system file.
+ not exists(mi.getMacro().getFile().getRelativePath())
+ )
+}
diff --git a/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleStmtsPerLine.ql b/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleStmtsPerLine.ql
index 5eccd2c5cad..c99c3de470e 100644
--- a/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleStmtsPerLine.ql
+++ b/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleStmtsPerLine.ql
@@ -10,6 +10,7 @@
*/
import cpp
+import semmle.code.cpp.commons.Exclusions
class OneLineStmt extends Stmt {
OneLineStmt() {
@@ -34,5 +35,8 @@ where
other.onLine(f, line) and toMin = other.getLocation().getStartColumn()
|
toMin
- )
+ ) and
+ // Exclude statements that are from invocations of system-header macros.
+ // Example: FD_ISSET from glibc.
+ not isFromSystemMacroDefinition(o)
select o, "This line contains " + cnt + " statements; only one is allowed."
diff --git a/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleVarDeclsPerLine.ql b/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleVarDeclsPerLine.ql
index fcdb2471b76..41413353346 100644
--- a/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleVarDeclsPerLine.ql
+++ b/cpp/ql/src/JPL_C/LOC-4/Rule 24/MultipleVarDeclsPerLine.ql
@@ -10,11 +10,15 @@
*/
import cpp
+import semmle.code.cpp.commons.Exclusions
from DeclStmt d
where
exists(Variable v1, Variable v2 | v1 = d.getADeclaration() and v2 = d.getADeclaration() |
v1 != v2 and
v1.getLocation().getStartLine() = v2.getLocation().getStartLine()
- )
+ ) and
+ // Exclude declarations that are from invocations of system-header macros.
+ // Example: FD_ZERO from glibc.
+ not isFromSystemMacroDefinition(d)
select d, "Multiple variable declarations on the same line."
From a055c86c4f9618fc6a368696a1f3562e18c18db7 Mon Sep 17 00:00:00 2001
From: Jonas Jensen
Date: Tue, 21 Sep 2021 11:58:04 +0200
Subject: [PATCH 056/361] C++: change note
---
cpp/change-notes/2021-09-21-jpl24.md | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 cpp/change-notes/2021-09-21-jpl24.md
diff --git a/cpp/change-notes/2021-09-21-jpl24.md b/cpp/change-notes/2021-09-21-jpl24.md
new file mode 100644
index 00000000000..51af4db8b31
--- /dev/null
+++ b/cpp/change-notes/2021-09-21-jpl24.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* The opt-in queries for JPL C coding standards Rule 24 now exclude macros from
+ system headers.
From 88a257fcdc63c2504f3fbd697f401beade8f28d7 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Tue, 21 Sep 2021 20:32:08 +0300
Subject: [PATCH 057/361] Apply suggestions from code review
Co-authored-by: Mathias Vorreiter Pedersen
---
.../Security/CWE/CWE-1041/FindWrapperFunctions.ql | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
index 1baafd4a171..1df7babc862 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
@@ -1,8 +1,8 @@
/**
- * @name Find Wrapper Functions
- * @description --Finding for function calls for which wrapper functions exist.
+ * @name Missed opportunity to call wrapper function
+ * @description If a wrapper function is defined for a given function, any call to the given function should be via the wrapper function.
* @kind problem
- * @id cpp/find-wrapper-functions
+ * @id cpp/call-to-function-without-wrapper
* @problem.severity warning
* @precision medium
* @tags correctness
@@ -137,4 +137,4 @@ where
fc.getEnclosingFunction() != fn and
fc.getEnclosingFunction().getMetrics().getNumberOfCalls() > fn.getMetrics().getNumberOfCalls()
)
-select fc, "consider changing the call to $@", fn, fn.getName()
+select fc, "Consider changing the call to $@", fn, fn.getName()
From 6c07a3e260724df620467f3e05045fab56e9441b Mon Sep 17 00:00:00 2001
From: haby0
Date: Wed, 22 Sep 2021 18:50:58 +0800
Subject: [PATCH 058/361] Apply @yoff's suggestion
---
.../semmle/python/frameworks/Log.qll | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/Log.qll b/python/ql/src/experimental/semmle/python/frameworks/Log.qll
index 6ffc3529daa..489bb859b1c 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/Log.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/Log.qll
@@ -37,10 +37,10 @@ private module log {
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
- result = this.getArg(0)
+ result in [this.getArg(_), this.getArgByName(_) ] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
- result = this.getArg(1)
+ result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
@@ -61,10 +61,10 @@ private module log {
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
- result = this.getArg(0)
+ result in [this.getArg(_), this.getArgByName(_) ] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
- result = this.getArg(1)
+ result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
@@ -84,10 +84,10 @@ private module log {
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
- result = this.getArg(0)
+ result in [this.getArg(_), this.getArgByName(_) ] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
- result = this.getArg(1)
+ result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
@@ -109,10 +109,10 @@ private module log {
override DataFlow::Node getAnInput() {
this.getFunction().(DataFlow::AttrRead).getAttributeName() != "log" and
- result = this.getArg(0)
+ result in [this.getArg(_), this.getArgByName(_) ] // this includes the arg named "msg"
or
this.getFunction().(DataFlow::AttrRead).getAttributeName() = "log" and
- result = this.getArg(1)
+ result in [this.getArg(any(int i | i > 0)), this.getArgByName(any(string s | s != "level"))]
}
}
}
From 805d1d170c196c4b989e3153638f364a8b3d34a1 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 22 Sep 2021 17:14:29 +0200
Subject: [PATCH 059/361] do not filter away regular expressions with
lookbehinds
---
.../lib/semmle/javascript/security/performance/ReDoSUtil.qll | 2 --
.../Performance/ReDoS/PolynomialBackTracking.expected | 5 +++++
.../ql/test/query-tests/Performance/ReDoS/ReDoS.expected | 3 +++
javascript/ql/test/query-tests/Performance/ReDoS/tst.js | 3 +++
.../ql/lib/semmle/python/security/performance/ReDoSUtil.qll | 2 --
5 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll b/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll
index 65431d7179e..12b7559615d 100644
--- a/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll
+++ b/javascript/ql/lib/semmle/javascript/security/performance/ReDoSUtil.qll
@@ -139,8 +139,6 @@ class RegExpRoot extends RegExpTerm {
predicate isRelevant() {
// there is at least one repetition
getRoot(any(InfiniteRepetitionQuantifier q)) = this and
- // there are no lookbehinds
- not exists(RegExpLookbehind lbh | getRoot(lbh) = this) and
// is actually used as a RegExp
isUsedAsRegExp() and
// not excluded for library specific reasons
diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected
index a38b8cb37fe..dbf74f78d8f 100644
--- a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected
+++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected
@@ -362,6 +362,7 @@
| regexplib/uri.js:29:2:29:45 | ((http\\:\\/\\/\|https\\:\\/\\/\|ftp\\:\\/\\/)\|(www.))+ | Strings with many repetitions of 'wwwa' can start matching anywhere after the start of the preceeding ((http\\:\\/\\/\|https\\:\\/\\/\|ftp\\:\\/\\/)\|(www.))+(([a-zA-Z0-9\\.-]+\\.[a-zA-Z]{2,4})\|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(\\/[a-zA-Z0-9%:/-_\\?\\.'~]*)? |
| regexplib/uri.js:29:48:29:62 | [a-zA-Z0-9\\.-]+ | Strings starting with 'wwwa' and with many repetitions of 'www--' can start matching anywhere after the start of the preceeding ((http\\:\\/\\/\|https\\:\\/\\/\|ftp\\:\\/\\/)\|(www.))+ |
| regexplib/uri.js:31:65:31:69 | [^<]+ | Strings starting with 'href=! >' and with many repetitions of 'href=! >;' can start matching anywhere after the start of the preceeding href\\s*=\\s*(?:(?:\\"(?[^\\"]*)\\")\|(?[^\\s*] ))>(?[^<]+)<\\/\\w> |
+| regexplib/uri.js:34:3:34:9 | [^\\=&]+ | Strings with many repetitions of '%' can start matching anywhere after the start of the preceeding ([^\\=&]+)(?a' can start matching anywhere after the start of the preceeding .*? |
| regexplib/uri.js:41:16:41:31 | [a-zA-Z0-9\\-\\.]+ | Strings starting with '0' and with many repetitions of '00' can start matching anywhere after the start of the preceeding [a-zA-Z0-9]+ |
+| regexplib/uri.js:44:2:44:4 | .*? | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding .*?$(?\|]+ | Strings starting with 'A:\\\\!.' and with many repetitions of '!.' can start matching anywhere after the start of the preceeding [^\\/\\\\:*?"<>\|]+ |
+| regexplib/uri.js:73:2:73:4 | .*? | Strings with many repetitions of 'a' can start matching anywhere after the start of the preceeding .*?$(?
Date: Thu, 23 Sep 2021 12:53:16 +0300
Subject: [PATCH 060/361] Update FindWrapperFunctions.expected
---
.../CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected
index ab377e4b3b0..b8152734e2d 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-1041/semmle/tests/FindWrapperFunctions.expected
@@ -1 +1 @@
-| test.cpp:23:3:23:8 | call to fclose | consider changing the call to $@ | test.cpp:9:6:9:13 | myFclose | myFclose |
+| test.cpp:23:3:23:8 | call to fclose | Consider changing the call to $@ | test.cpp:9:6:9:13 | myFclose | myFclose |
From 13741ba1374f13a033dcb233d184bf67b00a2bad Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 23 Sep 2021 12:55:03 +0300
Subject: [PATCH 061/361] Update FindWrapperFunctions.ql
---
.../Security/CWE/CWE-1041/FindWrapperFunctions.ql | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
index 1df7babc862..5b609697b73 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-1041/FindWrapperFunctions.ql
@@ -6,6 +6,7 @@
* @problem.severity warning
* @precision medium
* @tags correctness
+ * maintainability
* security
* external/cwe/cwe-1041
*/
@@ -48,7 +49,7 @@ predicate conditionsOutsideWrapper(FunctionCall fcp) {
not exists(ExprCall ectmp | fcp = ectmp.getAnArgument().getAChild*())
}
-/** Holds if the conditions for a call within the wrapper function are met. */
+/** Held if the conditions for calling `fcp` inside the `fnp` wrapper function are met. */
pragma[inline]
predicate conditionsInsideWrapper(FunctionCall fcp, Function fnp) {
not exists(FunctionCall fctmp2 |
@@ -60,7 +61,7 @@ predicate conditionsInsideWrapper(FunctionCall fcp, Function fnp) {
fnp.getNumberOfParameters() > 0 and
// the call arguments must be passed through the arguments of the wrapper function
forall(int i | i in [0 .. fcp.getNumberOfArguments() - 1] |
- fcp.getArgument(i).(VariableAccess).getTarget() = fnp.getAParameter().getAnAccess().getTarget()
+ globalValueNumber(fcp.getArgument(i)) = globalValueNumber(fnp.getAParameter().getAnAccess())
) and
// there should be no more than one required call inside the wrapper function
not exists(FunctionCall fctmp |
@@ -119,8 +120,8 @@ predicate conditionsForWrapper(FunctionCall fcp, Function fnp) {
forall(int i | i in [0 .. fnp.getNumberOfParameters() - 1] |
fnp.getParameter(i).getAnAccess().getTarget() =
fcp.getAnArgument().(VariableAccess).getTarget() or
- fnp.getParameter(i).getType() instanceof Class or
- fnp.getParameter(i).getType().(ReferenceType).getBaseType() instanceof Class or
+ fnp.getParameter(i).getUnspecifiedType() instanceof Class or
+ fnp.getParameter(i).getUnspecifiedType().(ReferenceType).getBaseType() instanceof Class or
fnp.getParameter(i).getAnAccess().getTarget() =
fctmp.getAnArgument().(VariableAccess).getTarget()
)
From 0919042692707a2a56d180d3f2bb6eea984434fc Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Thu, 23 Sep 2021 12:03:45 +0100
Subject: [PATCH 062/361] Model Bundle and Intent extra methods
---
.../code/java/dataflow/ExternalFlow.qll | 1 +
.../code/java/frameworks/android/Intent.qll | 124 ++++++++++++++++++
2 files changed, 125 insertions(+)
diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
index bce80a3ee08..679f8704bc9 100644
--- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
@@ -78,6 +78,7 @@ private import FlowSummary
private module Frameworks {
private import internal.ContainerFlow
private import semmle.code.java.frameworks.android.XssSinks
+ private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.ApacheHttp
private import semmle.code.java.frameworks.apache.Collections
private import semmle.code.java.frameworks.apache.Lang
diff --git a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
index 92c49f3101a..edb981cff8e 100644
--- a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
@@ -1,5 +1,6 @@
import java
import semmle.code.java.dataflow.FlowSteps
+import semmle.code.java.dataflow.ExternalFlow
class TypeIntent extends Class {
TypeIntent() { hasQualifiedName("android.content", "Intent") }
@@ -52,3 +53,126 @@ class BundleGetterMethod extends Method, TaintPreservingCallable {
override predicate returnsTaintFrom(int arg) { arg = -1 }
}
+
+private class IntentBundleFlowSteps extends SummaryModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ //"namespace;type;subtypes;name;signature;ext;input;output;kind"
+ "android.os;BaseBundle;true;get;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;BaseBundle;true;getString;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;BaseBundle;true;getString;(String,String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;BaseBundle;true;getString;(String,String);;Argument[1];ReturnValue;value",
+ "android.os;BaseBundle;true;getStringArray;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;BaseBundle;true;keySet;();;MapKey of Argument[-1];Element of ReturnValue;value",
+ "android.os;BaseBundle;true;putAll;(PersistableBundle);;MapKey of Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putAll;(PersistableBundle);;MapValue of Argument[0];MapValue of Argument[-1];value",
+ "android.os;BaseBundle;true;putBoolean;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putBooleanArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putDouble;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putDoubleArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putInt;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putIntArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putLong;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putLongArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putString;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putString;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;BaseBundle;true;putStringArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;BaseBundle;true;putStringArray;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;getBinder;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getBundle;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getByteArray;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getCharArray;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getCharSequence;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getCharSequence;(String,CharSequence);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getCharSequence;(String,CharSequence);;Argument[1];ReturnValue;value",
+ "android.os;Bundle;true;getCharSequenceArray;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getCharSequenceArrayList;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getParcelable;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getParcelableArray;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getParcelableArrayList;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getSerializable;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getSparseParcelableArray;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;getStringArrayList;(String);;MapValue of Argument[-1];ReturnValue;value",
+ "android.os;Bundle;true;putAll;(Bundle);;MapKey of Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putAll;(Bundle);;MapValue of Argument[0];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putBinder;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putBinder;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putBundle;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putBundle;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putByte;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putByteArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putByteArray;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putChar;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putCharArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putCharArray;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putCharSequence;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putCharSequence;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putCharSequenceArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putCharSequenceArray;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putCharSequenceArrayList;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putCharSequenceArrayList;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putFloat;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putFloatArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putIntegerArrayList;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putParcelable;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putParcelable;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putParcelableArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putParcelableArray;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putParcelableArrayList;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putParcelableArrayList;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putSerializable;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putSerializable;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putShort;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putShortArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putSize;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putSizeF;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putSparceParcelableArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putSparseParcelableArray;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;putStringArrayList;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putStringArrayList;;;Argument[1];MapValue of Argument[-1];value",
+ "android.os;Bundle;true;readFromParcel;;;Argument[0];MapKey of Argument[-1];taint",
+ "android.os;Bundle;true;readFromParcel;;;Argument[0];MapValue of Argument[-1];taint",
+ "android.content;Intent;true;getExtras;();;SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getBundleExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getByteArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getCharArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getCharSequenceArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getCharSequenceArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getCharSequenceExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getParcelableArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getParcelableArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getParcelableExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getSerializableExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getStringArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getStringArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;getStringExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putIntegerArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putIntegerArrayListExtra;;;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putParcelableArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putParcelableArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putParcelableArrayListExtra;;;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putStringArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putStringArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putStringArrayListExtra;;;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putExtras;(Bundle);;MapKey of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putExtras;(Bundle);;MapValue of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putExtras;(Bundle);;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;putExtras;(Intent);;MapKey of SyntheticField[android.content.Intent.extras] of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putExtras;(Intent);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;putExtras;(Intent);;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;replaceExtras;(Bundle);;MapKey of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;replaceExtras;(Bundle);;MapValue of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;replaceExtras;(Bundle);;Argument[-1];ReturnValue;value",
+ "android.content;Intent;true;replaceExtras;(Intent);;MapKey of SyntheticField[android.content.Intent.extras] of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;replaceExtras;(Intent);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value",
+ "android.content;Intent;true;replaceExtras;(Intent);;Argument[-1];ReturnValue;value"
+ ]
+ }
+}
From 81adb7dd2a73b4e1fd3ac54bf10e9330de258b7e Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 23 Sep 2021 15:28:05 +0200
Subject: [PATCH 063/361] Python: Add tests for `os.path`-functions
---
.../library-tests/frameworks/stdlib/FileSystemAccess.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py b/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py
index bcf6589ef85..ccc2dd76f2c 100644
--- a/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py
+++ b/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py
@@ -27,3 +27,10 @@ def through_function(open_file):
open_file.write("foo") # $ fileWriteData="foo" getAPathArgument="path"
through_function(f)
+
+from os import path
+path.exists("filepath") # $ MISSING: getAPathArgument="filepath"
+path.isfile("filepath") # $ MISSING: getAPathArgument="filepath"
+path.isdir("filepath") # $ MISSING: getAPathArgument="filepath"
+path.islink("filepath") # $ MISSING: getAPathArgument="filepath"
+path.ismount("filepath") # $ MISSING: getAPathArgument="filepath"
From f2fbeed4903522dcb4f415c18504672006ad46de Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 23 Sep 2021 15:28:33 +0200
Subject: [PATCH 064/361] Python: Model `os.path`-functions
---
.../lib/semmle/python/frameworks/Stdlib.qll | 26 +++++++++++++++++++
.../frameworks/stdlib/FileSystemAccess.py | 10 +++----
2 files changed, 31 insertions(+), 5 deletions(-)
diff --git a/python/ql/lib/semmle/python/frameworks/Stdlib.qll b/python/ql/lib/semmle/python/frameworks/Stdlib.qll
index 539f0dcabb0..46f0349de77 100644
--- a/python/ql/lib/semmle/python/frameworks/Stdlib.qll
+++ b/python/ql/lib/semmle/python/frameworks/Stdlib.qll
@@ -195,6 +195,32 @@ private module StdlibPrivate {
}
}
+ /**
+ * A call to `os.path.exists` or `os.path.lexists` will check if a file exists on the file system.
+ * The `os.path` module offers e number of methods for checking if a file exists and/or has certain
+ * properties, leading to a file system access.
+ * (Although, on some platforms, the check may return `false` due to missing permissions.)
+ * See:
+ * - https://docs.python.org/3/library/os.path.html#os.path.exists
+ * - https://docs.python.org/3/library/os.path.html#os.path.lexists
+ * - https://docs.python.org/3/library/os.path.html#os.path.isfile
+ * - https://docs.python.org/3/library/os.path.html#os.path.isdir
+ * - https://docs.python.org/3/library/os.path.html#os.path.islink
+ * - https://docs.python.org/3/library/os.path.html#os.path.ismount
+ */
+ private class OsPathProbingCall extends FileSystemAccess::Range, DataFlow::CallCfgNode {
+ OsPathProbingCall() {
+ this =
+ os::path()
+ .getMember(["exists", "lexists", "isfile", "isdir", "islink", "ismount"])
+ .getACall()
+ }
+
+ override DataFlow::Node getAPathArgument() {
+ result in [this.getArg(0), this.getArgByName("path")]
+ }
+ }
+
/**
* A call to `os.path.normpath`.
* See https://docs.python.org/3/library/os.path.html#os.path.normpath
diff --git a/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py b/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py
index ccc2dd76f2c..64f8ad5010d 100644
--- a/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py
+++ b/python/ql/test/library-tests/frameworks/stdlib/FileSystemAccess.py
@@ -29,8 +29,8 @@ def through_function(open_file):
through_function(f)
from os import path
-path.exists("filepath") # $ MISSING: getAPathArgument="filepath"
-path.isfile("filepath") # $ MISSING: getAPathArgument="filepath"
-path.isdir("filepath") # $ MISSING: getAPathArgument="filepath"
-path.islink("filepath") # $ MISSING: getAPathArgument="filepath"
-path.ismount("filepath") # $ MISSING: getAPathArgument="filepath"
+path.exists("filepath") # $ getAPathArgument="filepath"
+path.isfile("filepath") # $ getAPathArgument="filepath"
+path.isdir("filepath") # $ getAPathArgument="filepath"
+path.islink("filepath") # $ getAPathArgument="filepath"
+path.ismount("filepath") # $ getAPathArgument="filepath"
From 9b969e15fc82e878ade65c93805792af519ccb04 Mon Sep 17 00:00:00 2001
From: haby0
Date: Fri, 24 Sep 2021 12:56:10 +0800
Subject: [PATCH 065/361] Modify according to @yoff suggestion
---
.../ClientSuppliedIpUsedInSecurityCheck.ql | 4 +-
...ClientSuppliedIpUsedInSecurityCheckLib.qll | 85 ++++++++++---------
2 files changed, 46 insertions(+), 43 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
index 1c96a10d531..270a0444cfb 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.ql
@@ -27,9 +27,7 @@ class ClientSuppliedIpUsedInSecurityCheckConfig extends TaintTracking::Configura
source instanceof ClientSuppliedIpUsedInSecurityCheck
}
- override predicate isSink(DataFlow::Node sink) {
- sink instanceof ClientSuppliedIpUsedInSecurityCheckSink
- }
+ override predicate isSink(DataFlow::Node sink) { sink instanceof PossibleSecurityCheck }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallCfgNode ccn |
diff --git a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
index 82da4f5b46f..97e9bbfd457 100644
--- a/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
+++ b/python/ql/src/experimental/Security/CWE-348/ClientSuppliedIpUsedInSecurityCheckLib.qll
@@ -14,55 +14,60 @@ abstract class ClientSuppliedIpUsedInSecurityCheck extends DataFlow::CallCfgNode
private class FlaskClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck {
FlaskClientSuppliedIpUsedInSecurityCheck() {
- this =
- API::moduleImport("flask")
- .getMember("request")
- .getMember("headers")
- .getMember(["get", "get_all", "getlist"])
- .getACall() and
- this.getArg(0).asCfgNode().getNode().(StrConst).getText().toLowerCase() =
- clientIpParameterName()
+ exists(RemoteFlowSource rfs, DataFlow::AttrRead get |
+ rfs.getSourceType() = "flask.request" and this.getFunction() = get
+ |
+ // `get` is a call to request.headers.get or request.headers.get_all or request.headers.getlist
+ // request.headers
+ get.getObject()
+ .(DataFlow::AttrRead)
+ // request
+ .getObject()
+ .getALocalSource() = rfs and
+ get.getAttributeName() in ["get", "get_all", "getlist"] and
+ get.getObject().(DataFlow::AttrRead).getAttributeName() = "headers" and
+ this.getArg(0).asExpr().(StrConst).getText().toLowerCase() = clientIpParameterName()
+ )
}
}
private class DjangoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck {
DjangoClientSuppliedIpUsedInSecurityCheck() {
- exists(RemoteFlowSource rfs, DataFlow::LocalSourceNode lsn |
- rfs.getSourceType() = "django.http.request.HttpRequest" and rfs.asCfgNode() = lsn.asCfgNode()
+ exists(RemoteFlowSource rfs, DataFlow::AttrRead get |
+ rfs.getSourceType() = "django.http.request.HttpRequest" and this.getFunction() = get
|
- lsn.flowsTo(DataFlow::exprNode(this.getFunction()
- .asExpr()
- .(Attribute)
- .getObject()
- .(Attribute)
- .getObject())) and
- this.getFunction().asExpr().(Attribute).getName() = "get" and
- this.getFunction().asExpr().(Attribute).getObject().(Attribute).getName() in [
- "headers", "META"
- ] and
- this.getArg(0).asCfgNode().getNode().(StrConst).getText().toLowerCase() =
- clientIpParameterName()
+ // `get` is a call to request.headers.get or request.META.get
+ // request.headers
+ get.getObject()
+ .(DataFlow::AttrRead)
+ // request
+ .getObject()
+ .getALocalSource() = rfs and
+ get.getAttributeName() = "get" and
+ get.getObject().(DataFlow::AttrRead).getAttributeName() in ["headers", "META"] and
+ this.getArg(0).asExpr().(StrConst).getText().toLowerCase() = clientIpParameterName()
)
}
}
private class TornadoClientSuppliedIpUsedInSecurityCheck extends ClientSuppliedIpUsedInSecurityCheck {
TornadoClientSuppliedIpUsedInSecurityCheck() {
- exists(RemoteFlowSource rfs, DataFlow::LocalSourceNode lsn |
- rfs.getSourceType() = "tornado.web.RequestHandler" and rfs.asCfgNode() = lsn.asCfgNode()
+ exists(RemoteFlowSource rfs, DataFlow::AttrRead get |
+ rfs.getSourceType() = "tornado.web.RequestHandler" and this.getFunction() = get
|
- lsn.flowsTo(DataFlow::exprNode(this.getFunction()
- .asExpr()
- .(Attribute)
- .getObject()
- .(Attribute)
- .getObject()
- .(Attribute)
- .getObject())) and
- this.getFunction().asExpr().(Attribute).getName() in ["get", "get_list"] and
- this.getFunction().asExpr().(Attribute).getObject().(Attribute).getName() = "headers" and
- this.getArg(0).asCfgNode().getNode().(StrConst).getText().toLowerCase() =
- clientIpParameterName()
+ // `get` is a call to `rfs`.request.headers.get
+ // `rfs`.request.headers
+ get.getObject()
+ .(DataFlow::AttrRead)
+ // `rfs`.request
+ .getObject()
+ .(DataFlow::AttrRead)
+ // `rfs`
+ .getObject()
+ .getALocalSource() = rfs and
+ get.getAttributeName() in ["get", "get_list"] and
+ get.getObject().(DataFlow::AttrRead).getAttributeName() = "headers" and
+ this.getArg(0).asExpr().(StrConst).getText().toLowerCase() = clientIpParameterName()
)
}
}
@@ -77,11 +82,11 @@ private string clientIpParameterName() {
}
/** A data flow sink for ip address forgery vulnerabilities. */
-abstract class ClientSuppliedIpUsedInSecurityCheckSink extends DataFlow::Node { }
+abstract class PossibleSecurityCheck extends DataFlow::Node { }
/** A data flow sink for sql operation. */
-private class SqlOperationSink extends ClientSuppliedIpUsedInSecurityCheckSink {
- SqlOperationSink() { this = any(SqlExecution e).getSql() }
+private class SqlOperationAsSecurityCheck extends PossibleSecurityCheck {
+ SqlOperationAsSecurityCheck() { this = any(SqlExecution e).getSql() }
}
/**
@@ -90,7 +95,7 @@ private class SqlOperationSink extends ClientSuppliedIpUsedInSecurityCheckSink {
* For example: `if not ipAddr.startswith('192.168.') : ...` determine whether the client ip starts
* with `192.168.`, and the program can be deceived by forging the ip address.
*/
-private class CompareSink extends ClientSuppliedIpUsedInSecurityCheckSink {
+private class CompareSink extends PossibleSecurityCheck {
CompareSink() {
exists(Call call |
call.getFunc().(Attribute).getName() = "startswith" and
From 49f8f463541d22b9fe541e384218a18ff7243239 Mon Sep 17 00:00:00 2001
From: alexet
Date: Fri, 17 Sep 2021 18:15:47 +0100
Subject: [PATCH 066/361] Java: Cache params string computation.
---
java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll | 1 +
1 file changed, 1 insertion(+)
diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
index bce80a3ee08..9c46291c58f 100644
--- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
@@ -638,6 +638,7 @@ private string paramsStringPart(Callable c, int i) {
* Returns the empty string if the callable has no parameters.
* Parameter types are represented by their type erasure.
*/
+cached
string paramsString(Callable c) { result = concat(int i | | paramsStringPart(c, i) order by i) }
private Element interpretElement0(
From 9f59bc8f7bccbb298bd84a0a5682580c6e3d788c Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 24 Sep 2021 14:35:10 +0100
Subject: [PATCH 067/361] C++: Naive translation to use RemoteFlow*Function.
---
.../CWE/CWE-311/CleartextTransmission.ql | 34 ++++++++++++-------
1 file changed, 21 insertions(+), 13 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index c130c9a49b6..70d0b472d66 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -14,6 +14,7 @@
import cpp
import semmle.code.cpp.security.SensitiveExprs
import semmle.code.cpp.dataflow.TaintTracking
+import semmle.code.cpp.models.interfaces.FlowSource
import DataFlow::PathGraph
/**
@@ -38,30 +39,38 @@ abstract class NetworkSendRecv extends FunctionCall {
* note: functions such as `write` may be writing to a network source or a file. We could attempt to determine which, and sort results into `cpp/cleartext-transmission` and perhaps `cpp/cleartext-storage-file`. In practice it usually isn't very important which query reports a result as long as its reported exactly once.
*/
class NetworkSend extends NetworkSendRecv {
- NetworkSend() {
- this.getTarget()
- .hasGlobalName(["send", "sendto", "sendmsg", "write", "writev", "pwritev", "pwritev2"])
- }
+ RemoteFlowSinkFunction target;
+
+ NetworkSend() { target = this.getTarget() }
override Expr getSocketExpr() { result = this.getArgument(0) }
- override Expr getDataExpr() { result = this.getArgument(1) }
+ override Expr getDataExpr() {
+ exists(FunctionInput input, int arg |
+ target.hasRemoteFlowSink(input, _) and
+ input.isParameterDeref(arg) and
+ result = this.getArgument(arg)
+ )
+ }
}
/**
* A function call that receives data over a network.
*/
class NetworkRecv extends NetworkSendRecv {
- NetworkRecv() {
- this.getTarget()
- .hasGlobalName([
- "recv", "recvfrom", "recvmsg", "read", "pread", "readv", "preadv", "preadv2"
- ])
- }
+ RemoteFlowSourceFunction target;
+
+ NetworkRecv() { target = this.getTarget() }
override Expr getSocketExpr() { result = this.getArgument(0) }
- override Expr getDataExpr() { result = this.getArgument(1) }
+ override Expr getDataExpr() {
+ exists(FunctionOutput output, int arg |
+ target.hasRemoteFlowSource(output, _) and
+ output.isParameterDeref(arg) and
+ result = this.getArgument(arg)
+ )
+ }
}
/**
@@ -76,7 +85,6 @@ class SensitiveSendRecvConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(NetworkSendRecv transmission |
sink.asExpr() = transmission.getDataExpr() and
-
// a zero file descriptor is standard input, which is not interesting for this query.
not exists(Zero zero |
DataFlow::localFlow(DataFlow::exprNode(zero),
From 6901d9d9c254c9c825270f6052bd9922db751696 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 24 Sep 2021 14:55:28 +0100
Subject: [PATCH 068/361] C++: Add and use getRemoteSocket predicates.
---
.../code/cpp/models/implementations/Recv.qll | 4 ++++
.../code/cpp/models/implementations/Send.qll | 4 ++++
.../code/cpp/models/interfaces/FlowSource.qll | 12 ++++++++++++
.../CWE/CWE-311/CleartextTransmission.ql | 18 +++++++++++++++---
4 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/cpp/ql/lib/semmle/code/cpp/models/implementations/Recv.qll b/cpp/ql/lib/semmle/code/cpp/models/implementations/Recv.qll
index 691ba528f42..de4d470ce1f 100644
--- a/cpp/ql/lib/semmle/code/cpp/models/implementations/Recv.qll
+++ b/cpp/ql/lib/semmle/code/cpp/models/implementations/Recv.qll
@@ -85,4 +85,8 @@ private class Recv extends AliasFunction, ArrayFunction, SideEffectFunction,
) and
description = "Buffer read by " + this.getName()
}
+
+ override predicate hasSocketInput(FunctionInput input) {
+ input.isParameter(0)
+ }
}
diff --git a/cpp/ql/lib/semmle/code/cpp/models/implementations/Send.qll b/cpp/ql/lib/semmle/code/cpp/models/implementations/Send.qll
index 6086bc7748f..e747d8182b7 100644
--- a/cpp/ql/lib/semmle/code/cpp/models/implementations/Send.qll
+++ b/cpp/ql/lib/semmle/code/cpp/models/implementations/Send.qll
@@ -60,4 +60,8 @@ private class Send extends AliasFunction, ArrayFunction, SideEffectFunction, Rem
override predicate hasRemoteFlowSink(FunctionInput input, string description) {
input.isParameterDeref(1) and description = "Buffer sent by " + this.getName()
}
+
+ override predicate hasSocketInput(FunctionInput input) {
+ input.isParameter(0)
+ }
}
diff --git a/cpp/ql/lib/semmle/code/cpp/models/interfaces/FlowSource.qll b/cpp/ql/lib/semmle/code/cpp/models/interfaces/FlowSource.qll
index 8c80377c8ec..d454257ea86 100644
--- a/cpp/ql/lib/semmle/code/cpp/models/interfaces/FlowSource.qll
+++ b/cpp/ql/lib/semmle/code/cpp/models/interfaces/FlowSource.qll
@@ -18,6 +18,12 @@ abstract class RemoteFlowSourceFunction extends Function {
* Holds if remote data described by `description` flows from `output` of a call to this function.
*/
abstract predicate hasRemoteFlowSource(FunctionOutput output, string description);
+
+ /**
+ * Holds if remote data from this source comes from a socket described by
+ * `input`. There is no result if a socket is not specified.
+ */
+ predicate hasSocketInput(FunctionInput input) { none() }
}
/**
@@ -51,4 +57,10 @@ abstract class RemoteFlowSinkFunction extends Function {
* send over a network connection.
*/
abstract predicate hasRemoteFlowSink(FunctionInput input, string description);
+
+ /**
+ * Holds if data put into this sink is transmitted through a socket described
+ * by `input`. There is no result if a socket is not specified.
+ */
+ predicate hasSocketInput(FunctionInput input) { none() }
}
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 70d0b472d66..70b00aecd6d 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -43,7 +43,13 @@ class NetworkSend extends NetworkSendRecv {
NetworkSend() { target = this.getTarget() }
- override Expr getSocketExpr() { result = this.getArgument(0) }
+ override Expr getSocketExpr() {
+ exists(FunctionInput input, int arg |
+ target.hasSocketInput(input) and
+ input.isParameter(arg) and
+ result = this.getArgument(arg)
+ )
+ }
override Expr getDataExpr() {
exists(FunctionInput input, int arg |
@@ -62,7 +68,13 @@ class NetworkRecv extends NetworkSendRecv {
NetworkRecv() { target = this.getTarget() }
- override Expr getSocketExpr() { result = this.getArgument(0) }
+ override Expr getSocketExpr() {
+ exists(FunctionInput input, int arg |
+ target.hasSocketInput(input) and
+ input.isParameter(arg) and
+ result = this.getArgument(arg)
+ )
+ }
override Expr getDataExpr() {
exists(FunctionOutput output, int arg |
@@ -85,7 +97,7 @@ class SensitiveSendRecvConfiguration extends TaintTracking::Configuration {
override predicate isSink(DataFlow::Node sink) {
exists(NetworkSendRecv transmission |
sink.asExpr() = transmission.getDataExpr() and
- // a zero file descriptor is standard input, which is not interesting for this query.
+ // a zero socket descriptor is standard input, which is not interesting for this query.
not exists(Zero zero |
DataFlow::localFlow(DataFlow::exprNode(zero),
DataFlow::exprNode(transmission.getSocketExpr()))
From ded30885293f934e6d4d3436a5789de43f7260d7 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 27 Sep 2021 12:08:40 +0200
Subject: [PATCH 069/361] Python/JS: Recognize SHA-3 hash functions
Official names are SHA3-224, SHA3-256, SHA3-384, SHA3-512 as per
https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
---
.../semmle/javascript/security/CryptoAlgorithms.qll | 6 +++++-
.../ql/lib/semmle/python/concepts/CryptoAlgorithms.qll | 6 +++++-
.../test/library-tests/frameworks/crypto/test_sha3.py | 10 ++++++++++
.../library-tests/frameworks/cryptodome/test_sha3.py | 10 ++++++++++
4 files changed, 30 insertions(+), 2 deletions(-)
create mode 100644 python/ql/test/library-tests/frameworks/crypto/test_sha3.py
create mode 100644 python/ql/test/library-tests/frameworks/cryptodome/test_sha3.py
diff --git a/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll b/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll
index d9f25b42c9a..a5bfd6696be 100644
--- a/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll
+++ b/javascript/ql/lib/semmle/javascript/security/CryptoAlgorithms.qll
@@ -28,7 +28,11 @@ private module AlgorithmNames {
name = "SHA256" or
name = "SHA384" or
name = "SHA512" or
- name = "SHA3"
+ name = "SHA3" or
+ name = "SHA3224" or
+ name = "SHA3256" or
+ name = "SHA3384" or
+ name = "SHA3512"
}
predicate isWeakHashingAlgorithm(string name) {
diff --git a/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll b/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll
index d9f25b42c9a..a5bfd6696be 100644
--- a/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll
+++ b/python/ql/lib/semmle/python/concepts/CryptoAlgorithms.qll
@@ -28,7 +28,11 @@ private module AlgorithmNames {
name = "SHA256" or
name = "SHA384" or
name = "SHA512" or
- name = "SHA3"
+ name = "SHA3" or
+ name = "SHA3224" or
+ name = "SHA3256" or
+ name = "SHA3384" or
+ name = "SHA3512"
}
predicate isWeakHashingAlgorithm(string name) {
diff --git a/python/ql/test/library-tests/frameworks/crypto/test_sha3.py b/python/ql/test/library-tests/frameworks/crypto/test_sha3.py
new file mode 100644
index 00000000000..426d0266fc4
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/crypto/test_sha3.py
@@ -0,0 +1,10 @@
+from Crypto.Hash import SHA3_224
+
+hasher = SHA3_224.new(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=SHA3224
+print(hasher.hexdigest())
+
+
+hasher = SHA3_224.new() # $ CryptographicOperation CryptographicOperationAlgorithm=SHA3224
+hasher.update(b"secret") # $ CryptographicOperation CryptographicOperationInput=b"secret" CryptographicOperationAlgorithm=SHA3224
+hasher.update(b" message") # $ CryptographicOperation CryptographicOperationInput=b" message" CryptographicOperationAlgorithm=SHA3224
+print(hasher.hexdigest())
diff --git a/python/ql/test/library-tests/frameworks/cryptodome/test_sha3.py b/python/ql/test/library-tests/frameworks/cryptodome/test_sha3.py
new file mode 100644
index 00000000000..2329fd7e1c0
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/cryptodome/test_sha3.py
@@ -0,0 +1,10 @@
+from Cryptodome.Hash import SHA3_224
+
+hasher = SHA3_224.new(b"secret message") # $ CryptographicOperation CryptographicOperationInput=b"secret message" CryptographicOperationAlgorithm=SHA3224
+print(hasher.hexdigest())
+
+
+hasher = SHA3_224.new() # $ CryptographicOperation CryptographicOperationAlgorithm=SHA3224
+hasher.update(b"secret") # $ CryptographicOperation CryptographicOperationInput=b"secret" CryptographicOperationAlgorithm=SHA3224
+hasher.update(b" message") # $ CryptographicOperation CryptographicOperationInput=b" message" CryptographicOperationAlgorithm=SHA3224
+print(hasher.hexdigest())
From 9a9bbe3123d8670e93cf2d91ea920d9370883492 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Mon, 20 Sep 2021 14:22:50 +0200
Subject: [PATCH 070/361] Dataflow: Support side-effects for callbacks in
summaries.
---
.../dataflow/internal/FlowSummaryImpl.qll | 108 ++++++++++++------
.../internal/FlowSummaryImplSpecific.qll | 5 +
.../dataflow/callback-dispatch/A.java | 59 ++++++++++
.../dataflow/callback-dispatch/test.ql | 4 +
4 files changed, 144 insertions(+), 32 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
index 523516e60f8..f76baca5b8a 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
@@ -186,10 +186,15 @@ module Private {
TArgumentSummaryComponent(int i) { parameterPosition(i) } or
TReturnSummaryComponent(ReturnKind rk)
+ private TSummaryComponent thisParam() { result = TParameterSummaryComponent(instanceParameterPosition()) }
+
newtype TSummaryComponentStack =
TSingletonSummaryComponentStack(SummaryComponent c) or
TConsSummaryComponentStack(SummaryComponent head, SummaryComponentStack tail) {
tail.(RequiredSummaryComponentStack).required(head)
+ or
+ tail.(RequiredSummaryComponentStack).required(TParameterSummaryComponent(_)) and
+ head = thisParam()
}
pragma[nomagic]
@@ -198,21 +203,63 @@ module Private {
boolean preservesValue
) {
c.propagatesFlow(input, output, preservesValue)
+ or
+ // observe side effects of callbacks on input arguments
+ c.propagatesFlow(output, input, preservesValue) and
+ preservesValue = true and
+ isCallbackParameter(input) and
+ isContentOfArgument(output)
+ or
+ // flow from the receiver of a callback into the instance-parameter
+ exists(SummaryComponentStack s, SummaryComponentStack callbackRef |
+ c.propagatesFlow(s, _, _) or c.propagatesFlow(_, s, _)
+ |
+ callbackRef = s.drop(_) and
+ (isCallbackParameter(callbackRef) or callbackRef.head() = TReturnSummaryComponent(_)) and
+ input = callbackRef.tail() and
+ output = TConsSummaryComponentStack(thisParam(), input) and
+ preservesValue = true
+ )
+ }
+
+ private predicate isCallbackParameter(SummaryComponentStack s) {
+ s.head() = TParameterSummaryComponent(_) and exists(s.tail())
+ }
+
+ private predicate isContentOfArgument(SummaryComponentStack s) {
+ s.head() = TContentSummaryComponent(_) and isContentOfArgument(s.tail())
+ or
+ s = TSingletonSummaryComponentStack(TArgumentSummaryComponent(_))
+ }
+
+ private predicate outputState(SummarizedCallable c, SummaryComponentStack s) {
+ summary(c, _, s, _)
+ or
+ exists(SummaryComponentStack out |
+ outputState(c, out) and
+ out.head() = TContentSummaryComponent(_) and
+ s = out.tail()
+ )
+ or
+ // Add the argument node corresponding to the requested post-update node
+ inputState(c, s) and isCallbackParameter(s)
+ }
+
+ private predicate inputState(SummarizedCallable c, SummaryComponentStack s) {
+ summary(c, s, _, _)
+ or
+ exists(SummaryComponentStack inp | inputState(c, inp) and s = inp.tail())
+ or
+ exists(SummaryComponentStack out |
+ outputState(c, out) and
+ out.head() = TParameterSummaryComponent(_) and
+ s = out.tail()
+ )
}
private newtype TSummaryNodeState =
- TSummaryNodeInputState(SummaryComponentStack s) {
- exists(SummaryComponentStack input |
- summary(_, input, _, _) and
- s = input.drop(_)
- )
- } or
- TSummaryNodeOutputState(SummaryComponentStack s) {
- exists(SummaryComponentStack output |
- summary(_, _, output, _) and
- s = output.drop(_)
- )
- }
+ TSummaryNodeInputState(SummaryComponentStack s) { inputState(_, s) } or
+ TSummaryNodeOutputState(SummaryComponentStack s) { outputState(_, s) }
/**
* A state used to break up (complex) flow summaries into atomic flow steps.
@@ -238,20 +285,14 @@ module Private {
pragma[nomagic]
predicate isInputState(SummarizedCallable c, SummaryComponentStack s) {
this = TSummaryNodeInputState(s) and
- exists(SummaryComponentStack input |
- summary(c, input, _, _) and
- s = input.drop(_)
- )
+ inputState(c, s)
}
/** Holds if this state is a valid output state for `c`. */
pragma[nomagic]
predicate isOutputState(SummarizedCallable c, SummaryComponentStack s) {
this = TSummaryNodeOutputState(s) and
- exists(SummaryComponentStack output |
- summary(c, _, output, _) and
- s = output.drop(_)
- )
+ outputState(c, s)
}
/** Gets a textual representation of this state. */
@@ -331,19 +372,12 @@ module Private {
receiver = summaryNodeInputState(c, s.drop(1))
}
- private Node pre(Node post) {
- summaryPostUpdateNode(post, result)
- or
- not summaryPostUpdateNode(post, _) and
- result = post
- }
-
private predicate callbackInput(
SummarizedCallable c, SummaryComponentStack s, Node receiver, int i
) {
any(SummaryNodeState state).isOutputState(c, s) and
s.head() = TParameterSummaryComponent(i) and
- receiver = pre(summaryNodeOutputState(c, s.drop(1)))
+ receiver = summaryNodeInputState(c, s.drop(1))
}
/** Holds if a call targeting `receiver` should be synthesized inside `c`. */
@@ -395,7 +429,7 @@ module Private {
or
exists(int i | head = TParameterSummaryComponent(i) |
result =
- getCallbackParameterType(getNodeType(summaryNodeOutputState(pragma[only_bind_out](c),
+ getCallbackParameterType(getNodeType(summaryNodeInputState(pragma[only_bind_out](c),
s.drop(1))), i)
)
)
@@ -421,10 +455,16 @@ module Private {
}
/** Holds if summary node `post` is a post-update node with pre-update node `pre`. */
- predicate summaryPostUpdateNode(Node post, ParamNode pre) {
+ predicate summaryPostUpdateNode(Node post, Node pre) {
exists(SummarizedCallable c, int i |
isParameterPostUpdate(post, c, i) and
- pre.isParameterOf(c, i)
+ pre.(ParamNode).isParameterOf(c, i)
+ )
+ or
+ exists(SummarizedCallable callable, SummaryComponentStack s |
+ callbackInput(callable, s, _, _) and
+ pre = summaryNodeOutputState(callable, s) and
+ post = summaryNodeInputState(callable, s)
)
}
@@ -462,7 +502,11 @@ module Private {
// for `StringBuilder.append(x)` with a specified value flow from qualifier to
// return value and taint flow from argument 0 to the qualifier, then this
// allows us to infer taint flow from argument 0 to the return value.
- summaryPostUpdateNode(pred, succ) and preservesValue = true
+ succ instanceof ParamNode and summaryPostUpdateNode(pred, succ) and preservesValue = true
+ or
+ // Similarly we would like to chain together summaries where values get passed
+ // into callbacks along the way.
+ pred instanceof ArgNode and summaryPostUpdateNode(succ, pred) and preservesValue = true
}
/**
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
index d3a4612255d..e4c3091f05e 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll
@@ -16,6 +16,9 @@ private module FlowSummaries {
/** Holds is `i` is a valid parameter position. */
predicate parameterPosition(int i) { i in [-1 .. any(Parameter p).getPosition()] }
+/** Gets the parameter position of the instance parameter. */
+int instanceParameterPosition() { result = -1 }
+
/** Gets the synthesized summary data-flow node for the given values. */
Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSummaryNode(c, state) }
@@ -37,6 +40,8 @@ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
*/
DataFlowType getCallbackParameterType(DataFlowType t, int i) {
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getParameterType(i))
+ or
+ result = getErasedRepr(t.(FunctionalInterface)) and i = -1
}
/**
diff --git a/java/ql/test/library-tests/dataflow/callback-dispatch/A.java b/java/ql/test/library-tests/dataflow/callback-dispatch/A.java
index 4a4f0b11ded..6224a2e3d95 100644
--- a/java/ql/test/library-tests/dataflow/callback-dispatch/A.java
+++ b/java/ql/test/library-tests/dataflow/callback-dispatch/A.java
@@ -1,5 +1,7 @@
package my.callback.qltest;
+import java.util.*;
+
public class A {
public interface Consumer1 {
void eat(Object o);
@@ -28,6 +30,20 @@ public class A {
// con.eat(x);
}
+ static T applyConsumer3_ret_postup(Consumer3 con) {
+ // summary:
+ // x = new T();
+ // con.eat(x);
+ // return x;
+ return null;
+ }
+
+ static void forEach(T[] xs, Consumer3 con) {
+ // summary:
+ // x = xs[..];
+ // con.eat(x);
+ }
+
public interface Producer1 {
T make();
}
@@ -38,6 +54,14 @@ public class A {
return null;
}
+ static T produceConsume(Producer1 prod, Consumer3 con) {
+ // summary:
+ // x = prod.make();
+ // con.eat(x);
+ // return x;
+ return null;
+ }
+
public interface Converter1 {
T2 conv(T1 x);
}
@@ -109,5 +133,40 @@ public class A {
};
applyConsumer3(new Integer[] { (Integer)source(12) }, pc);
sink(applyProducer1(pc)[0]); // $ flow=11
+
+ Integer res = applyProducer1(new Producer1() {
+ private Integer ii = (Integer)source(13);
+ @Override public Integer make() {
+ return this.ii;
+ }
+ });
+ sink(res); // $ flow=13
+
+ ArrayList
-
Ensure that sensitive information is always encrypted before being stored or transmitted, especially before writing to a file.
+
Ensure that sensitive information is always encrypted before being stored to a file or transmitted over the network.
It may be wise to encrypt information before it is put into a buffer that may be readable in memory.
In general, decrypt sensitive information only at the point where it is necessary for it to be used in
From 89098f54be7c09a23af7defbb4a7d4086c88903c Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 28 Sep 2021 16:03:04 +0100
Subject: [PATCH 074/361] C++: Correct comment.
---
cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
index 70b00aecd6d..d7e5343d6dc 100644
--- a/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
+++ b/cpp/ql/src/Security/CWE/CWE-311/CleartextTransmission.ql
@@ -23,7 +23,7 @@ import DataFlow::PathGraph
abstract class NetworkSendRecv extends FunctionCall {
/**
* Gets the expression for the socket or similar object used for sending or
- * receiving data.
+ * receiving data (if any).
*/
abstract Expr getSocketExpr();
From 5317022d2ea7af5b0c59f058d871ecce04e2eecd Mon Sep 17 00:00:00 2001
From: Calum Grant
Date: Tue, 28 Sep 2021 20:51:21 +0100
Subject: [PATCH 075/361] Basic query for Ruby
---
.../basic-query-for-ruby-code.rst | 146 ++++++++++++++++++
.../codeql/reusables/ruby-further-reading.rst | 3 +
2 files changed, 149 insertions(+)
create mode 100644 docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst
create mode 100644 docs/codeql/reusables/ruby-further-reading.rst
diff --git a/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst b/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst
new file mode 100644
index 00000000000..a48933fc4cf
--- /dev/null
+++ b/docs/codeql/codeql-language-guides/basic-query-for-ruby-code.rst
@@ -0,0 +1,146 @@
+.. _basic-query-for-ruby-code:
+
+Basic query for Ruby code
+=========================
+
+Learn to write and run a simple CodeQL query using CodeQL.
+
+About the query
+---------------
+
+The query we're going to run performs a basic search of the code for ``if`` expressions that are redundant, in the sense that they have an empty ``then`` branch. For example, code such as:
+
+.. code-block:: ruby
+
+ if error
+ # Handle the error
+
+Running the query
+-----------------
+
+#. In the main search box on LGTM.com, search for the project you want to query. For tips, see `Searching `__.
+
+#. Click the project in the search results.
+
+#. Click **Query this project**.
+
+ This opens the query console. (For information about using this, see `Using the query console `__.)
+
+ .. pull-quote::
+
+ Note
+
+ Alternatively, you can go straight to the query console by clicking **Query console** (at the top of any page), selecting **Ruby** from the **Language** drop-down list, then choosing one or more projects to query from those displayed in the **Project** drop-down list.
+
+#. Copy the following query into the text box in the query console:
+
+ .. code-block:: ql
+
+ import ruby
+
+ from IfExpr ifexpr
+ where
+ not exists(ifexpr.getThen())
+ select ifexpr, "This 'if' expression is redundant."
+
+ LGTM checks whether your query compiles and, if all is well, the **Run** button changes to green to indicate that you can go ahead and run the query.
+
+#. Click **Run**.
+
+ The name of the project you are querying, and the ID of the most recently analyzed commit to the project, are listed below the query box. To the right of this is an icon that indicates the progress of the query operation:
+
+ .. image:: ../images/query-progress.png
+ :align: center
+
+ .. pull-quote::
+
+ Note
+
+ Your query is always run against the most recently analyzed commit to the selected project.
+
+ The query will take a few moments to return results. When the query completes, the results are displayed below the project name. The query results are listed in two columns, corresponding to the two expressions in the ``select`` clause of the query. The first column corresponds to the expression ``ifexpr`` and is linked to the location in the source code of the project where ``ifexpr`` occurs. The second column is the alert message.
+
+ ➤ `Example query results `__
+
+ .. pull-quote::
+
+ Note
+
+ An ellipsis (…) at the bottom of the table indicates that the entire list is not displayed—click it to show more results.
+
+#. If any matching code is found, click a link in the ``ifexpr`` column to view the ``if`` statement in the code viewer.
+
+ The matching ``if`` expression is highlighted with a yellow background in the code viewer. If any code in the file also matches a query from the standard query library for that language, you will see a red alert message at the appropriate point within the code.
+
+About the query structure
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+After the initial ``import`` statement, this simple query comprises three parts that serve similar purposes to the FROM, WHERE, and SELECT parts of an SQL query.
+
++---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
+| Query part | Purpose | Details |
++===============================================================+===================================================================================================================+========================================================================================================================+
+| ``import ruby`` | Imports the standard CodeQL libraries for Ruby. | Every query begins with one or more ``import`` statements. |
++---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
+| ``from IfExpr ifexpr`` | Defines the variables for the query. | We use: an ``IfExpr`` variable for ``if`` expressions. |
+| | Declarations are of the form: | |
+| | ```` | |
++---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
+| ``where not exists(ifexpr.getThen())`` | Defines a condition on the variables. | ``ifexpr.getThen()``: gets the ``then`` branch of the ``if`` expression. |
+| | | |
+| | | ``exists(...)``: requires that there is a matching element, in this case a ``then`` branch. |
++---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
+| ``select ifexpr, "This 'if' expression is redundant."`` | Defines what to report for each match. | Reports the resulting ``if`` expression with a string that explains the problem. |
+| | | |
+| | ``select`` statements for queries that are used to find instances of poor coding practice are always in the form: | |
+| | ``select , ""`` | |
++---------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------+
+
+Extend the query
+----------------
+
+Query writing is an inherently iterative process. You write a simple query and then, when you run it, you discover examples that you had not previously considered, or opportunities for improvement.
+
+Remove false positive results
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Browsing the results of our basic query shows that it could be improved. Among the results you are likely to find examples of ``if`` statements with an ``else`` branch, where an empty ``then`` branch does serve a purpose. For example:
+
+.. code-block:: ruby
+
+ if ...
+ ...
+ elsif option == "-verbose"
+ # nothing to do - handled earlier
+ else
+ error "unrecognized option"
+
+In this case, identifying the ``if`` statement with the empty ``then`` branch as redundant is a false positive. One solution to this is to modify the query to ignore empty ``then`` branches if the ``if`` statement has an ``else`` branch.
+
+To exclude ``if`` statements that have an ``else`` branch:
+
+#. Add the following to the where clause:
+
+ .. code-block:: ql
+
+ and not exists(ifstmt.getElse())
+
+ The ``where`` clause is now:
+
+ .. code-block:: ql
+
+ where
+ not exists(ifexpr.getThen()) and
+ not exists(ifexpr.getElse())
+
+#. Click **Run**.
+
+ There are now fewer results because ``if`` expressions with an ``else`` branch are no longer included.
+
+➤ `See this in the query console `__
+
+Further reading
+---------------
+
+.. include:: ../reusables/ruby-further-reading.rst
+.. include:: ../reusables/codeql-ref-tools-further-reading.rst
diff --git a/docs/codeql/reusables/ruby-further-reading.rst b/docs/codeql/reusables/ruby-further-reading.rst
new file mode 100644
index 00000000000..032eaff5112
--- /dev/null
+++ b/docs/codeql/reusables/ruby-further-reading.rst
@@ -0,0 +1,3 @@
+- `CodeQL queries for Ruby `__
+- `Example queries for Ruby `__
+- `CodeQL library reference for Ruby `__
From dea8dde566abd228890e6a4064488c014f12684b Mon Sep 17 00:00:00 2001
From: alexet
Date: Wed, 22 Sep 2021 15:17:06 +0100
Subject: [PATCH 076/361] Java: Improve performance of confusing overloading
query.
---
.../Naming Conventions/ConfusingOverloading.ql | 1 +
1 file changed, 1 insertion(+)
diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
index 1d8509c6e03..22bf9d36142 100644
--- a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
+++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql
@@ -50,6 +50,7 @@ private predicate whitelist(string name) { name = "visit" }
* Method `m` has name `name`, number of parameters `numParams`
* and is declared in `t` or inherited from a supertype of `t`.
*/
+pragma[nomagic]
private predicate candidateMethod(RefType t, Method m, string name, int numParam) {
exists(Method n | n.getSourceDeclaration() = m | t.inherits(n)) and
m.getName() = name and
From 3ae5f13c3dbb3dc012b11a65d8fc2c683159abb1 Mon Sep 17 00:00:00 2001
From: Joe Farebrother
Date: Wed, 29 Sep 2021 15:44:21 +0100
Subject: [PATCH 077/361] Generate tests and stubs
---
.../code/java/frameworks/android/Intent.qll | 2 +-
.../frameworks/android/intent/Test.java | 1316 +++++++++++++++++
.../frameworks/android/intent/models.csv | 114 ++
.../frameworks/android/intent/options | 1 +
.../frameworks/android/intent/test.expected | 0
.../frameworks/android/intent/test.ql | 17 +
.../android/android/accounts/Account.java | 21 +
.../android/content/BroadcastReceiver.java | 292 +---
.../android/android/content/ClipData.java | 51 +
.../android/content/ClipDescription.java | 32 +
.../android/content/ComponentCallbacks.java | 11 +
.../android/content/ComponentCallbacks2.java | 17 +
.../android/content/ComponentName.java | 35 +
.../android/content/ContentProvider.java | 85 +-
.../content/ContentProviderClient.java | 49 +
.../content/ContentProviderOperation.java | 58 +
.../content/ContentProviderResult.java | 26 +
.../android/content/ContentResolver.java | 163 +-
.../android/content/ContentValues.java | 47 +-
.../android/android/content/Context.java | 262 +++-
.../stubs/android/android/content/Intent.java | 641 +++++---
.../android/android/content/IntentFilter.java | 104 ++
.../android/android/content/IntentSender.java | 40 +
.../android/android/content/PeriodicSync.java | 23 +
.../android/content/ServiceConnection.java | 14 +
.../android/content/SharedPreferences.java | 38 +
.../android/content/SyncAdapterType.java | 28 +
.../android/android/content/SyncInfo.java | 16 +
.../android/android/content/SyncRequest.java | 13 +
.../android/content/SyncStatusObserver.java | 9 +
.../android/content/UriPermission.java | 20 +
.../android/content/pm/ActivityInfo.java | 112 ++
.../android/content/pm/ApplicationInfo.java | 101 ++
.../android/content/pm/ChangedPackages.java | 18 +
.../android/content/pm/ComponentInfo.java | 29 +
.../android/content/pm/ConfigurationInfo.java | 25 +
.../android/content/pm/FeatureGroupInfo.java | 17 +
.../android/content/pm/FeatureInfo.java | 23 +
.../android/content/pm/InstallSourceInfo.java | 18 +
.../content/pm/InstrumentationInfo.java | 27 +
.../android/content/pm/ModuleInfo.java | 19 +
.../android/content/pm/PackageInfo.java | 59 +
.../android/content/pm/PackageInstaller.java | 151 ++
.../android/content/pm/PackageItemInfo.java | 34 +
.../android/content/pm/PackageManager.java | 331 +++++
.../android/content/pm/PathPermission.java | 18 +
.../content/pm/PermissionGroupInfo.java | 24 +
.../android/content/pm/PermissionInfo.java | 48 +
.../android/content/pm/ProviderInfo.java | 33 +
.../android/content/pm/ResolveInfo.java | 42 +
.../android/content/pm/ServiceInfo.java | 37 +
.../android/content/pm/SharedLibraryInfo.java | 26 +
.../android/android/content/pm/Signature.java | 22 +
.../android/content/pm/SigningInfo.java | 20 +
.../android/content/pm/VersionedPackage.java | 22 +
.../content/res/AssetFileDescriptor.java | 33 +
.../android/content/res/AssetManager.java | 26 +
.../android/content/res/ColorStateList.java | 27 +
.../android/content/res/Configuration.java | 130 ++
.../android/content/res/Resources.java | 105 ++
.../android/content/res/TypedArray.java | 46 +
.../content/res/XmlResourceParser.java | 12 +
.../content/res/loader/AssetsProvider.java | 10 +
.../content/res/loader/ResourcesLoader.java | 16 +
.../content/res/loader/ResourcesProvider.java | 21 +
.../android/database/CharArrayBuffer.java | 13 +
.../android/database/ContentObserver.java | 22 +
.../android/android/database/Cursor.java | 61 +-
.../android/database/DataSetObserver.java | 11 +
.../database/DatabaseErrorHandler.java | 10 +
.../database/sqlite/SQLiteClosable.java | 16 +
.../database/sqlite/SQLiteCursorDriver.java | 15 +
.../database/sqlite/SQLiteDatabase.java | 171 ++-
.../database/sqlite/SQLiteProgram.java | 18 +
.../android/database/sqlite/SQLiteQuery.java | 10 +
.../database/sqlite/SQLiteStatement.java | 17 +
.../sqlite/SQLiteTransactionListener.java | 11 +
.../android/android/graphics/Bitmap.java | 97 ++
.../android/graphics/BitmapFactory.java | 54 +
.../android/android/graphics/BlendMode.java | 10 +
.../android/android/graphics/Canvas.java | 141 ++
.../stubs/android/android/graphics/Color.java | 80 +
.../android/android/graphics/ColorFilter.java | 9 +
.../android/android/graphics/ColorSpace.java | 120 ++
.../android/android/graphics/DrawFilter.java | 10 +
.../android/android/graphics/Insets.java | 29 +
.../android/android/graphics/MaskFilter.java | 10 +
.../android/android/graphics/Matrix.java | 74 +
.../stubs/android/android/graphics/Movie.java | 23 +
.../android/android/graphics/NinePatch.java | 31 +
.../android/android/graphics/Outline.java | 29 +
.../stubs/android/android/graphics/Paint.java | 212 +++
.../stubs/android/android/graphics/Path.java | 73 +
.../android/android/graphics/PathEffect.java | 10 +
.../android/android/graphics/Picture.java | 18 +
.../stubs/android/android/graphics/Point.java | 26 +
.../android/android/graphics/PorterDuff.java | 14 +
.../android/graphics/RecordingCanvas.java | 82 +
.../stubs/android/android/graphics/Rect.java | 52 +
.../stubs/android/android/graphics/RectF.java | 53 +
.../android/android/graphics/Region.java | 53 +
.../android/android/graphics/RenderNode.java | 82 +
.../android/android/graphics/Shader.java | 12 +
.../android/android/graphics/Typeface.java | 33 +
.../android/android/graphics/Xfermode.java | 9 +
.../android/graphics/drawable/Drawable.java | 111 ++
.../android/graphics/drawable/Icon.java | 55 +
.../android/graphics/text/MeasuredText.java | 13 +
.../android/hardware/HardwareBuffer.java | 50 +
.../android/android/icu/util/ULocale.java | 138 ++
.../test/stubs/android/android/net/Uri.java | 73 +-
.../stubs/android/android/os/BaseBundle.java | 43 +
.../test/stubs/android/android/os/Bundle.java | 163 +-
.../android/os/CancellationSignal.java | 14 +-
.../stubs/android/android/os/Handler.java | 53 +
.../stubs/android/android/os/IBinder.java | 33 +
.../stubs/android/android/os/IInterface.java | 10 +
.../stubs/android/android/os/LocaleList.java | 32 +
.../test/stubs/android/android/os/Looper.java | 25 +
.../stubs/android/android/os/Message.java | 44 +
.../android/android/os/MessageQueue.java | 26 +
.../stubs/android/android/os/Messenger.java | 25 +
.../test/stubs/android/android/os/Parcel.java | 146 ++
.../android/os/ParcelFileDescriptor.java | 55 +-
.../stubs/android/android/os/Parcelable.java | 18 +
.../android/android/os/PatternMatcher.java | 24 +
.../android/android/os/PersistableBundle.java | 27 +
.../stubs/android/android/os/UserHandle.java | 21 +
.../android/util/AndroidException.java | 12 +
.../stubs/android/android/util/ArrayMap.java | 40 +
.../android/android/util/AttributeSet.java | 95 +-
.../android/android/util/DisplayMetrics.java | 46 +
.../test/stubs/android/android/util/Pair.java | 16 +
.../stubs/android/android/util/Printer.java | 9 +
.../test/stubs/android/android/util/Size.java | 16 +
.../stubs/android/android/util/SizeF.java | 16 +
.../android/android/util/SparseArray.java | 28 +
.../android/util/SparseBooleanArray.java | 27 +
.../android/android/util/TypedValue.java | 73 +
.../stubs/android/android/view/Display.java | 89 ++
.../android/android/view/DisplayCutout.java | 28 +
.../android/org/xmlpull/v1/XmlPullParser.java | 64 +
.../android/org/xmlpull/v1/XmlSerializer.java | 35 +
143 files changed, 8174 insertions(+), 667 deletions(-)
create mode 100644 java/ql/test/library-tests/frameworks/android/intent/Test.java
create mode 100644 java/ql/test/library-tests/frameworks/android/intent/models.csv
create mode 100644 java/ql/test/library-tests/frameworks/android/intent/options
create mode 100644 java/ql/test/library-tests/frameworks/android/intent/test.expected
create mode 100644 java/ql/test/library-tests/frameworks/android/intent/test.ql
create mode 100644 java/ql/test/stubs/android/android/accounts/Account.java
create mode 100644 java/ql/test/stubs/android/android/content/ClipData.java
create mode 100644 java/ql/test/stubs/android/android/content/ClipDescription.java
create mode 100644 java/ql/test/stubs/android/android/content/ComponentCallbacks.java
create mode 100644 java/ql/test/stubs/android/android/content/ComponentCallbacks2.java
create mode 100644 java/ql/test/stubs/android/android/content/ComponentName.java
create mode 100644 java/ql/test/stubs/android/android/content/ContentProviderClient.java
create mode 100644 java/ql/test/stubs/android/android/content/ContentProviderOperation.java
create mode 100644 java/ql/test/stubs/android/android/content/ContentProviderResult.java
create mode 100644 java/ql/test/stubs/android/android/content/IntentFilter.java
create mode 100644 java/ql/test/stubs/android/android/content/IntentSender.java
create mode 100644 java/ql/test/stubs/android/android/content/PeriodicSync.java
create mode 100644 java/ql/test/stubs/android/android/content/ServiceConnection.java
create mode 100644 java/ql/test/stubs/android/android/content/SharedPreferences.java
create mode 100644 java/ql/test/stubs/android/android/content/SyncAdapterType.java
create mode 100644 java/ql/test/stubs/android/android/content/SyncInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/SyncRequest.java
create mode 100644 java/ql/test/stubs/android/android/content/SyncStatusObserver.java
create mode 100644 java/ql/test/stubs/android/android/content/UriPermission.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ActivityInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ApplicationInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ChangedPackages.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ComponentInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ConfigurationInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/FeatureGroupInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/FeatureInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/InstallSourceInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/InstrumentationInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ModuleInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PackageInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PackageInstaller.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PackageItemInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PackageManager.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PathPermission.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PermissionGroupInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/PermissionInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ProviderInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ResolveInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/ServiceInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/SharedLibraryInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/Signature.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/SigningInfo.java
create mode 100644 java/ql/test/stubs/android/android/content/pm/VersionedPackage.java
create mode 100644 java/ql/test/stubs/android/android/content/res/AssetFileDescriptor.java
create mode 100644 java/ql/test/stubs/android/android/content/res/AssetManager.java
create mode 100644 java/ql/test/stubs/android/android/content/res/ColorStateList.java
create mode 100644 java/ql/test/stubs/android/android/content/res/Configuration.java
create mode 100644 java/ql/test/stubs/android/android/content/res/Resources.java
create mode 100644 java/ql/test/stubs/android/android/content/res/TypedArray.java
create mode 100644 java/ql/test/stubs/android/android/content/res/XmlResourceParser.java
create mode 100644 java/ql/test/stubs/android/android/content/res/loader/AssetsProvider.java
create mode 100644 java/ql/test/stubs/android/android/content/res/loader/ResourcesLoader.java
create mode 100644 java/ql/test/stubs/android/android/content/res/loader/ResourcesProvider.java
create mode 100644 java/ql/test/stubs/android/android/database/CharArrayBuffer.java
create mode 100644 java/ql/test/stubs/android/android/database/ContentObserver.java
create mode 100644 java/ql/test/stubs/android/android/database/DataSetObserver.java
create mode 100644 java/ql/test/stubs/android/android/database/DatabaseErrorHandler.java
create mode 100644 java/ql/test/stubs/android/android/database/sqlite/SQLiteClosable.java
create mode 100644 java/ql/test/stubs/android/android/database/sqlite/SQLiteCursorDriver.java
create mode 100644 java/ql/test/stubs/android/android/database/sqlite/SQLiteProgram.java
create mode 100644 java/ql/test/stubs/android/android/database/sqlite/SQLiteQuery.java
create mode 100644 java/ql/test/stubs/android/android/database/sqlite/SQLiteStatement.java
create mode 100644 java/ql/test/stubs/android/android/database/sqlite/SQLiteTransactionListener.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Bitmap.java
create mode 100644 java/ql/test/stubs/android/android/graphics/BitmapFactory.java
create mode 100644 java/ql/test/stubs/android/android/graphics/BlendMode.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Canvas.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Color.java
create mode 100644 java/ql/test/stubs/android/android/graphics/ColorFilter.java
create mode 100644 java/ql/test/stubs/android/android/graphics/ColorSpace.java
create mode 100644 java/ql/test/stubs/android/android/graphics/DrawFilter.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Insets.java
create mode 100644 java/ql/test/stubs/android/android/graphics/MaskFilter.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Matrix.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Movie.java
create mode 100644 java/ql/test/stubs/android/android/graphics/NinePatch.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Outline.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Paint.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Path.java
create mode 100644 java/ql/test/stubs/android/android/graphics/PathEffect.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Picture.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Point.java
create mode 100644 java/ql/test/stubs/android/android/graphics/PorterDuff.java
create mode 100644 java/ql/test/stubs/android/android/graphics/RecordingCanvas.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Rect.java
create mode 100644 java/ql/test/stubs/android/android/graphics/RectF.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Region.java
create mode 100644 java/ql/test/stubs/android/android/graphics/RenderNode.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Shader.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Typeface.java
create mode 100644 java/ql/test/stubs/android/android/graphics/Xfermode.java
create mode 100644 java/ql/test/stubs/android/android/graphics/drawable/Drawable.java
create mode 100644 java/ql/test/stubs/android/android/graphics/drawable/Icon.java
create mode 100644 java/ql/test/stubs/android/android/graphics/text/MeasuredText.java
create mode 100644 java/ql/test/stubs/android/android/hardware/HardwareBuffer.java
create mode 100644 java/ql/test/stubs/android/android/icu/util/ULocale.java
create mode 100644 java/ql/test/stubs/android/android/os/BaseBundle.java
create mode 100644 java/ql/test/stubs/android/android/os/Handler.java
create mode 100644 java/ql/test/stubs/android/android/os/IBinder.java
create mode 100644 java/ql/test/stubs/android/android/os/IInterface.java
create mode 100644 java/ql/test/stubs/android/android/os/LocaleList.java
create mode 100644 java/ql/test/stubs/android/android/os/Looper.java
create mode 100644 java/ql/test/stubs/android/android/os/Message.java
create mode 100644 java/ql/test/stubs/android/android/os/MessageQueue.java
create mode 100644 java/ql/test/stubs/android/android/os/Messenger.java
create mode 100644 java/ql/test/stubs/android/android/os/Parcel.java
create mode 100644 java/ql/test/stubs/android/android/os/Parcelable.java
create mode 100644 java/ql/test/stubs/android/android/os/PatternMatcher.java
create mode 100644 java/ql/test/stubs/android/android/os/PersistableBundle.java
create mode 100644 java/ql/test/stubs/android/android/os/UserHandle.java
create mode 100644 java/ql/test/stubs/android/android/util/AndroidException.java
create mode 100644 java/ql/test/stubs/android/android/util/ArrayMap.java
create mode 100644 java/ql/test/stubs/android/android/util/DisplayMetrics.java
create mode 100644 java/ql/test/stubs/android/android/util/Pair.java
create mode 100644 java/ql/test/stubs/android/android/util/Printer.java
create mode 100644 java/ql/test/stubs/android/android/util/Size.java
create mode 100644 java/ql/test/stubs/android/android/util/SizeF.java
create mode 100644 java/ql/test/stubs/android/android/util/SparseArray.java
create mode 100644 java/ql/test/stubs/android/android/util/SparseBooleanArray.java
create mode 100644 java/ql/test/stubs/android/android/util/TypedValue.java
create mode 100644 java/ql/test/stubs/android/android/view/Display.java
create mode 100644 java/ql/test/stubs/android/android/view/DisplayCutout.java
create mode 100644 java/ql/test/stubs/android/org/xmlpull/v1/XmlPullParser.java
create mode 100644 java/ql/test/stubs/android/org/xmlpull/v1/XmlSerializer.java
diff --git a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
index edb981cff8e..77c45b1e6f2 100644
--- a/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/android/Intent.qll
@@ -127,7 +127,7 @@ private class IntentBundleFlowSteps extends SummaryModelCsv {
"android.os;Bundle;true;putShortArray;;;Argument[0];MapKey of Argument[-1];value",
"android.os;Bundle;true;putSize;;;Argument[0];MapKey of Argument[-1];value",
"android.os;Bundle;true;putSizeF;;;Argument[0];MapKey of Argument[-1];value",
- "android.os;Bundle;true;putSparceParcelableArray;;;Argument[0];MapKey of Argument[-1];value",
+ "android.os;Bundle;true;putSparseParcelableArray;;;Argument[0];MapKey of Argument[-1];value",
"android.os;Bundle;true;putSparseParcelableArray;;;Argument[1];MapValue of Argument[-1];value",
"android.os;Bundle;true;putStringArrayList;;;Argument[0];MapKey of Argument[-1];value",
"android.os;Bundle;true;putStringArrayList;;;Argument[1];MapValue of Argument[-1];value",
diff --git a/java/ql/test/library-tests/frameworks/android/intent/Test.java b/java/ql/test/library-tests/frameworks/android/intent/Test.java
new file mode 100644
index 00000000000..525986aeb45
--- /dev/null
+++ b/java/ql/test/library-tests/frameworks/android/intent/Test.java
@@ -0,0 +1,1316 @@
+package generatedtest;
+
+import android.content.Intent;
+import android.os.BaseBundle;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.util.SparseArray;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Set;
+
+// Test case generated by GenerateFlowTestCase.ql
+public class Test {
+
+ T getElement(Iterable it) { return it.iterator().next(); }
+ Object getIntent_extrasDefault(Object container) { return null; }
+ Object getMapKeyDefault(Object container) { return null; }
+ Object getMapValueDefault(Object container) { return null; }
+ Object newWithIntent_extrasDefault(Object element) { return null; }
+ Object newWithMapKeyDefault(Object element) { return null; }
+ Object newWithMapValueDefault(Object element) { return null; }
+ Object source() { return null; }
+ void sink(Object o) { }
+
+ public void test() throws Exception {
+
+ {
+ // "android.content;Intent;true;getBundleExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ Bundle out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getBundleExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getByteArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ byte[] out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getByteArrayExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getCharArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ char[] out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getCharArrayExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getCharSequenceArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ CharSequence[] out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getCharSequenceArrayExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getCharSequenceArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ ArrayList out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getCharSequenceArrayListExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getCharSequenceExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ CharSequence out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getCharSequenceExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getExtras;();;SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ Bundle out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(source());
+ out = in.getExtras();
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getParcelableArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ Parcelable[] out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getParcelableArrayExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getParcelableArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ ArrayList out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getParcelableArrayListExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getParcelableExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ Parcelable out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getParcelableExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getSerializableExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ Serializable out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getSerializableExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getStringArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ String[] out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getStringArrayExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getStringArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ ArrayList out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getStringArrayListExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;getStringExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value"
+ String out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out = in.getStringExtra(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putCharSequenceArrayListExtra(null, null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putCharSequenceArrayListExtra(in, null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ ArrayList in = (ArrayList)source();
+ out.putCharSequenceArrayListExtra(null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, false);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, 0L);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, 0.0f);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, 0.0);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, 0);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (short[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (short)0);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (long[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (int[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (float[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (double[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (char[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (byte[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (byte)0);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (boolean[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (String[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (String)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (Serializable)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (Parcelable[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (Parcelable)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (CharSequence[])null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (CharSequence)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, (Bundle)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtra((String)null, '\0');
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, false);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, 0L);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, 0.0f);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, 0.0);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, 0);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (short[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (short)0);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (long[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (int[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (float[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (double[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (char[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (byte[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (byte)0);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (boolean[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (String[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (String)null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (Serializable)null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (Parcelable[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (Parcelable)null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (CharSequence[])null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (CharSequence)null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, (Bundle)null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra(in, '\0');
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ short[] in = (short[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ short in = (short)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ long[] in = (long[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ long in = (long)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ int[] in = (int[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ int in = (int)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ float[] in = (float[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ float in = (float)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ double[] in = (double[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ double in = (double)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ char[] in = (char[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ char in = (char)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ byte[] in = (byte[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ byte in = (byte)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ boolean[] in = (boolean[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ boolean in = (boolean)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String[] in = (String[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Serializable in = (Serializable)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Parcelable[] in = (Parcelable[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Parcelable in = (Parcelable)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ CharSequence[] in = (CharSequence[])source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ CharSequence in = (CharSequence)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Bundle in = (Bundle)source();
+ out.putExtra((String)null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtras;(Bundle);;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtras((Bundle)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtras;(Bundle);;MapKey of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Bundle in = (Bundle)newWithMapKeyDefault(source());
+ out.putExtras(in);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtras;(Bundle);;MapValue of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out.putExtras(in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtras;(Intent);;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putExtras((Intent)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtras;(Intent);;MapKey of SyntheticField[android.content.Intent.extras] of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapKeyDefault(source()));
+ out.putExtras(in);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putExtras;(Intent);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out.putExtras(in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putIntegerArrayListExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putIntegerArrayListExtra(null, null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putIntegerArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putIntegerArrayListExtra(in, null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putParcelableArrayListExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putParcelableArrayListExtra(null, null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putParcelableArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putParcelableArrayListExtra(in, null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putParcelableArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ ArrayList in = (ArrayList)source();
+ out.putParcelableArrayListExtra(null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putStringArrayListExtra;;;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.putStringArrayListExtra(null, null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putStringArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ String in = (String)source();
+ out.putStringArrayListExtra(in, null);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;putStringArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ ArrayList in = (ArrayList)source();
+ out.putStringArrayListExtra(null, in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;replaceExtras;(Bundle);;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.replaceExtras((Bundle)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;replaceExtras;(Bundle);;MapKey of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Bundle in = (Bundle)newWithMapKeyDefault(source());
+ out.replaceExtras(in);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;replaceExtras;(Bundle);;MapValue of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out.replaceExtras(in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;replaceExtras;(Intent);;Argument[-1];ReturnValue;value"
+ Intent out = null;
+ Intent in = (Intent)source();
+ out = in.replaceExtras((Intent)null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;replaceExtras;(Intent);;MapKey of SyntheticField[android.content.Intent.extras] of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapKeyDefault(source()));
+ out.replaceExtras(in);
+ sink(getMapKeyDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.content;Intent;true;replaceExtras;(Intent);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value"
+ Intent out = null;
+ Intent in = (Intent)newWithIntent_extrasDefault(newWithMapValueDefault(source()));
+ out.replaceExtras(in);
+ sink(getMapValueDefault(getIntent_extrasDefault(out))); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;get;(String);;MapValue of Argument[-1];ReturnValue;value"
+ Object out = null;
+ BaseBundle in = (BaseBundle)newWithMapValueDefault(source());
+ out = in.get(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;getString;(String);;MapValue of Argument[-1];ReturnValue;value"
+ String out = null;
+ BaseBundle in = (BaseBundle)newWithMapValueDefault(source());
+ out = in.getString(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;getString;(String,String);;Argument[1];ReturnValue;value"
+ String out = null;
+ String in = (String)source();
+ BaseBundle instance = null;
+ out = instance.getString(null, in);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;getString;(String,String);;MapValue of Argument[-1];ReturnValue;value"
+ String out = null;
+ BaseBundle in = (BaseBundle)newWithMapValueDefault(source());
+ out = in.getString(null, null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;getStringArray;(String);;MapValue of Argument[-1];ReturnValue;value"
+ String[] out = null;
+ BaseBundle in = (BaseBundle)newWithMapValueDefault(source());
+ out = in.getStringArray(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;keySet;();;MapKey of Argument[-1];Element of ReturnValue;value"
+ Set out = null;
+ BaseBundle in = (BaseBundle)newWithMapKeyDefault(source());
+ out = in.keySet();
+ sink(getElement(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putAll;(PersistableBundle);;MapKey of Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ PersistableBundle in = (PersistableBundle)newWithMapKeyDefault(source());
+ out.putAll(in);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putAll;(PersistableBundle);;MapValue of Argument[0];MapValue of Argument[-1];value"
+ BaseBundle out = null;
+ PersistableBundle in = (PersistableBundle)newWithMapValueDefault(source());
+ out.putAll(in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putBoolean;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putBoolean(in, false);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putBooleanArray;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putBooleanArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putDouble;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putDouble(in, 0.0);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putDoubleArray;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putDoubleArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putInt;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putInt(in, 0);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putIntArray;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putIntArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putLong;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putLong(in, 0L);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putLongArray;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putLongArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putString;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putString(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putString;;;Argument[1];MapValue of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putString(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putStringArray;;;Argument[0];MapKey of Argument[-1];value"
+ BaseBundle out = null;
+ String in = (String)source();
+ out.putStringArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;BaseBundle;true;putStringArray;;;Argument[1];MapValue of Argument[-1];value"
+ BaseBundle out = null;
+ String[] in = (String[])source();
+ out.putStringArray(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getBinder;(String);;MapValue of Argument[-1];ReturnValue;value"
+ IBinder out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getBinder(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getBundle;(String);;MapValue of Argument[-1];ReturnValue;value"
+ Bundle out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getBundle(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getByteArray;(String);;MapValue of Argument[-1];ReturnValue;value"
+ byte[] out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getByteArray(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getCharArray;(String);;MapValue of Argument[-1];ReturnValue;value"
+ char[] out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getCharArray(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getCharSequence;(String);;MapValue of Argument[-1];ReturnValue;value"
+ CharSequence out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getCharSequence(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getCharSequence;(String,CharSequence);;Argument[1];ReturnValue;value"
+ CharSequence out = null;
+ CharSequence in = (CharSequence)source();
+ Bundle instance = null;
+ out = instance.getCharSequence(null, in);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getCharSequence;(String,CharSequence);;MapValue of Argument[-1];ReturnValue;value"
+ CharSequence out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getCharSequence(null, null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getCharSequenceArray;(String);;MapValue of Argument[-1];ReturnValue;value"
+ CharSequence[] out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getCharSequenceArray(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getCharSequenceArrayList;(String);;MapValue of Argument[-1];ReturnValue;value"
+ ArrayList out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getCharSequenceArrayList(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getParcelable;(String);;MapValue of Argument[-1];ReturnValue;value"
+ Parcelable out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getParcelable(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getParcelableArray;(String);;MapValue of Argument[-1];ReturnValue;value"
+ Parcelable[] out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getParcelableArray(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getParcelableArrayList;(String);;MapValue of Argument[-1];ReturnValue;value"
+ ArrayList out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getParcelableArrayList(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getSerializable;(String);;MapValue of Argument[-1];ReturnValue;value"
+ Serializable out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getSerializable(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getSparseParcelableArray;(String);;MapValue of Argument[-1];ReturnValue;value"
+ SparseArray out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getSparseParcelableArray(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;getStringArrayList;(String);;MapValue of Argument[-1];ReturnValue;value"
+ ArrayList out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out = in.getStringArrayList(null);
+ sink(out); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putAll;(Bundle);;MapKey of Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ Bundle in = (Bundle)newWithMapKeyDefault(source());
+ out.putAll(in);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putAll;(Bundle);;MapValue of Argument[0];MapValue of Argument[-1];value"
+ Bundle out = null;
+ Bundle in = (Bundle)newWithMapValueDefault(source());
+ out.putAll(in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putBinder;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putBinder(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putBinder;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ IBinder in = (IBinder)source();
+ out.putBinder(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putBundle;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putBundle(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putBundle;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ Bundle in = (Bundle)source();
+ out.putBundle(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putByte;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putByte(in, (byte)0);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putByteArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putByteArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putByteArray;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ byte[] in = (byte[])source();
+ out.putByteArray(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putChar;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putChar(in, '\0');
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putCharArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharArray;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ char[] in = (char[])source();
+ out.putCharArray(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharSequence;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putCharSequence(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharSequence;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ CharSequence in = (CharSequence)source();
+ out.putCharSequence(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharSequenceArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putCharSequenceArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharSequenceArray;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ CharSequence[] in = (CharSequence[])source();
+ out.putCharSequenceArray(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharSequenceArrayList;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putCharSequenceArrayList(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putCharSequenceArrayList;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ ArrayList in = (ArrayList)source();
+ out.putCharSequenceArrayList(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putFloat;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putFloat(in, 0.0f);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putFloatArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putFloatArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putIntegerArrayList;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putIntegerArrayList(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putParcelable;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putParcelable(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putParcelable;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ Parcelable in = (Parcelable)source();
+ out.putParcelable(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putParcelableArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putParcelableArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putParcelableArray;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ Parcelable[] in = (Parcelable[])source();
+ out.putParcelableArray(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putParcelableArrayList;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putParcelableArrayList(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putParcelableArrayList;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ ArrayList in = (ArrayList)source();
+ out.putParcelableArrayList(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putSerializable;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putSerializable(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putSerializable;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ Serializable in = (Serializable)source();
+ out.putSerializable(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putShort;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putShort(in, (short)0);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putShortArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putShortArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putSize;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putSize(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putSizeF;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putSizeF(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putSparseParcelableArray;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putSparseParcelableArray(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putSparseParcelableArray;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ SparseArray in = (SparseArray)source();
+ out.putSparseParcelableArray(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putStringArrayList;;;Argument[0];MapKey of Argument[-1];value"
+ Bundle out = null;
+ String in = (String)source();
+ out.putStringArrayList(in, null);
+ sink(getMapKeyDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;putStringArrayList;;;Argument[1];MapValue of Argument[-1];value"
+ Bundle out = null;
+ ArrayList in = (ArrayList)source();
+ out.putStringArrayList(null, in);
+ sink(getMapValueDefault(out)); // $ hasValueFlow
+ }
+ {
+ // "android.os;Bundle;true;readFromParcel;;;Argument[0];MapKey of Argument[-1];taint"
+ Bundle out = null;
+ Parcel in = (Parcel)source();
+ out.readFromParcel(in);
+ sink(getMapKeyDefault(out)); // $ hasTaintFlow
+ }
+ {
+ // "android.os;Bundle;true;readFromParcel;;;Argument[0];MapValue of Argument[-1];taint"
+ Bundle out = null;
+ Parcel in = (Parcel)source();
+ out.readFromParcel(in);
+ sink(getMapValueDefault(out)); // $ hasTaintFlow
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/java/ql/test/library-tests/frameworks/android/intent/models.csv b/java/ql/test/library-tests/frameworks/android/intent/models.csv
new file mode 100644
index 00000000000..83be9bbcb0c
--- /dev/null
+++ b/java/ql/test/library-tests/frameworks/android/intent/models.csv
@@ -0,0 +1,114 @@
+android.os;BaseBundle;true;get;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;BaseBundle;true;getString;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;BaseBundle;true;getString;(String,String);;MapValue of Argument[-1];ReturnValue;value
+android.os;BaseBundle;true;getString;(String,String);;Argument[1];ReturnValue;value
+android.os;BaseBundle;true;getStringArray;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;BaseBundle;true;keySet;();;MapKey of Argument[-1];Element of ReturnValue;value
+android.os;BaseBundle;true;putAll;(PersistableBundle);;MapKey of Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putAll;(PersistableBundle);;MapValue of Argument[0];MapValue of Argument[-1];value
+android.os;BaseBundle;true;putBoolean;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putBooleanArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putDouble;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putDoubleArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putInt;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putIntArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putLong;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putLongArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putString;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putString;;;Argument[1];MapValue of Argument[-1];value
+android.os;BaseBundle;true;putStringArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;BaseBundle;true;putStringArray;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;getBinder;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getBundle;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getByteArray;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getCharArray;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getCharSequence;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getCharSequence;(String,CharSequence);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getCharSequence;(String,CharSequence);;Argument[1];ReturnValue;value
+android.os;Bundle;true;getCharSequenceArray;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getCharSequenceArrayList;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getParcelable;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getParcelableArray;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getParcelableArrayList;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getSerializable;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getSparseParcelableArray;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;getStringArrayList;(String);;MapValue of Argument[-1];ReturnValue;value
+android.os;Bundle;true;putAll;(Bundle);;MapKey of Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putAll;(Bundle);;MapValue of Argument[0];MapValue of Argument[-1];value
+android.os;Bundle;true;putBinder;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putBinder;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putBundle;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putBundle;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putByte;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putByteArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putByteArray;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putChar;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putCharArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putCharArray;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putCharSequence;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putCharSequence;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putCharSequenceArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putCharSequenceArray;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putCharSequenceArrayList;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putCharSequenceArrayList;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putFloat;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putFloatArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putIntegerArrayList;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putParcelable;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putParcelable;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putParcelableArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putParcelableArray;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putParcelableArrayList;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putParcelableArrayList;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putSerializable;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putSerializable;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putShort;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putShortArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putSize;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putSizeF;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putSparseParcelableArray;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putSparseParcelableArray;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;putStringArrayList;;;Argument[0];MapKey of Argument[-1];value
+android.os;Bundle;true;putStringArrayList;;;Argument[1];MapValue of Argument[-1];value
+android.os;Bundle;true;readFromParcel;;;Argument[0];MapKey of Argument[-1];taint
+android.os;Bundle;true;readFromParcel;;;Argument[0];MapValue of Argument[-1];taint
+android.content;Intent;true;getExtras;();;SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getBundleExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getByteArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getCharArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getCharSequenceArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getCharSequenceArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getCharSequenceExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getParcelableArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getParcelableArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getParcelableExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getSerializableExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getStringArrayExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getStringArrayListExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;getStringExtra;(String);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];ReturnValue;value
+android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putCharSequenceArrayListExtra;;;Argument[-1];ReturnValue;value
+android.content;Intent;true;putExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putExtra;;;Argument[-1];ReturnValue;value
+android.content;Intent;true;putIntegerArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putIntegerArrayListExtra;;;Argument[-1];ReturnValue;value
+android.content;Intent;true;putParcelableArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putParcelableArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putParcelableArrayListExtra;;;Argument[-1];ReturnValue;value
+android.content;Intent;true;putStringArrayListExtra;;;Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putStringArrayListExtra;;;Argument[1];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putStringArrayListExtra;;;Argument[-1];ReturnValue;value
+android.content;Intent;true;putExtras;(Bundle);;MapKey of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putExtras;(Bundle);;MapValue of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putExtras;(Bundle);;Argument[-1];ReturnValue;value
+android.content;Intent;true;putExtras;(Intent);;MapKey of SyntheticField[android.content.Intent.extras] of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putExtras;(Intent);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;putExtras;(Intent);;Argument[-1];ReturnValue;value
+android.content;Intent;true;replaceExtras;(Bundle);;MapKey of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;replaceExtras;(Bundle);;MapValue of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;replaceExtras;(Bundle);;Argument[-1];ReturnValue;value
+android.content;Intent;true;replaceExtras;(Intent);;MapKey of SyntheticField[android.content.Intent.extras] of Argument[0];MapKey of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;replaceExtras;(Intent);;MapValue of SyntheticField[android.content.Intent.extras] of Argument[0];MapValue of SyntheticField[android.content.Intent.extras] of Argument[-1];value
+android.content;Intent;true;replaceExtras;(Intent);;Argument[-1];ReturnValue;value
\ No newline at end of file
diff --git a/java/ql/test/library-tests/frameworks/android/intent/options b/java/ql/test/library-tests/frameworks/android/intent/options
new file mode 100644
index 00000000000..020a4a1cebb
--- /dev/null
+++ b/java/ql/test/library-tests/frameworks/android/intent/options
@@ -0,0 +1 @@
+//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/android
diff --git a/java/ql/test/library-tests/frameworks/android/intent/test.expected b/java/ql/test/library-tests/frameworks/android/intent/test.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/java/ql/test/library-tests/frameworks/android/intent/test.ql b/java/ql/test/library-tests/frameworks/android/intent/test.ql
new file mode 100644
index 00000000000..e89ac1ba290
--- /dev/null
+++ b/java/ql/test/library-tests/frameworks/android/intent/test.ql
@@ -0,0 +1,17 @@
+import java
+import TestUtilities.InlineFlowTest
+
+class SummaryModelTest extends SummaryModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ //"package;type;overrides;name;signature;ext;inputspec;outputspec;kind",
+ "generatedtest;Test;false;newWithMapKeyDefault;(Object);;Argument[0];MapKey of ReturnValue;value",
+ "generatedtest;Test;false;getMapKeyDefault;(Object);;MapKey of Argument[0];ReturnValue;value",
+ "generatedtest;Test;false;newWithMapValueDefault;(Object);;Argument[0];MapValue of ReturnValue;value",
+ "generatedtest;Test;false;getMapValueDefault;(Object);;MapValue of Argument[0];ReturnValue;value",
+ "generatedtest;Test;false;newWithIntent_extrasDefault;(Object);;Argument[0];SyntheticField[android.content.Intent.extras] of ReturnValue;value",
+ "generatedtest;Test;false;getIntent_extrasDefault;(Object);;SyntheticField[android.content.Intent.extras] of Argument[0];ReturnValue;value"
+ ]
+ }
+}
diff --git a/java/ql/test/stubs/android/android/accounts/Account.java b/java/ql/test/stubs/android/android/accounts/Account.java
new file mode 100644
index 00000000000..806f076452e
--- /dev/null
+++ b/java/ql/test/stubs/android/android/accounts/Account.java
@@ -0,0 +1,21 @@
+// Generated automatically from android.accounts.Account for testing purposes
+
+package android.accounts;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class Account implements Parcelable
+{
+ protected Account() {}
+ public Account(Parcel p0){}
+ public Account(String p0, String p1){}
+ public String toString(){ return null; }
+ public boolean equals(Object p0){ return false; }
+ public final String name = null;
+ public final String type = null;
+ public int describeContents(){ return 0; }
+ public int hashCode(){ return 0; }
+ public static Parcelable.Creator CREATOR = null;
+ public void writeToParcel(Parcel p0, int p1){}
+}
diff --git a/java/ql/test/stubs/android/android/content/BroadcastReceiver.java b/java/ql/test/stubs/android/android/content/BroadcastReceiver.java
index 1d73018c96d..93375c04d85 100644
--- a/java/ql/test/stubs/android/android/content/BroadcastReceiver.java
+++ b/java/ql/test/stubs/android/android/content/BroadcastReceiver.java
@@ -1,255 +1,45 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+// Generated automatically from android.content.BroadcastReceiver for testing purposes
+
package android.content;
+import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
-/**
- * Base class for code that will receive intents sent by sendBroadcast().
- *
- *
If you don't need to send broadcasts across applications, consider using
- * this class with {@link android.support.v4.content.LocalBroadcastManager} instead
- * of the more general facilities described below. This will give you a much
- * more efficient implementation (no cross-process communication needed) and allow
- * you to avoid thinking about any security issues related to other applications
- * being able to receive or send your broadcasts.
- *
- *
You can either dynamically register an instance of this class with
- * {@link Context#registerReceiver Context.registerReceiver()}
- * or statically publish an implementation through the
- * {@link android.R.styleable#AndroidManifestReceiver <receiver>}
- * tag in your AndroidManifest.xml.
- *
- *
Note:
- * If registering a receiver in your
- * {@link android.app.Activity#onResume() Activity.onResume()}
- * implementation, you should unregister it in
- * {@link android.app.Activity#onPause() Activity.onPause()}.
- * (You won't receive intents when paused,
- * and this will cut down on unnecessary system overhead). Do not unregister in
- * {@link android.app.Activity#onSaveInstanceState(android.os.Bundle) Activity.onSaveInstanceState()},
- * because this won't be called if the user moves back in the history
- * stack.
- *
- *
There are two major classes of broadcasts that can be received:
- *
- *
Normal broadcasts (sent with {@link Context#sendBroadcast(Intent)
- * Context.sendBroadcast}) are completely asynchronous. All receivers of the
- * broadcast are run in an undefined order, often at the same time. This is
- * more efficient, but means that receivers cannot use the result or abort
- * APIs included here.
- *
Ordered broadcasts (sent with {@link Context#sendOrderedBroadcast(Intent, String)
- * Context.sendOrderedBroadcast}) are delivered to one receiver at a time.
- * As each receiver executes in turn, it can propagate a result to the next
- * receiver, or it can completely abort the broadcast so that it won't be passed
- * to other receivers. The order receivers run in can be controlled with the
- * {@link android.R.styleable#AndroidManifestIntentFilter_priority
- * android:priority} attribute of the matching intent-filter; receivers with
- * the same priority will be run in an arbitrary order.
- *
- *
- *
Even in the case of normal broadcasts, the system may in some
- * situations revert to delivering the broadcast one receiver at a time. In
- * particular, for receivers that may require the creation of a process, only
- * one will be run at a time to avoid overloading the system with new processes.
- * In this situation, however, the non-ordered semantics hold: these receivers still
- * cannot return results or abort their broadcast.
- *
- *
Note that, although the Intent class is used for sending and receiving
- * these broadcasts, the Intent broadcast mechanism here is completely separate
- * from Intents that are used to start Activities with
- * {@link Context#startActivity Context.startActivity()}.
- * There is no way for a BroadcastReceiver
- * to see or capture Intents used with startActivity(); likewise, when
- * you broadcast an Intent, you will never find or start an Activity.
- * These two operations are semantically very different: starting an
- * Activity with an Intent is a foreground operation that modifies what the
- * user is currently interacting with; broadcasting an Intent is a background
- * operation that the user is not normally aware of.
- *
- *
The BroadcastReceiver class (when launched as a component through
- * a manifest's {@link android.R.styleable#AndroidManifestReceiver <receiver>}
- * tag) is an important part of an
- * application's overall lifecycle.
For information about how to use this class to receive and resolve intents, read the
- * Intents and Intent Filters
- * developer guide.
- *
- *
- *
- *
Security
- *
- *
Receivers used with the {@link Context} APIs are by their nature a
- * cross-application facility, so you must consider how other applications
- * may be able to abuse your use of them. Some things to consider are:
- *
- *
- *
The Intent namespace is global. Make sure that Intent action names and
- * other strings are written in a namespace you own, or else you may inadvertantly
- * conflict with other applications.
- *
When you use {@link Context#registerReceiver(BroadcastReceiver, IntentFilter)},
- * any application may send broadcasts to that registered receiver. You can
- * control who can send broadcasts to it through permissions described below.
- *
When you publish a receiver in your application's manifest and specify
- * intent-filters for it, any other application can send broadcasts to it regardless
- * of the filters you specify. To prevent others from sending to it, make it
- * unavailable to them with android:exported="false".
- *
When you use {@link Context#sendBroadcast(Intent)} or related methods,
- * normally any other application can receive these broadcasts. You can control who
- * can receive such broadcasts through permissions described below. Alternatively,
- * starting with {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH}, you
- * can also safely restrict the broadcast to a single application with
- * {@link Intent#setPackage(String) Intent.setPackage}
- *
- *
- *
None of these issues exist when using
- * {@link android.support.v4.content.LocalBroadcastManager}, since intents
- * broadcast it never go outside of the current process.
- *
- *
Access permissions can be enforced by either the sender or receiver
- * of a broadcast.
- *
- *
To enforce a permission when sending, you supply a non-null
- * permission argument to
- * {@link Context#sendBroadcast(Intent, String)} or
- * {@link Context#sendOrderedBroadcast(Intent, String, BroadcastReceiver, android.os.Handler, int, String, Bundle)}.
- * Only receivers who have been granted this permission
- * (by requesting it with the
- * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
- * tag in their AndroidManifest.xml) will be able to receive
- * the broadcast.
- *
- *
To enforce a permission when receiving, you supply a non-null
- * permission when registering your receiver -- either when calling
- * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter, String, android.os.Handler)}
- * or in the static
- * {@link android.R.styleable#AndroidManifestReceiver <receiver>}
- * tag in your AndroidManifest.xml. Only broadcasters who have
- * been granted this permission (by requesting it with the
- * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>}
- * tag in their AndroidManifest.xml) will be able to send an
- * Intent to the receiver.
- *
- *
See the Security and Permissions
- * document for more information on permissions and security in general.
- *
- *
- *
Receiver Lifecycle
- *
- *
A BroadcastReceiver object is only valid for the duration of the call
- * to {@link #onReceive}. Once your code returns from this function,
- * the system considers the object to be finished and no longer active.
- *
- *
This has important repercussions to what you can do in an
- * {@link #onReceive} implementation: anything that requires asynchronous
- * operation is not available, because you will need to return from the
- * function to handle the asynchronous operation, but at that point the
- * BroadcastReceiver is no longer active and thus the system is free to kill
- * its process before the asynchronous operation completes.
- *
- *
In particular, you may not show a dialog or bind to a service from
- * within a BroadcastReceiver. For the former, you should instead use the
- * {@link android.app.NotificationManager} API. For the latter, you can
- * use {@link android.content.Context#startService Context.startService()} to
- * send a command to the service.
- *
- *
- *
Process Lifecycle
- *
- *
A process that is currently executing a BroadcastReceiver (that is,
- * currently running the code in its {@link #onReceive} method) is
- * considered to be a foreground process and will be kept running by the
- * system except under cases of extreme memory pressure.
- *
- *
Once you return from onReceive(), the BroadcastReceiver is no longer
- * active, and its hosting process is only as important as any other application
- * components that are running in it. This is especially important because if
- * that process was only hosting the BroadcastReceiver (a common case for
- * applications that the user has never or not recently interacted with), then
- * upon returning from onReceive() the system will consider its process
- * to be empty and aggressively kill it so that resources are available for other
- * more important processes.
- *
- *
This means that for longer-running operations you will often use
- * a {@link android.app.Service} in conjunction with a BroadcastReceiver to keep
- * the containing process active for the entire time of your operation.
- */
-public abstract class BroadcastReceiver {
-
- /**
- * State for a result that is pending for a broadcast receiver. Returned
- * by {@link BroadcastReceiver#goAsync() goAsync()}
- * while in {@link BroadcastReceiver#onReceive BroadcastReceiver.onReceive()}.
- * This allows you to return from onReceive() without having the broadcast
- * terminate; you must call {@link #finish()} once you are done with the
- * broadcast. This allows you to process the broadcast off of the main
- * thread of your app.
- *
- *
Note on threading: the state inside of this class is not itself
- * thread-safe, however you can use it from any thread if you properly
- * sure that you do not have races. Typically this means you will hand
- * the entire object to another thread, which will be solely responsible
- * for setting any results and finally calling {@link #finish()}.
- */
-
- public BroadcastReceiver() {
+import android.os.IBinder;
+
+abstract public class BroadcastReceiver
+{
+ public BroadcastReceiver(){}
+ public IBinder peekService(Context p0, Intent p1){ return null; }
+ public abstract void onReceive(Context p0, Intent p1);
+ public final BroadcastReceiver.PendingResult goAsync(){ return null; }
+ public final Bundle getResultExtras(boolean p0){ return null; }
+ public final String getResultData(){ return null; }
+ public final boolean getAbortBroadcast(){ return false; }
+ public final boolean getDebugUnregister(){ return false; }
+ public final boolean isInitialStickyBroadcast(){ return false; }
+ public final boolean isOrderedBroadcast(){ return false; }
+ public final int getResultCode(){ return 0; }
+ public final void abortBroadcast(){}
+ public final void clearAbortBroadcast(){}
+ public final void setDebugUnregister(boolean p0){}
+ public final void setOrderedHint(boolean p0){}
+ public final void setResult(int p0, String p1, Bundle p2){}
+ public final void setResultCode(int p0){}
+ public final void setResultData(String p0){}
+ public final void setResultExtras(Bundle p0){}
+ static public class PendingResult
+ {
+ public final Bundle getResultExtras(boolean p0){ return null; }
+ public final String getResultData(){ return null; }
+ public final boolean getAbortBroadcast(){ return false; }
+ public final int getResultCode(){ return 0; }
+ public final void abortBroadcast(){}
+ public final void clearAbortBroadcast(){}
+ public final void finish(){}
+ public final void setResult(int p0, String p1, Bundle p2){}
+ public final void setResultCode(int p0){}
+ public final void setResultData(String p0){}
+ public final void setResultExtras(Bundle p0){}
}
- /**
- * This method is called when the BroadcastReceiver is receiving an Intent
- * broadcast. During this time you can use the other methods on
- * BroadcastReceiver to view/modify the current result values. This method
- * is always called within the main thread of its process, unless you
- * explicitly asked for it to be scheduled on a different thread using
- * {@link android.content.Context#registerReceiver(BroadcastReceiver,
- * IntentFilter, String, android.os.Handler)}. When it runs on the main
- * thread you should
- * never perform long-running operations in it (there is a timeout of
- * 10 seconds that the system allows before considering the receiver to
- * be blocked and a candidate to be killed). You cannot launch a popup dialog
- * in your implementation of onReceive().
- *
- *
If this BroadcastReceiver was launched through a <receiver> tag,
- * then the object is no longer alive after returning from this
- * function. This means you should not perform any operations that
- * return a result to you asynchronously -- in particular, for interacting
- * with services, you should use
- * {@link Context#startService(Intent)} instead of
- * {@link Context#bindService(Intent, ServiceConnection, int)}. If you wish
- * to interact with a service that is already running, you can use
- * {@link #peekService}.
- *
- *