mirror of
https://github.com/github/codeql.git
synced 2026-07-20 18:58:36 +02:00
Merge branch 'main' into promote-clickhouse
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
lgtm,codescanning
|
||||
* Added modeling of sources/sinks when using the `aiohttp.web` web framework.
|
||||
@@ -4,7 +4,9 @@
|
||||
* @kind problem
|
||||
* @tags security
|
||||
* correctness
|
||||
* security/cwe/cwe-78
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/use-of-input
|
||||
|
||||
@@ -9,7 +9,7 @@ information being thrown away.</p>
|
||||
|
||||
<p>A return value is considered to be trivial if it is <code>None</code> or it is a parameter (parameters, usually <code>self</code> are often
|
||||
returned to assist with method chaining, but can be ignored).
|
||||
A return value is also assumed to be trivial if it is ignored for 75% or more of calls.
|
||||
A return value is also assumed to be trivial if it is ignored for more than 25% of calls.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
* and is therefore associated with security risks.
|
||||
* @kind problem
|
||||
* @tags security
|
||||
* external/cwe/cwe-200
|
||||
* @problem.severity error
|
||||
* @security-severity 3.6
|
||||
* @sub-severity low
|
||||
* @precision high
|
||||
* @id py/bind-socket-all-network-interfaces
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* @kind path-problem
|
||||
* @precision low
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @tags security external/cwe/cwe-20
|
||||
*/
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Matching a URL or hostname against a regular expression that contains an unescaped dot as part of the hostname might match more hostnames than expected.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.9
|
||||
* @precision high
|
||||
* @id py/incomplete-hostname-regexp
|
||||
* @tags correctness
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Security checks on the substrings of an unparsed URL are often vulnerable to bypassing.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.9
|
||||
* @precision high
|
||||
* @id py/incomplete-url-substring-sanitization
|
||||
* @tags correctness
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Accessing paths influenced by users can allow an attacker to access unexpected resources.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.4
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/path-injection
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* @kind path-problem
|
||||
* @id py/tarslip
|
||||
* @problem.severity error
|
||||
* @security-severity 6.4
|
||||
* @precision medium
|
||||
* @tags security
|
||||
* external/cwe/cwe-022
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* user to change the meaning of the command.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/command-line-injection
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* cause a cross-site scripting vulnerability.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 2.9
|
||||
* @precision medium
|
||||
* @id py/jinja2/autoescape-false
|
||||
* @tags security
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* allows for a cross-site scripting vulnerability.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 2.9
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/reflective-xss
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* malicious SQL code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.4
|
||||
* @precision high
|
||||
* @id py/sql-injection
|
||||
* @tags security
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* code execution.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 10.0
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @id py/code-injection
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* developing a subsequent exploit.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 3.6
|
||||
* @precision high
|
||||
* @id py/stack-trace-exposure
|
||||
* @tags security
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Running a Flask app in debug mode may allow an attacker to run arbitrary code through the Werkzeug debugger.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 6.4
|
||||
* @precision high
|
||||
* @id py/flask-debug
|
||||
* @tags security
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Accepting unknown host keys can allow man-in-the-middle attacks.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.2
|
||||
* @precision high
|
||||
* @id py/paramiko-missing-host-key-validation
|
||||
* @tags security
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Making a request without certificate validation can allow man-in-the-middle attacks.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.2
|
||||
* @precision medium
|
||||
* @id py/request-without-cert-validation
|
||||
* @tags security
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* expose it to an attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @precision high
|
||||
* @id py/clear-text-logging-sensitive-data
|
||||
* @tags security
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* attacker.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @precision high
|
||||
* @id py/clear-text-storage-sensitive-data
|
||||
* @tags security
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Use of a cryptographic key that is too small may allow the encryption to be broken.
|
||||
* @kind problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.2
|
||||
* @precision high
|
||||
* @id py/weak-crypto-key
|
||||
* @tags security
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Using broken or weak cryptographic algorithms can compromise security.
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.2
|
||||
* @precision high
|
||||
* @id py/weak-cryptographic-algorithm
|
||||
* @tags security
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* @id py/insecure-default-protocol
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.2
|
||||
* @precision high
|
||||
* @tags security
|
||||
* external/cwe/cwe-327
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @id py/insecure-protocol
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.2
|
||||
* @precision high
|
||||
* @tags security
|
||||
* external/cwe/cwe-327
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Using broken or weak cryptographic hashing algorithms can compromise security.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.9
|
||||
* @precision high
|
||||
* @id py/weak-sensitive-data-hashing
|
||||
* @tags security
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind problem
|
||||
* @id py/insecure-temporary-file
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @tags external/cwe/cwe-377
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind path-problem
|
||||
* @id py/unsafe-deserialization
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @sub-severity high
|
||||
* @precision high
|
||||
* @tags external/cwe/cwe-502
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* may cause redirection to malicious web sites.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 2.7
|
||||
* @sub-severity low
|
||||
* @id py/url-redirection
|
||||
* @tags security
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @kind problem
|
||||
* @id py/overly-permissive-file
|
||||
* @problem.severity warning
|
||||
* @security-severity 5.9
|
||||
* @sub-severity high
|
||||
* @precision medium
|
||||
* @tags external/cwe/cwe-732
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* @description Credentials are hard coded in the source code of the application.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @security-severity 5.9
|
||||
* @precision medium
|
||||
* @id py/hardcoded-credentials
|
||||
* @tags security
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* @tags security
|
||||
* correctness
|
||||
* @problem.severity error
|
||||
* @security-severity 4.2
|
||||
* @sub-severity high
|
||||
* @precision low
|
||||
* @id py/use-of-exec
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* database. This query counts the lines of code, excluding whitespace or comments.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
* lines-of-code
|
||||
* @id py/summary/lines-of-code
|
||||
*/
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* be counted as user written code.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
* lines-of-code
|
||||
* @id py/summary/lines-of-user-code
|
||||
*/
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>If an LDAP query or DN is built using string concatenation or string formatting, and the
|
||||
components of the concatenation include user input without any proper sanitization, a user
|
||||
is likely to be able to run malicious LDAP queries.</p>
|
||||
</overview>
|
||||
|
||||
<recommendation>
|
||||
<p>If user input must be included in an LDAP query or DN, it should be escaped to
|
||||
avoid a malicious user providing special characters that change the meaning
|
||||
of the query. In Python2, user input should be escaped with <code>ldap.dn.escape_dn_chars</code>
|
||||
or <code>ldap.filter.escape_filter_chars</code>, while in Python3, user input should be escaped with
|
||||
<code>ldap3.utils.dn.escape_rdn</code> or <code>ldap3.utils.conv.escape_filter_chars</code>
|
||||
depending on the component tainted by the user. A good practice is to escape filter characters
|
||||
that could change the meaning of the query (https://tools.ietf.org/search/rfc4515#section-3).</p>
|
||||
</recommendation>
|
||||
|
||||
<example>
|
||||
<p>In the following examples, the code accepts both <code>username</code> and <code>dc</code> from the user,
|
||||
which it then uses to build a LDAP query and DN.</p>
|
||||
|
||||
<p>The first and the second example uses the unsanitized user input directly
|
||||
in the search filter and DN for the LDAP query.
|
||||
A malicious user could provide special characters to change the meaning of these
|
||||
components, and search for a completely different set of values.</p>
|
||||
|
||||
<sample src="examples/example_bad1.py" />
|
||||
<sample src="examples/example_bad2.py" />
|
||||
|
||||
<p>In the third and four example, the input provided by the user is sanitized before it is included in the search filter or DN.
|
||||
This ensures the meaning of the query cannot be changed by a malicious user.</p>
|
||||
|
||||
<sample src="examples/example_good1.py" />
|
||||
<sample src="examples/example_good2.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html">LDAP Injection Prevention Cheat Sheet</a>.</li>
|
||||
<li>OWASP: <a href="https://owasp.org/www-community/attacks/LDAP_Injection">LDAP Injection</a>.</li>
|
||||
<li>SonarSource: <a href="https://rules.sonarsource.com/python/RSPEC-2078">RSPEC-2078</a>.</li>
|
||||
<li>Python2: <a href="https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html">LDAP Documentation</a>.</li>
|
||||
<li>Python3: <a href="https://ldap3.readthedocs.io/en/latest/">LDAP Documentation</a>.</li>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/LDAP_injection">LDAP injection</a>.</li>
|
||||
<li>BlackHat: <a href="https://www.blackhat.com/presentations/bh-europe-08/Alonso-Parada/Whitepaper/bh-eu-08-alonso-parada-WP.pdf">LDAP Injection and Blind LDAP Injection</a>.</li>
|
||||
<li>LDAP: <a href="https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/">Understanding and Defending Against LDAP Injection Attacks</a>.</li>
|
||||
</references>
|
||||
</qhelp>
|
||||
21
python/ql/src/experimental/Security/CWE-090/LDAPInjection.ql
Normal file
21
python/ql/src/experimental/Security/CWE-090/LDAPInjection.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name LDAP query built from user-controlled sources
|
||||
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
|
||||
* malicious LDAP code by the user.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @id py/ldap-injection
|
||||
* @tags experimental
|
||||
* security
|
||||
* external/cwe/cwe-090
|
||||
*/
|
||||
|
||||
// Determine precision above
|
||||
import python
|
||||
import experimental.semmle.python.security.injection.LDAP
|
||||
import DataFlow::PathGraph
|
||||
|
||||
from LDAPInjectionFlowConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
|
||||
where config.hasFlowPath(source, sink)
|
||||
select sink.getNode(), source, sink, "$@ LDAP query parameter comes from $@.", sink.getNode(),
|
||||
"This", source.getNode(), "a user-provided value"
|
||||
@@ -0,0 +1,15 @@
|
||||
from flask import request, Flask
|
||||
import ldap
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
ldap_connection = ldap.initialize("ldap://127.0.0.1")
|
||||
user = ldap_connection.search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, search_filter)
|
||||
@@ -0,0 +1,15 @@
|
||||
from flask import request, Flask
|
||||
import ldap3
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
srv = ldap3.Server('ldap://127.0.0.1')
|
||||
conn = ldap3.Connection(srv, user=dn, auto_bind=True)
|
||||
conn.search(dn, search_filter)
|
||||
@@ -0,0 +1,20 @@
|
||||
from flask import request, Flask
|
||||
import ldap
|
||||
import ldap.filter
|
||||
import ldap.dn
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = ldap.dn.escape_dn_chars(unsafe_dc)
|
||||
safe_filter = ldap.filter.escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
ldap_connection = ldap.initialize("ldap://127.0.0.1")
|
||||
user = ldap_connection.search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, search_filter)
|
||||
@@ -0,0 +1,20 @@
|
||||
from flask import request, Flask
|
||||
import ldap3
|
||||
from ldap3.utils.dn import escape_rdn
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = escape_rdn(unsafe_dc)
|
||||
safe_filter = escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
srv = ldap3.Server('ldap://127.0.0.1')
|
||||
conn = ldap3.Connection(srv, user=dn, auto_bind=True)
|
||||
conn.search(dn, search_filter)
|
||||
@@ -80,3 +80,69 @@ class RegexEscape extends DataFlow::Node {
|
||||
|
||||
DataFlow::Node getRegexNode() { result = range.getRegexNode() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling LDAP query execution-related APIs. */
|
||||
module LDAPQuery {
|
||||
/**
|
||||
* A data-flow node that collects methods executing a LDAP query.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LDAPQuery` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the argument containing the executed expression.
|
||||
*/
|
||||
abstract DataFlow::Node getQuery();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collect methods executing a LDAP query.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LDAPQuery::Range` instead.
|
||||
*/
|
||||
class LDAPQuery extends DataFlow::Node {
|
||||
LDAPQuery::Range range;
|
||||
|
||||
LDAPQuery() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the executed expression.
|
||||
*/
|
||||
DataFlow::Node getQuery() { result = range.getQuery() }
|
||||
}
|
||||
|
||||
/** Provides classes for modeling LDAP components escape-related APIs. */
|
||||
module LDAPEscape {
|
||||
/**
|
||||
* A data-flow node that collects functions escaping LDAP components.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `LDAPEscape` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/**
|
||||
* Gets the argument containing the escaped expression.
|
||||
*/
|
||||
abstract DataFlow::Node getAnInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A data-flow node that collects functions escaping LDAP components.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `LDAPEscape::Range` instead.
|
||||
*/
|
||||
class LDAPEscape extends DataFlow::Node {
|
||||
LDAPEscape::Range range;
|
||||
|
||||
LDAPEscape() { this = range }
|
||||
|
||||
/**
|
||||
* Gets the argument containing the escaped expression.
|
||||
*/
|
||||
DataFlow::Node getAnInput() { result = range.getAnInput() }
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@
|
||||
*/
|
||||
|
||||
private import experimental.semmle.python.frameworks.Stdlib
|
||||
private import experimental.semmle.python.frameworks.LDAP
|
||||
|
||||
153
python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
Normal file
153
python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
Normal file
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the LDAP 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.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for Python's ldap-related libraries.
|
||||
*/
|
||||
private module LDAP {
|
||||
/**
|
||||
* Provides models for the `python-ldap` PyPI package (imported as `ldap`).
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/index.html
|
||||
*/
|
||||
private module LDAP2 {
|
||||
/**
|
||||
* List of `ldap` methods used to execute a query.
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap.html#functions
|
||||
*/
|
||||
private class LDAP2QueryMethods extends string {
|
||||
LDAP2QueryMethods() {
|
||||
this in ["search", "search_s", "search_st", "search_ext", "search_ext_s"]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to find `ldap` methods executing a query.
|
||||
*
|
||||
* See `LDAP2QueryMethods`
|
||||
*/
|
||||
private class LDAP2Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
|
||||
DataFlow::Node ldapQuery;
|
||||
|
||||
LDAP2Query() {
|
||||
exists(DataFlow::AttrRead searchMethod |
|
||||
this.getFunction() = searchMethod and
|
||||
API::moduleImport("ldap").getMember("initialize").getACall() =
|
||||
searchMethod.getObject().getALocalSource() and
|
||||
searchMethod.getAttributeName() instanceof LDAP2QueryMethods and
|
||||
(
|
||||
ldapQuery = this.getArg(0)
|
||||
or
|
||||
(
|
||||
ldapQuery = this.getArg(2) or
|
||||
ldapQuery = this.getArgByName("filterstr")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = ldapQuery }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to find calls to `ldap.dn.escape_dn_chars`.
|
||||
*
|
||||
* See https://github.com/python-ldap/python-ldap/blob/7ce471e238cdd9a4dd8d17baccd1c9e05e6f894a/Lib/ldap/dn.py#L17
|
||||
*/
|
||||
private class LDAP2EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
LDAP2EscapeDNCall() {
|
||||
this = API::moduleImport("ldap").getMember("dn").getMember("escape_dn_chars").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to find calls to `ldap.filter.escape_filter_chars`.
|
||||
*
|
||||
* See https://www.python-ldap.org/en/python-ldap-3.3.0/reference/ldap-filter.html#ldap.filter.escape_filter_chars
|
||||
*/
|
||||
private class LDAP2EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
LDAP2EscapeFilterCall() {
|
||||
this =
|
||||
API::moduleImport("ldap").getMember("filter").getMember("escape_filter_chars").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `ldap3` PyPI package
|
||||
*
|
||||
* See https://pypi.org/project/ldap3/
|
||||
*/
|
||||
private module LDAP3 {
|
||||
/**
|
||||
* A class to find `ldap3` methods executing a query.
|
||||
*/
|
||||
private class LDAP3Query extends DataFlow::CallCfgNode, LDAPQuery::Range {
|
||||
DataFlow::Node ldapQuery;
|
||||
|
||||
LDAP3Query() {
|
||||
exists(DataFlow::AttrRead searchMethod |
|
||||
this.getFunction() = searchMethod and
|
||||
API::moduleImport("ldap3").getMember("Connection").getACall() =
|
||||
searchMethod.getObject().getALocalSource() and
|
||||
searchMethod.getAttributeName() = "search" and
|
||||
(
|
||||
ldapQuery = this.getArg(0) or
|
||||
ldapQuery = this.getArg(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getQuery() { result = ldapQuery }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to find calls to `ldap3.utils.dn.escape_rdn`.
|
||||
*
|
||||
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/dn.py#L390
|
||||
*/
|
||||
private class LDAP3EscapeDNCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
LDAP3EscapeDNCall() {
|
||||
this =
|
||||
API::moduleImport("ldap3")
|
||||
.getMember("utils")
|
||||
.getMember("dn")
|
||||
.getMember("escape_rdn")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A class to find calls to `ldap3.utils.conv.escape_filter_chars`.
|
||||
*
|
||||
* See https://github.com/cannatag/ldap3/blob/4d33166f0869b929f59c6e6825a1b9505eb99967/ldap3/utils/conv.py#L91
|
||||
*/
|
||||
private class LDAP3EscapeFilterCall extends DataFlow::CallCfgNode, LDAPEscape::Range {
|
||||
LDAP3EscapeFilterCall() {
|
||||
this =
|
||||
API::moduleImport("ldap3")
|
||||
.getMember("utils")
|
||||
.getMember("conv")
|
||||
.getMember("escape_filter_chars")
|
||||
.getACall()
|
||||
}
|
||||
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Provides a taint-tracking configuration for detecting LDAP 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 taint-tracking configuration for detecting LDAP injections.
|
||||
*/
|
||||
class LDAPInjectionFlowConfig extends TaintTracking::Configuration {
|
||||
LDAPInjectionFlowConfig() { this = "LDAPInjectionFlowConfig" }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) { sink = any(LDAPQuery ldapQuery).getQuery() }
|
||||
|
||||
override predicate isSanitizer(DataFlow::Node sanitizer) {
|
||||
sanitizer = any(LDAPEscape ldapEsc).getAnInput()
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/**
|
||||
* Helper file that imports all framework modeling.
|
||||
*/
|
||||
@@ -5,6 +6,7 @@
|
||||
// If you add modeling of a new framework/library, remember to add it it to the docs in
|
||||
// `docs/codeql/support/reusables/frameworks.rst`
|
||||
private import semmle.python.frameworks.Aioch
|
||||
private import semmle.python.frameworks.Aiohttp
|
||||
private import semmle.python.frameworks.ClickhouseDriver
|
||||
private import semmle.python.frameworks.Cryptodome
|
||||
private import semmle.python.frameworks.Cryptography
|
||||
@@ -14,6 +16,7 @@ private import semmle.python.frameworks.Fabric
|
||||
private import semmle.python.frameworks.Flask
|
||||
private import semmle.python.frameworks.Idna
|
||||
private import semmle.python.frameworks.Invoke
|
||||
private import semmle.python.frameworks.Multidict
|
||||
private import semmle.python.frameworks.MysqlConnectorPython
|
||||
private import semmle.python.frameworks.MySQLdb
|
||||
private import semmle.python.frameworks.Psycopg2
|
||||
@@ -23,3 +26,4 @@ private import semmle.python.frameworks.Stdlib
|
||||
private import semmle.python.frameworks.Tornado
|
||||
private import semmle.python.frameworks.Ujson
|
||||
private import semmle.python.frameworks.Yaml
|
||||
private import semmle.python.frameworks.Yarl
|
||||
|
||||
@@ -1,167 +1,16 @@
|
||||
/** Step Summaries and Type Tracking */
|
||||
/**
|
||||
* This file acts as a wrapper for `internal.TypeTracker`, exposing some of the functionality with
|
||||
* names that are more appropriate for Python.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import internal.DataFlowPublic
|
||||
private import internal.DataFlowPrivate
|
||||
private import internal.TypeTracker as Internal
|
||||
|
||||
/** Any string that may appear as the name of an attribute or access path. */
|
||||
class AttributeName extends string {
|
||||
AttributeName() { this = any(AttrRef a).getAttributeName() }
|
||||
}
|
||||
class AttributeName = Internal::ContentName;
|
||||
|
||||
/** Either an attribute name, or the empty string (representing no attribute). */
|
||||
class OptionalAttributeName extends string {
|
||||
OptionalAttributeName() { this instanceof AttributeName or this = "" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
private newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(AttributeName attr) or
|
||||
LoadStep(AttributeName attr)
|
||||
|
||||
/**
|
||||
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
|
||||
*
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
class StepSummary extends TStepSummary {
|
||||
/** Gets a textual representation of this step summary. */
|
||||
string toString() {
|
||||
this instanceof LevelStep and result = "level"
|
||||
or
|
||||
this instanceof CallStep and result = "call"
|
||||
or
|
||||
this instanceof ReturnStep and result = "return"
|
||||
or
|
||||
exists(string attr | this = StoreStep(attr) | result = "store " + attr)
|
||||
or
|
||||
exists(string attr | this = LoadStep(attr) | result = "load " + attr)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for updating step summaries (`StepSummary`s). */
|
||||
module StepSummary {
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
cached
|
||||
predicate step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
|
||||
exists(Node mid | nodeFrom.flowsTo(mid) and smallstep(mid, nodeTo, summary))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Unlike `StepSummary::step`, this predicate does not compress
|
||||
* type-preserving steps.
|
||||
*/
|
||||
predicate smallstep(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
jumpStep(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
callStep(nodeFrom, nodeTo) and summary = CallStep()
|
||||
or
|
||||
returnStep(nodeFrom, nodeTo) and
|
||||
summary = ReturnStep()
|
||||
or
|
||||
exists(string attr |
|
||||
basicStoreStep(nodeFrom, nodeTo, attr) and
|
||||
summary = StoreStep(attr)
|
||||
or
|
||||
basicLoadStep(nodeFrom, nodeTo, attr) and summary = LoadStep(attr)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a callable for the call where `nodeFrom` is used as the `i`'th argument.
|
||||
*
|
||||
* Helper predicate to avoid bad join order experienced in `callStep`.
|
||||
* This happened when `isParameterOf` was joined _before_ `getCallable`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private DataFlowCallable getCallableForArgument(ArgumentNode nodeFrom, int i) {
|
||||
exists(DataFlowCall call |
|
||||
nodeFrom.argumentOf(call, i) and
|
||||
result = call.getCallable()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being passed as a parameter in a call. */
|
||||
predicate callStep(ArgumentNode nodeFrom, ParameterNode nodeTo) {
|
||||
// TODO: Support special methods?
|
||||
exists(DataFlowCallable callable, int i |
|
||||
callable = getCallableForArgument(nodeFrom, i) and
|
||||
nodeTo.isParameterOf(callable, i)
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `nodeFrom` steps to `nodeTo` by being returned from a call. */
|
||||
predicate returnStep(ReturnNode nodeFrom, Node nodeTo) {
|
||||
exists(DataFlowCall call |
|
||||
nodeFrom.getEnclosingCallable() = call.getCallable() and nodeTo.asCfgNode() = call.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `attr` attribute of the object in `nodeTo`.
|
||||
*
|
||||
* Note that the choice of `nodeTo` does not have to make sense "chronologically".
|
||||
* All we care about is whether the `attr` attribute of `nodeTo` can have a specific type,
|
||||
* and the assumption is that if a specific type appears here, then any access of that
|
||||
* particular attribute can yield something of that particular type.
|
||||
*
|
||||
* Thus, in an example such as
|
||||
*
|
||||
* ```python
|
||||
* def foo(y):
|
||||
* x = Foo()
|
||||
* bar(x)
|
||||
* x.attr = y
|
||||
* baz(x)
|
||||
*
|
||||
* def bar(x):
|
||||
* z = x.attr
|
||||
* ```
|
||||
* for the attribute write `x.attr = y`, we will have `attr` being the literal string `"attr"`,
|
||||
* `nodeFrom` will be `y`, and `nodeTo` will be the object `Foo()` created on the first line of the
|
||||
* function. This means we will track the fact that `x.attr` can have the type of `y` into the
|
||||
* assignment to `z` inside `bar`, even though this attribute write happens _after_ `bar` is called.
|
||||
*/
|
||||
predicate basicStoreStep(Node nodeFrom, LocalSourceNode nodeTo, string attr) {
|
||||
exists(AttrWrite a |
|
||||
a.mayHaveAttributeName(attr) and
|
||||
nodeFrom = a.getValue() and
|
||||
nodeTo.flowsTo(a.getObject())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeTo` is the result of accessing the `attr` attribute of `nodeFrom`.
|
||||
*/
|
||||
predicate basicLoadStep(Node nodeFrom, Node nodeTo, string attr) {
|
||||
exists(AttrRead a |
|
||||
a.mayHaveAttributeName(attr) and
|
||||
nodeFrom = a.getObject() and
|
||||
nodeTo = a
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility class that is equivalent to `boolean` but does not require type joining.
|
||||
*/
|
||||
private class Boolean extends boolean {
|
||||
Boolean() { this = true or this = false }
|
||||
}
|
||||
|
||||
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalAttributeName attr)
|
||||
class OptionalAttributeName = Internal::OptionalContentName;
|
||||
|
||||
/**
|
||||
* Summary of the steps needed to track a value to a given dataflow node.
|
||||
@@ -173,8 +22,8 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalAttributeN
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for tracking some type `myType`:
|
||||
* ```
|
||||
* private DataFlow::LocalSourceNode myType(DataFlow::TypeTracker t) {
|
||||
* ```ql
|
||||
* DataFlow::LocalSourceNode myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
@@ -183,279 +32,34 @@ private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalAttributeN
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
* DataFlow::LocalSourceNode myType() { myType(DataFlow::TypeTracker::end()) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myType(t2).track(t2, t)`, you can also use the equivalent
|
||||
* `t = t2.step(myType(t2), result)`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t = t2.smallstep(myCallback(t2), result)`.
|
||||
*/
|
||||
class TypeTracker extends TTypeTracker {
|
||||
Boolean hasCall;
|
||||
OptionalAttributeName attr;
|
||||
|
||||
TypeTracker() { this = MkTypeTracker(hasCall, attr) }
|
||||
|
||||
/** Gets the summary resulting from appending `step` to this type-tracking summary. */
|
||||
cached
|
||||
TypeTracker append(StepSummary step) {
|
||||
step = LevelStep() and result = this
|
||||
or
|
||||
step = CallStep() and result = MkTypeTracker(true, attr)
|
||||
or
|
||||
step = ReturnStep() and hasCall = false and result = this
|
||||
or
|
||||
step = LoadStep(attr) and result = MkTypeTracker(hasCall, "")
|
||||
or
|
||||
exists(string p | step = StoreStep(p) and attr = "" and result = MkTypeTracker(hasCall, p))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withCall, string withAttr |
|
||||
(if hasCall = true then withCall = "with" else withCall = "without") and
|
||||
(if attr != "" then withAttr = " with attribute " + attr else withAttr = "") and
|
||||
result = "type tracker " + withCall + " call steps" + withAttr
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasCall = false and attr = "" }
|
||||
|
||||
class TypeTracker extends Internal::TypeTracker {
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the value starts in the attribute named `attrName`.
|
||||
* The type tracking only ends after the attribute has been loaded.
|
||||
*/
|
||||
predicate startInAttr(AttributeName attrName) { hasCall = false and attr = attrName }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking
|
||||
* when tracking a parameter into a call, but not out of it.
|
||||
*/
|
||||
predicate call() { hasCall = true and attr = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { attr = "" }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been tracked into a call.
|
||||
*/
|
||||
boolean hasCall() { result = hasCall }
|
||||
predicate startInAttr(string attrName) { this.startInContent(attrName) }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Gets the attribute associated with this type tracker.
|
||||
*/
|
||||
string getAttr() { result = attr }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type has not been tracked into an attribute.
|
||||
*/
|
||||
TypeTracker continue() { attr = "" and result = this }
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
|
||||
result = this.append(pragma[only_bind_into](summary))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a forwards
|
||||
* local, heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*
|
||||
* Unlike `TypeTracker::step`, this predicate exposes all edges
|
||||
* in the flow graph, and not just the edges between `Node`s.
|
||||
* It may therefore be less performant.
|
||||
*
|
||||
* Type tracking predicates using small steps typically take the following form:
|
||||
* ```ql
|
||||
* DataFlow::Node myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* t = t2.smallstep(myType(t2), result)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() {
|
||||
* result = myType(DataFlow::TypeTracker::end())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
result = this.append(summary)
|
||||
)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
result = this
|
||||
}
|
||||
string getAttr() { result = this.getContent() }
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeTracker`s. */
|
||||
module TypeTracker {
|
||||
/**
|
||||
* Gets a valid end point of type tracking.
|
||||
*/
|
||||
TypeTracker end() { result.end() }
|
||||
}
|
||||
module TypeTracker = Internal::TypeTracker;
|
||||
|
||||
private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalAttributeName attr)
|
||||
class StepSummary = Internal::StepSummary;
|
||||
|
||||
/**
|
||||
* Summary of the steps needed to back-track a use of a value to a given dataflow node.
|
||||
*
|
||||
* This can for example be used to track callbacks that are passed to a certain API,
|
||||
* so we can model specific parameters of that callback as having a certain type.
|
||||
*
|
||||
* Note that type back-tracking does not provide a source/sink relation, that is,
|
||||
* it may determine that a node will be used in an API call somewhere, but it won't
|
||||
* determine exactly where that use was, or the path that led to the use.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for back-tracking some callback type `myCallback`:
|
||||
*
|
||||
* ```
|
||||
* private DataFlow::LocalSourceNode myCallback(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = (< some API call >).getArgument(< n >).getALocalSource()
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* result = myCallback(t2).backtrack(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::LocalSourceNode myCallback() { result = myCallback(DataFlow::TypeBackTracker::end()) }
|
||||
* ```
|
||||
*
|
||||
* Instead of `result = myCallback(t2).backtrack(t2, t)`, you can also use the equivalent
|
||||
* `t2 = t.step(result, myCallback(t2))`. If you additionally want to track individual
|
||||
* intra-procedural steps, use `t2 = t.smallstep(result, myCallback(t2))`.
|
||||
*/
|
||||
class TypeBackTracker extends TTypeBackTracker {
|
||||
Boolean hasReturn;
|
||||
string attr;
|
||||
module StepSummary = Internal::StepSummary;
|
||||
|
||||
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, attr) }
|
||||
class TypeBackTracker = Internal::TypeBackTracker;
|
||||
|
||||
/** Gets the summary resulting from prepending `step` to this type-tracking summary. */
|
||||
TypeBackTracker prepend(StepSummary step) {
|
||||
step = LevelStep() and result = this
|
||||
or
|
||||
step = CallStep() and hasReturn = false and result = this
|
||||
or
|
||||
step = ReturnStep() and result = MkTypeBackTracker(true, attr)
|
||||
or
|
||||
exists(string p | step = LoadStep(p) and attr = "" and result = MkTypeBackTracker(hasReturn, p))
|
||||
or
|
||||
step = StoreStep(attr) and result = MkTypeBackTracker(hasReturn, "")
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withReturn, string withAttr |
|
||||
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
|
||||
(if attr != "" then withAttr = " with attribute " + attr else withAttr = "") and
|
||||
result = "type back-tracker " + withReturn + " return steps" + withAttr
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasReturn = false and attr = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { attr = "" }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been back-tracked into a call through return edge.
|
||||
*/
|
||||
boolean hasReturn() { result = hasReturn }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type has not been tracked into an attribute.
|
||||
*/
|
||||
TypeBackTracker continue() { attr = "" and result = this }
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a backwards
|
||||
* heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
||||
this = result.prepend(pragma[only_bind_into](summary))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the summary that corresponds to having taken a backwards
|
||||
* local, heap and/or inter-procedural step from `nodeTo` to `nodeFrom`.
|
||||
*
|
||||
* Unlike `TypeBackTracker::step`, this predicate exposes all edges
|
||||
* in the flowgraph, and not just the edges between
|
||||
* `LocalSourceNode`s. It may therefore be less performant.
|
||||
*
|
||||
* Type tracking predicates using small steps typically take the following form:
|
||||
* ```ql
|
||||
* DataFlow::Node myType(DataFlow::TypeBackTracker t) {
|
||||
* t.start() and
|
||||
* result = < some API call >.getArgument(< n >)
|
||||
* or
|
||||
* exists (DataFlow::TypeBackTracker t2 |
|
||||
* t = t2.smallstep(result, myType(t2))
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() {
|
||||
* result = myType(DataFlow::TypeBackTracker::end())
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeBackTracker smallstep(Node nodeFrom, Node nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::smallstep(nodeFrom, nodeTo, summary) and
|
||||
this = result.prepend(summary)
|
||||
)
|
||||
or
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeBackTracker`s. */
|
||||
module TypeBackTracker {
|
||||
/**
|
||||
* Gets a valid end point of type back-tracking.
|
||||
*/
|
||||
TypeBackTracker end() { result.end() }
|
||||
}
|
||||
module TypeBackTracker = Internal::TypeBackTracker;
|
||||
|
||||
@@ -532,7 +532,9 @@ private module Stage1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`.
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`
|
||||
* and data might flow through the target callable resulting in reverse flow
|
||||
* reaching an argument of `call`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
|
||||
@@ -605,6 +607,15 @@ private module Stage1 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(ArgNode arg, boolean toReturn |
|
||||
revFlow(arg, toReturn, config) and
|
||||
revFlowInToReturn(call, arg, config) and
|
||||
revFlowIsReturned(call, toReturn, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, config)) and
|
||||
@@ -827,6 +838,16 @@ private module Stage2 {
|
||||
PrevStage::revFlow(node, _, _, apa, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -893,13 +914,11 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -950,17 +969,14 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -975,7 +991,13 @@ private module Stage2 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,6 +1028,27 @@ private module Stage2 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1071,14 +1114,12 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1126,12 +1167,10 @@ private module Stage2 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1141,7 +1180,12 @@ private module Stage2 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1211,6 +1255,15 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -1459,6 +1512,16 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -1532,13 +1595,11 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1589,17 +1650,14 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -1614,7 +1672,13 @@ private module Stage3 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,6 +1709,27 @@ private module Stage3 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1710,14 +1795,12 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1765,12 +1848,10 @@ private module Stage3 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1780,7 +1861,12 @@ private module Stage3 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1850,6 +1936,15 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -2120,8 +2215,6 @@ private module Stage4 {
|
||||
bindingset[innercc, inner, call]
|
||||
private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) {
|
||||
resolveReturn(innercc, inner, call)
|
||||
or
|
||||
innercc.(CallContextCall).matchesCall(call)
|
||||
}
|
||||
|
||||
bindingset[node, cc, config]
|
||||
@@ -2174,6 +2267,16 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -2247,13 +2350,11 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2304,17 +2405,14 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -2329,7 +2427,13 @@ private module Stage4 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2360,6 +2464,27 @@ private module Stage4 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -2425,14 +2550,12 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -2480,12 +2603,10 @@ private module Stage4 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -2495,7 +2616,12 @@ private module Stage4 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2565,6 +2691,15 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
|
||||
@@ -532,7 +532,9 @@ private module Stage1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`.
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`
|
||||
* and data might flow through the target callable resulting in reverse flow
|
||||
* reaching an argument of `call`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
|
||||
@@ -605,6 +607,15 @@ private module Stage1 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(ArgNode arg, boolean toReturn |
|
||||
revFlow(arg, toReturn, config) and
|
||||
revFlowInToReturn(call, arg, config) and
|
||||
revFlowIsReturned(call, toReturn, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, config)) and
|
||||
@@ -827,6 +838,16 @@ private module Stage2 {
|
||||
PrevStage::revFlow(node, _, _, apa, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -893,13 +914,11 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -950,17 +969,14 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -975,7 +991,13 @@ private module Stage2 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,6 +1028,27 @@ private module Stage2 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1071,14 +1114,12 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1126,12 +1167,10 @@ private module Stage2 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1141,7 +1180,12 @@ private module Stage2 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1211,6 +1255,15 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -1459,6 +1512,16 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -1532,13 +1595,11 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1589,17 +1650,14 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -1614,7 +1672,13 @@ private module Stage3 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,6 +1709,27 @@ private module Stage3 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1710,14 +1795,12 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1765,12 +1848,10 @@ private module Stage3 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1780,7 +1861,12 @@ private module Stage3 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1850,6 +1936,15 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -2120,8 +2215,6 @@ private module Stage4 {
|
||||
bindingset[innercc, inner, call]
|
||||
private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) {
|
||||
resolveReturn(innercc, inner, call)
|
||||
or
|
||||
innercc.(CallContextCall).matchesCall(call)
|
||||
}
|
||||
|
||||
bindingset[node, cc, config]
|
||||
@@ -2174,6 +2267,16 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -2247,13 +2350,11 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2304,17 +2405,14 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -2329,7 +2427,13 @@ private module Stage4 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2360,6 +2464,27 @@ private module Stage4 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -2425,14 +2550,12 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -2480,12 +2603,10 @@ private module Stage4 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -2495,7 +2616,12 @@ private module Stage4 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2565,6 +2691,15 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
|
||||
@@ -532,7 +532,9 @@ private module Stage1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`.
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`
|
||||
* and data might flow through the target callable resulting in reverse flow
|
||||
* reaching an argument of `call`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
|
||||
@@ -605,6 +607,15 @@ private module Stage1 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(ArgNode arg, boolean toReturn |
|
||||
revFlow(arg, toReturn, config) and
|
||||
revFlowInToReturn(call, arg, config) and
|
||||
revFlowIsReturned(call, toReturn, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, config)) and
|
||||
@@ -827,6 +838,16 @@ private module Stage2 {
|
||||
PrevStage::revFlow(node, _, _, apa, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -893,13 +914,11 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -950,17 +969,14 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -975,7 +991,13 @@ private module Stage2 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,6 +1028,27 @@ private module Stage2 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1071,14 +1114,12 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1126,12 +1167,10 @@ private module Stage2 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1141,7 +1180,12 @@ private module Stage2 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1211,6 +1255,15 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -1459,6 +1512,16 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -1532,13 +1595,11 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1589,17 +1650,14 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -1614,7 +1672,13 @@ private module Stage3 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,6 +1709,27 @@ private module Stage3 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1710,14 +1795,12 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1765,12 +1848,10 @@ private module Stage3 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1780,7 +1861,12 @@ private module Stage3 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1850,6 +1936,15 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -2120,8 +2215,6 @@ private module Stage4 {
|
||||
bindingset[innercc, inner, call]
|
||||
private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) {
|
||||
resolveReturn(innercc, inner, call)
|
||||
or
|
||||
innercc.(CallContextCall).matchesCall(call)
|
||||
}
|
||||
|
||||
bindingset[node, cc, config]
|
||||
@@ -2174,6 +2267,16 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -2247,13 +2350,11 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2304,17 +2405,14 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -2329,7 +2427,13 @@ private module Stage4 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2360,6 +2464,27 @@ private module Stage4 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -2425,14 +2550,12 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -2480,12 +2603,10 @@ private module Stage4 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -2495,7 +2616,12 @@ private module Stage4 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2565,6 +2691,15 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
|
||||
@@ -532,7 +532,9 @@ private module Stage1 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`.
|
||||
* Holds if an output from `call` is reached in the flow covered by `revFlow`
|
||||
* and data might flow through the target callable resulting in reverse flow
|
||||
* reaching an argument of `call`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIsReturned(DataFlowCall call, boolean toReturn, Configuration config) {
|
||||
@@ -605,6 +607,15 @@ private module Stage1 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(ArgNode arg, boolean toReturn |
|
||||
revFlow(arg, toReturn, config) and
|
||||
revFlowInToReturn(call, arg, config) and
|
||||
revFlowIsReturned(call, toReturn, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, config)) and
|
||||
@@ -827,6 +838,16 @@ private module Stage2 {
|
||||
PrevStage::revFlow(node, _, _, apa, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -893,13 +914,11 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -950,17 +969,14 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -975,7 +991,13 @@ private module Stage2 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1006,6 +1028,27 @@ private module Stage2 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1071,14 +1114,12 @@ private module Stage2 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1126,12 +1167,10 @@ private module Stage2 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1141,7 +1180,12 @@ private module Stage2 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1211,6 +1255,15 @@ private module Stage2 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -1459,6 +1512,16 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -1532,13 +1595,11 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1589,17 +1650,14 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -1614,7 +1672,13 @@ private module Stage3 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,6 +1709,27 @@ private module Stage3 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -1710,14 +1795,12 @@ private module Stage3 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -1765,12 +1848,10 @@ private module Stage3 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -1780,7 +1861,12 @@ private module Stage3 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1850,6 +1936,15 @@ private module Stage3 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
@@ -2120,8 +2215,6 @@ private module Stage4 {
|
||||
bindingset[innercc, inner, call]
|
||||
private predicate checkCallContextReturn(Cc innercc, DataFlowCallable inner, DataFlowCall call) {
|
||||
resolveReturn(innercc, inner, call)
|
||||
or
|
||||
innercc.(CallContextCall).matchesCall(call)
|
||||
}
|
||||
|
||||
bindingset[node, cc, config]
|
||||
@@ -2174,6 +2267,16 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughOutOfCall(
|
||||
DataFlowCall call, ReturnNodeExt ret, Node out, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, pragma[only_bind_into](config)) and
|
||||
PrevStage::callMayFlowThroughRev(call, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(_, getNodeEnclosingCallable(ret), _,
|
||||
pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` is reachable with access path `ap` from a source in the
|
||||
* configuration `config`.
|
||||
@@ -2247,13 +2350,11 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
exists(DataFlowCall call |
|
||||
fwdFlowOut(call, node, any(CcNoCall innercc), cc, argAp, ap, config)
|
||||
or
|
||||
exists(Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
fwdFlowOutNotFromArg(node, cc, argAp, ap, config)
|
||||
or
|
||||
exists(DataFlowCall call, Ap argAp0 |
|
||||
fwdFlowOutFromArg(call, node, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, cc, argAp, argAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2304,17 +2405,14 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if flow may exit from `call` at `out` with access path `ap`. The
|
||||
* inner call context is `innercc`, but `ccOut` is just the call context
|
||||
* based on the return step. In the case of through-flow `ccOut` is discarded
|
||||
* and replaced by the outer call context as tracked by `fwdFlowIsEntered`.
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private predicate fwdFlowOut(
|
||||
DataFlowCall call, Node out, Cc innercc, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
private predicate fwdFlowOutNotFromArg(
|
||||
Node out, Cc ccOut, ApOption argAp, Ap ap, Configuration config
|
||||
) {
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, DataFlowCallable inner |
|
||||
exists(
|
||||
DataFlowCall call, ReturnNodeExt ret, boolean allowsFieldFlow, CcNoCall innercc,
|
||||
DataFlowCallable inner
|
||||
|
|
||||
fwdFlow(ret, innercc, argAp, ap, config) and
|
||||
flowOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
inner = getNodeEnclosingCallable(ret) and
|
||||
@@ -2329,7 +2427,13 @@ private module Stage4 {
|
||||
private predicate fwdFlowOutFromArg(
|
||||
DataFlowCall call, Node out, Ap argAp, Ap ap, Configuration config
|
||||
) {
|
||||
fwdFlowOut(call, out, any(CcCall ccc), _, apSome(argAp), ap, config)
|
||||
exists(ReturnNodeExt ret, boolean allowsFieldFlow, CcCall ccc |
|
||||
fwdFlow(ret, ccc, apSome(argAp), ap, config) and
|
||||
flowThroughOutOfCall(call, ret, out, allowsFieldFlow, config) and
|
||||
ccc.matchesCall(call)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2360,6 +2464,27 @@ private module Stage4 {
|
||||
fwdFlowConsCand(ap1, c, ap2, config)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
|
||||
exists(Ap argAp0, Node out, Cc cc, ApOption argAp, Ap ap |
|
||||
fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
|
||||
pragma[only_bind_into](config)) and
|
||||
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
|
||||
fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
|
||||
pragma[only_bind_into](config))
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate flowThroughIntoCall(
|
||||
DataFlowCall call, ArgNode arg, ParamNode p, boolean allowsFieldFlow, Configuration config
|
||||
) {
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config) and
|
||||
fwdFlow(arg, _, _, _, pragma[only_bind_into](config)) and
|
||||
PrevStage::parameterMayFlowThrough(p, _, _, pragma[only_bind_into](config)) and
|
||||
callMayFlowThroughFwd(call, pragma[only_bind_into](config))
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `node` with access path `ap` is part of a path from a source to a
|
||||
* sink in the configuration `config`.
|
||||
@@ -2425,14 +2550,12 @@ private module Stage4 {
|
||||
)
|
||||
or
|
||||
// flow into a callable
|
||||
exists(DataFlowCall call |
|
||||
revFlowIn(call, node, toReturn, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
revFlowInNotToReturn(node, returnAp, ap, config) and
|
||||
toReturn = false
|
||||
or
|
||||
exists(DataFlowCall call, Ap returnAp0 |
|
||||
revFlowInToReturn(call, node, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
or
|
||||
// flow out of a callable
|
||||
@@ -2480,12 +2603,10 @@ private module Stage4 {
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate revFlowIn(
|
||||
DataFlowCall call, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
private predicate revFlowInNotToReturn(ArgNode arg, ApOption returnAp, Ap ap, Configuration config) {
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, toReturn, returnAp, ap, config) and
|
||||
flowIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
revFlow(p, false, returnAp, ap, config) and
|
||||
flowIntoCall(_, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
@@ -2495,7 +2616,12 @@ private module Stage4 {
|
||||
private predicate revFlowInToReturn(
|
||||
DataFlowCall call, ArgNode arg, Ap returnAp, Ap ap, Configuration config
|
||||
) {
|
||||
revFlowIn(call, arg, true, apSome(returnAp), ap, config)
|
||||
exists(ParamNode p, boolean allowsFieldFlow |
|
||||
revFlow(p, true, apSome(returnAp), ap, config) and
|
||||
flowThroughIntoCall(call, arg, p, allowsFieldFlow, config)
|
||||
|
|
||||
ap instanceof ApNil or allowsFieldFlow = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2565,6 +2691,15 @@ private module Stage4 {
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
predicate callMayFlowThroughRev(DataFlowCall call, Configuration config) {
|
||||
exists(Ap returnAp0, ArgNode arg, boolean toReturn, ApOption returnAp, Ap ap |
|
||||
revFlow(arg, toReturn, returnAp, ap, config) and
|
||||
revFlowInToReturn(call, arg, returnAp0, ap, config) and
|
||||
revFlowIsReturned(call, toReturn, returnAp, returnAp0, config)
|
||||
)
|
||||
}
|
||||
|
||||
predicate stats(boolean fwd, int nodes, int fields, int conscand, int tuples, Configuration config) {
|
||||
fwd = true and
|
||||
nodes = count(Node node | fwdFlow(node, _, _, _, config)) and
|
||||
|
||||
562
python/ql/src/semmle/python/frameworks/Aiohttp.qll
Normal file
562
python/ql/src/semmle/python/frameworks/Aiohttp.qll
Normal file
@@ -0,0 +1,562 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `aiohttp` PyPI package.
|
||||
* See https://docs.aiohttp.org/en/stable/index.html
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
private import semmle.python.frameworks.internal.SelfRefMixin
|
||||
private import semmle.python.frameworks.Multidict
|
||||
private import semmle.python.frameworks.Yarl
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the web server part (`aiohttp.web`) of the `aiohttp` PyPI package.
|
||||
* See https://docs.aiohttp.org/en/stable/web.html
|
||||
*/
|
||||
module AiohttpWebModel {
|
||||
/**
|
||||
* Provides models for the `aiohttp.web.View` class and subclasses.
|
||||
*
|
||||
* See https://docs.aiohttp.org/en/stable/web_reference.html#view.
|
||||
*/
|
||||
module View {
|
||||
/** Gets a reference to the `aiohttp.web.View` class or any subclass. */
|
||||
API::Node subclassRef() {
|
||||
result = API::moduleImport("aiohttp").getMember("web").getMember("View").getASubclass*()
|
||||
}
|
||||
}
|
||||
|
||||
// -- route modeling --
|
||||
/** Gets a reference to an `aiohttp.web.Application` instance. */
|
||||
API::Node applicationInstance() {
|
||||
// Not sure whether you're allowed to add routes _after_ starting the app, for
|
||||
// example in the middle of handling a http request... but I'm guessing that for 99%
|
||||
// for all code, not modeling that `request.app` is a reference to an application
|
||||
// should be good enough for the route-setup part of the modeling :+1:
|
||||
result = API::moduleImport("aiohttp").getMember("web").getMember("Application").getReturn()
|
||||
}
|
||||
|
||||
/** Gets a reference to an `aiohttp.web.UrlDispatcher` instance. */
|
||||
API::Node urlDispathcerInstance() {
|
||||
result = API::moduleImport("aiohttp").getMember("web").getMember("UrlDispatcher").getReturn()
|
||||
or
|
||||
result = applicationInstance().getMember("router")
|
||||
}
|
||||
|
||||
/**
|
||||
* A route setup in `aiohttp.web`. Since all route-setups can technically use either
|
||||
* coroutines or view-classes as the handler argument (although that's not how you're
|
||||
* **supposed** to do things), we also need to handle this.
|
||||
*
|
||||
* Extend this class to refine existing API models. If you want to model new APIs,
|
||||
* extend `AiohttpRouteSetup::Range` instead.
|
||||
*/
|
||||
class AiohttpRouteSetup extends HTTP::Server::RouteSetup::Range {
|
||||
AiohttpRouteSetup::Range range;
|
||||
|
||||
AiohttpRouteSetup() { this = range }
|
||||
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
|
||||
override string getFramework() { result = "aiohttp.web" }
|
||||
|
||||
/** Gets the argument specifying the handler (either a coroutine or a view-class). */
|
||||
DataFlow::Node getHandlerArg() { result = range.getHandlerArg() }
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() { result = range.getUrlPatternArg() }
|
||||
|
||||
/** Gets the view-class that is referenced in the view-class handler argument, if any. */
|
||||
Class getViewClass() { result = range.getViewClass() }
|
||||
|
||||
override Function getARequestHandler() { result = range.getARequestHandler() }
|
||||
}
|
||||
|
||||
/** Provides a class for modeling new aiohttp.web route setups. */
|
||||
private module AiohttpRouteSetup {
|
||||
/**
|
||||
* A route setup in `aiohttp.web`. Since all route-setups can technically use either
|
||||
* coroutines or view-classes as the handler argument (although that's not how you're
|
||||
* **supposed** to do things), we also need to handle this.
|
||||
*
|
||||
* Extend this class to model new APIs. If you want to refine existing API models,
|
||||
* extend `AiohttpRouteSetup` instead.
|
||||
*/
|
||||
abstract class Range extends DataFlow::Node {
|
||||
/** Gets the argument used to set the URL pattern. */
|
||||
abstract DataFlow::Node getUrlPatternArg();
|
||||
|
||||
/** Gets the argument specifying the handler (either a coroutine or a view-class). */
|
||||
abstract DataFlow::Node getHandlerArg();
|
||||
|
||||
/** Gets the view-class that is referenced in the view-class handler argument, if any. */
|
||||
Class getViewClass() { result = getBackTrackedViewClass(this.getHandlerArg()) }
|
||||
|
||||
/**
|
||||
* Gets a function that will handle incoming requests for this route, if any.
|
||||
*
|
||||
* NOTE: This will be modified in the near future to have a `RequestHandler` result, instead of a `Function`.
|
||||
*/
|
||||
Function getARequestHandler() {
|
||||
this.getHandlerArg() = poorMansFunctionTracker(result)
|
||||
or
|
||||
result = this.getViewClass().(AiohttpViewClass).getARequestHandler()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a class, that has been backtracked from the view-class handler
|
||||
* argument `origin` (to a route-setup for view-classes).
|
||||
*/
|
||||
private DataFlow::LocalSourceNode viewClassBackTracker(
|
||||
DataFlow::TypeBackTracker t, DataFlow::Node origin
|
||||
) {
|
||||
t.start() and
|
||||
origin = any(Range rs).getHandlerArg() and
|
||||
result = origin.getALocalSource()
|
||||
or
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
result = viewClassBackTracker(t2, origin).backtrack(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to a class, that has been backtracked from the view-class handler
|
||||
* argument `origin` (to a route-setup for view-classes).
|
||||
*/
|
||||
DataFlow::LocalSourceNode viewClassBackTracker(DataFlow::Node origin) {
|
||||
result = viewClassBackTracker(DataFlow::TypeBackTracker::end(), origin)
|
||||
}
|
||||
|
||||
Class getBackTrackedViewClass(DataFlow::Node origin) {
|
||||
result.getParent() = viewClassBackTracker(origin).asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/** An aiohttp route setup that uses coroutines (async function) as request handlers. */
|
||||
class AiohttpCoroutineRouteSetup extends AiohttpRouteSetup {
|
||||
AiohttpCoroutineRouteSetup() { this.getHandlerArg() = poorMansFunctionTracker(_) }
|
||||
}
|
||||
|
||||
/** An aiohttp route setup that uses view-classes as request handlers. */
|
||||
class AiohttpViewRouteSetup extends AiohttpRouteSetup {
|
||||
AiohttpViewRouteSetup() { exists(this.getViewClass()) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A route-setup from
|
||||
* - `add_route`, `add_view`, `add_get`, `add_post`, , etc. on an `aiohttp.web.UrlDispatcher`.
|
||||
* - `route`, `view`, `get`, `post`, etc. functions from `aiohttp.web`.
|
||||
*/
|
||||
class AiohttpAddRouteCall extends AiohttpRouteSetup::Range, DataFlow::CallCfgNode {
|
||||
/** At what index route arguments starts, so we can handle "route" version together with get/post/... */
|
||||
int routeArgsStart;
|
||||
|
||||
AiohttpAddRouteCall() {
|
||||
exists(string funcName |
|
||||
funcName = HTTP::httpVerbLower() and
|
||||
routeArgsStart = 0
|
||||
or
|
||||
funcName = "view" and
|
||||
routeArgsStart = 0
|
||||
or
|
||||
funcName = "route" and
|
||||
routeArgsStart = 1
|
||||
|
|
||||
this = urlDispathcerInstance().getMember("add_" + funcName).getACall()
|
||||
or
|
||||
this = API::moduleImport("aiohttp").getMember("web").getMember(funcName).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result in [this.getArg(routeArgsStart + 0), this.getArgByName("path")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getHandlerArg() {
|
||||
result in [this.getArg(routeArgsStart + 1), this.getArgByName("handler")]
|
||||
}
|
||||
}
|
||||
|
||||
/** A route-setup using a decorator, such as `route`, `view`, `get`, `post`, etc. on an `aiohttp.web.RouteTableDef`. */
|
||||
class AiohttpDecoratorRouteSetup extends AiohttpRouteSetup::Range, DataFlow::CallCfgNode {
|
||||
/** At what index route arguments starts, so we can handle "route" version together with get/post/... */
|
||||
int routeArgsStart;
|
||||
|
||||
AiohttpDecoratorRouteSetup() {
|
||||
exists(string decoratorName |
|
||||
decoratorName = HTTP::httpVerbLower() and
|
||||
routeArgsStart = 0
|
||||
or
|
||||
decoratorName = "view" and
|
||||
routeArgsStart = 0
|
||||
or
|
||||
decoratorName = "route" and
|
||||
routeArgsStart = 1
|
||||
|
|
||||
this =
|
||||
API::moduleImport("aiohttp")
|
||||
.getMember("web")
|
||||
.getMember("RouteTableDef")
|
||||
.getReturn()
|
||||
.getMember(decoratorName)
|
||||
.getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getUrlPatternArg() {
|
||||
result in [this.getArg(routeArgsStart + 0), this.getArgByName("path")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getHandlerArg() { none() }
|
||||
|
||||
override Class getViewClass() { result.getADecorator() = this.asExpr() }
|
||||
|
||||
override Function getARequestHandler() {
|
||||
// we're decorating a class
|
||||
exists(this.getViewClass()) and
|
||||
result = super.getARequestHandler()
|
||||
or
|
||||
// we're decorating a function
|
||||
not exists(this.getViewClass()) and
|
||||
result.getADecorator() = this.asExpr()
|
||||
}
|
||||
}
|
||||
|
||||
/** A class that we consider an aiohttp.web View class. */
|
||||
abstract class AiohttpViewClass extends Class, SelfRefMixin {
|
||||
/** Gets a function that could handle incoming requests, if any. */
|
||||
Function getARequestHandler() {
|
||||
// TODO: This doesn't handle attribute assignment. Should be OK, but analysis is not as complete as with
|
||||
// points-to and `.lookup`, which would handle `post = my_post_handler` inside class def
|
||||
result = this.getAMethod() and
|
||||
result.getName() = HTTP::httpVerbLower()
|
||||
}
|
||||
}
|
||||
|
||||
/** A class that has a super-type which is an aiohttp.web View class. */
|
||||
class AiohttpViewClassFromSuperClass extends AiohttpViewClass {
|
||||
AiohttpViewClassFromSuperClass() { this.getABase() = View::subclassRef().getAUse().asExpr() }
|
||||
}
|
||||
|
||||
/** A class that is used in a route-setup, therefore being considered an aiohttp.web View class. */
|
||||
class AiohttpViewClassFromRouteSetup extends AiohttpViewClass {
|
||||
AiohttpViewClassFromRouteSetup() { this = any(AiohttpRouteSetup rs).getViewClass() }
|
||||
}
|
||||
|
||||
/** A request handler defined in an `aiohttp.web` view class, that has no known route. */
|
||||
private class AiohttpViewClassRequestHandlerWithoutKnownRoute extends HTTP::Server::RequestHandler::Range {
|
||||
AiohttpViewClassRequestHandlerWithoutKnownRoute() {
|
||||
exists(AiohttpViewClass vc | vc.getARequestHandler() = this) and
|
||||
not exists(AiohttpRouteSetup setup | setup.getARequestHandler() = this)
|
||||
}
|
||||
|
||||
override Parameter getARoutedParameter() { none() }
|
||||
|
||||
override string getFramework() { result = "aiohttp.web" }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// aiohttp.web.Request taint modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Provides models for the `aiohttp.web.Request` class
|
||||
*
|
||||
* See https://docs.aiohttp.org/en/stable/web_reference.html#request-and-base-request
|
||||
*/
|
||||
module Request {
|
||||
/**
|
||||
* A source of instances of `aiohttp.web.Request`, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use `Request::instance()` predicate to get
|
||||
* references to instances of `aiohttp.web.Request`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** Gets a reference to an instance of `aiohttp.web.Request`. */
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `aiohttp.web.Request`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `aiohttp.StreamReader` class
|
||||
*
|
||||
* See https://docs.aiohttp.org/en/stable/streams.html#aiohttp.StreamReader
|
||||
*/
|
||||
module StreamReader {
|
||||
/**
|
||||
* A source of instances of `aiohttp.StreamReader`, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use `StreamReader::instance()` predicate to get
|
||||
* references to instances of `aiohttp.StreamReader`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** Gets a reference to an instance of `aiohttp.StreamReader`. */
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `aiohttp.StreamReader`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Taint propagation for `aiohttp.StreamReader`.
|
||||
*/
|
||||
private class AiohttpStreamReaderAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// Methods
|
||||
//
|
||||
// TODO: When we have tools that make it easy, model these properly to handle
|
||||
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
|
||||
// (since it allows us to at least capture the most common cases).
|
||||
nodeFrom = StreamReader::instance() and
|
||||
exists(DataFlow::AttrRead attr | attr.getObject() = nodeFrom |
|
||||
// normal methods
|
||||
attr.getAttributeName() in ["read_nowait"] and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
|
||||
or
|
||||
// async methods
|
||||
exists(Await await, DataFlow::CallCfgNode call |
|
||||
attr.getAttributeName() in [
|
||||
"read", "readany", "readexactly", "readline", "readchunk", "iter_chunked",
|
||||
"iter_any", "iter_chunks"
|
||||
] and
|
||||
call.getFunction() = attr and
|
||||
await.getValue() = call.asExpr() and
|
||||
nodeTo.asExpr() = await
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A parameter that will receive an `aiohttp.web.Request` instance when a request
|
||||
* handler is invoked.
|
||||
*/
|
||||
class AiohttpRequestHandlerRequestParam extends Request::InstanceSource, RemoteFlowSource::Range,
|
||||
DataFlow::ParameterNode {
|
||||
AiohttpRequestHandlerRequestParam() {
|
||||
exists(Function requestHandler |
|
||||
requestHandler = any(AiohttpCoroutineRouteSetup setup).getARequestHandler() and
|
||||
// We select the _last_ parameter for the request since that is what they do in
|
||||
// `aiohttp-jinja2`.
|
||||
// https://github.com/aio-libs/aiohttp-jinja2/blob/7fb4daf2c3003921d34031d38c2311ee0e02c18b/aiohttp_jinja2/__init__.py#L235
|
||||
//
|
||||
// I assume that is just to handle cases such as the one below
|
||||
// ```py
|
||||
// class MyCustomHandlerClass:
|
||||
// async def foo_handler(self, request):
|
||||
// ...
|
||||
//
|
||||
// my_custom_handler = MyCustomHandlerClass()
|
||||
// app.router.add_get("/MyCustomHandlerClass/foo", my_custom_handler.foo_handler)
|
||||
// ```
|
||||
this.getParameter() =
|
||||
max(Parameter param, int i | param = requestHandler.getArg(i) | param order by i)
|
||||
)
|
||||
}
|
||||
|
||||
override string getSourceType() { result = "aiohttp.web.Request" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A read of the `request` attribute on an instance of an aiohttp.web View class,
|
||||
* which is the request being processed currently.
|
||||
*/
|
||||
class AiohttpViewClassRequestAttributeRead extends Request::InstanceSource,
|
||||
RemoteFlowSource::Range, DataFlow::Node {
|
||||
AiohttpViewClassRequestAttributeRead() {
|
||||
this.(DataFlow::AttrRead).getObject() = any(AiohttpViewClass vc).getASelfRef() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "request"
|
||||
}
|
||||
|
||||
override string getSourceType() {
|
||||
result = "aiohttp.web.Request from self.request in View class"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Taint propagation for `aiohttp.web.Request`.
|
||||
*
|
||||
* See https://docs.aiohttp.org/en/stable/web_reference.html#request-and-base-request
|
||||
*/
|
||||
private class AiohttpRequestAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// Methods
|
||||
//
|
||||
// TODO: When we have tools that make it easy, model these properly to handle
|
||||
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
|
||||
// (since it allows us to at least capture the most common cases).
|
||||
nodeFrom = Request::instance() and
|
||||
exists(DataFlow::AttrRead attr | attr.getObject() = nodeFrom |
|
||||
// normal methods
|
||||
attr.getAttributeName() in ["clone", "get_extra_info"] and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
|
||||
or
|
||||
// async methods
|
||||
exists(Await await, DataFlow::CallCfgNode call |
|
||||
attr.getAttributeName() in ["read", "text", "json", "multipart", "post"] and
|
||||
call.getFunction() = attr and
|
||||
await.getValue() = call.asExpr() and
|
||||
nodeTo.asExpr() = await
|
||||
)
|
||||
)
|
||||
or
|
||||
// Attributes
|
||||
nodeFrom = Request::instance() and
|
||||
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom and
|
||||
nodeTo.(DataFlow::AttrRead).getAttributeName() in [
|
||||
"url", "rel_url", "forwarded", "host", "remote", "path", "path_qs", "raw_path", "query",
|
||||
"headers", "transport", "cookies", "content", "_payload", "content_type", "charset",
|
||||
"http_range", "if_modified_since", "if_unmodified_since", "if_range", "match_info"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** An attribute read on an `aiohttp.web.Request` that is a `MultiDictProxy` instance. */
|
||||
class AiohttpRequestMultiDictProxyInstances extends Multidict::MultiDictProxy::InstanceSource {
|
||||
AiohttpRequestMultiDictProxyInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = Request::instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() in ["query", "headers"]
|
||||
or
|
||||
// Handle the common case of `x = await request.post()`
|
||||
// but don't try to handle anything else, since we don't have an easy way to do this yet.
|
||||
// TODO: more complete handling of `await request.post()`
|
||||
exists(Await await, DataFlow::CallCfgNode call, DataFlow::AttrRead read |
|
||||
this.asExpr() = await
|
||||
|
|
||||
read.(DataFlow::AttrRead).getObject() = Request::instance() and
|
||||
read.(DataFlow::AttrRead).getAttributeName() = "post" and
|
||||
call.getFunction() = read and
|
||||
await.getValue() = call.asExpr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** An attribute read on an `aiohttp.web.Request` that is a `yarl.URL` instance. */
|
||||
class AiohttpRequestYarlUrlInstances extends Yarl::Url::InstanceSource {
|
||||
AiohttpRequestYarlUrlInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = Request::instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() in ["url", "rel_url"]
|
||||
}
|
||||
}
|
||||
|
||||
/** An attribute read on an `aiohttp.web.Request` that is a `aiohttp.StreamReader` instance. */
|
||||
class AiohttpRequestStreamReaderInstances extends StreamReader::InstanceSource {
|
||||
AiohttpRequestStreamReaderInstances() {
|
||||
this.(DataFlow::AttrRead).getObject() = Request::instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() in ["content", "_payload"]
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// aiohttp.web Response modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* An instantiation of `aiohttp.web.Response`.
|
||||
*
|
||||
* Note that `aiohttp.web.HTTPException` (and it's subclasses) is a subclass of `aiohttp.web.Response`.
|
||||
*
|
||||
* See
|
||||
* - https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.Response
|
||||
* - https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
|
||||
*/
|
||||
class AiohttpWebResponseInstantiation extends HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CallCfgNode {
|
||||
AiohttpWebResponseInstantiation() {
|
||||
this = API::moduleImport("aiohttp").getMember("web").getMember("Response").getACall()
|
||||
or
|
||||
exists(string httpExceptionClassName |
|
||||
httpExceptionClassName in [
|
||||
"HTTPException", "HTTPSuccessful", "HTTPOk", "HTTPCreated", "HTTPAccepted",
|
||||
"HTTPNonAuthoritativeInformation", "HTTPNoContent", "HTTPResetContent",
|
||||
"HTTPPartialContent", "HTTPRedirection", "HTTPMultipleChoices", "HTTPMovedPermanently",
|
||||
"HTTPFound", "HTTPSeeOther", "HTTPNotModified", "HTTPUseProxy", "HTTPTemporaryRedirect",
|
||||
"HTTPPermanentRedirect", "HTTPError", "HTTPClientError", "HTTPBadRequest",
|
||||
"HTTPUnauthorized", "HTTPPaymentRequired", "HTTPForbidden", "HTTPNotFound",
|
||||
"HTTPMethodNotAllowed", "HTTPNotAcceptable", "HTTPProxyAuthenticationRequired",
|
||||
"HTTPRequestTimeout", "HTTPConflict", "HTTPGone", "HTTPLengthRequired",
|
||||
"HTTPPreconditionFailed", "HTTPRequestEntityTooLarge", "HTTPRequestURITooLong",
|
||||
"HTTPUnsupportedMediaType", "HTTPRequestRangeNotSatisfiable", "HTTPExpectationFailed",
|
||||
"HTTPMisdirectedRequest", "HTTPUnprocessableEntity", "HTTPFailedDependency",
|
||||
"HTTPUpgradeRequired", "HTTPPreconditionRequired", "HTTPTooManyRequests",
|
||||
"HTTPRequestHeaderFieldsTooLarge", "HTTPUnavailableForLegalReasons", "HTTPServerError",
|
||||
"HTTPInternalServerError", "HTTPNotImplemented", "HTTPBadGateway",
|
||||
"HTTPServiceUnavailable", "HTTPGatewayTimeout", "HTTPVersionNotSupported",
|
||||
"HTTPVariantAlsoNegotiates", "HTTPInsufficientStorage", "HTTPNotExtended",
|
||||
"HTTPNetworkAuthenticationRequired"
|
||||
] and
|
||||
this =
|
||||
API::moduleImport("aiohttp").getMember("web").getMember(httpExceptionClassName).getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
result in [this.getArgByName("text"), this.getArgByName("body")]
|
||||
}
|
||||
|
||||
override DataFlow::Node getMimetypeOrContentTypeArg() {
|
||||
result = this.getArgByName("content_type")
|
||||
}
|
||||
|
||||
override string getMimetypeDefault() {
|
||||
exists(this.getArgByName("text")) and
|
||||
result = "text/plain"
|
||||
or
|
||||
not exists(this.getArgByName("text")) and
|
||||
result = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An instantiation of aiohttp.web HTTP redirect exception.
|
||||
*
|
||||
* See the part about redirects at https://docs.aiohttp.org/en/stable/web_quickstart.html#aiohttp-web-exceptions
|
||||
*/
|
||||
class AiohttpRedirectExceptionInstantiation extends AiohttpWebResponseInstantiation,
|
||||
HTTP::Server::HttpRedirectResponse::Range {
|
||||
AiohttpRedirectExceptionInstantiation() {
|
||||
exists(string httpRedirectExceptionClassName |
|
||||
httpRedirectExceptionClassName in [
|
||||
"HTTPMultipleChoices", "HTTPMovedPermanently", "HTTPFound", "HTTPSeeOther",
|
||||
"HTTPNotModified", "HTTPUseProxy", "HTTPTemporaryRedirect", "HTTPPermanentRedirect"
|
||||
] and
|
||||
this =
|
||||
API::moduleImport("aiohttp")
|
||||
.getMember("web")
|
||||
.getMember(httpRedirectExceptionClassName)
|
||||
.getACall()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
result in [this.getArg(0), this.getArgByName("location")]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ private module CryptographyModel {
|
||||
* Gets a predefined curve class from
|
||||
* `cryptography.hazmat.primitives.asymmetric.ec` with a specific key size (in bits).
|
||||
*/
|
||||
private DataFlow::Node curveClassWithKeySize(int keySize) {
|
||||
private API::Node predefinedCurveClass(int keySize) {
|
||||
exists(string curveName |
|
||||
result =
|
||||
API::moduleImport("cryptography")
|
||||
@@ -31,7 +31,6 @@ private module CryptographyModel {
|
||||
.getMember("asymmetric")
|
||||
.getMember("ec")
|
||||
.getMember(curveName)
|
||||
.getAUse()
|
||||
|
|
||||
// obtained by manually looking at source code in
|
||||
// https://github.com/pyca/cryptography/blob/cba69f1922803f4f29a3fde01741890d88b8e217/src/cryptography/hazmat/primitives/asymmetric/ec.py#L208-L300
|
||||
@@ -75,13 +74,30 @@ private module CryptographyModel {
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to a predefined curve class with a specific key size (in bits), as well as the origin of the class. */
|
||||
private DataFlow::LocalSourceNode curveClassWithKeySize(
|
||||
DataFlow::TypeTracker t, int keySize, DataFlow::Node origin
|
||||
) {
|
||||
t.start() and
|
||||
result = predefinedCurveClass(keySize).getAnImmediateUse() and
|
||||
origin = result
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = curveClassWithKeySize(t2, keySize, origin).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a reference to a predefined curve class with a specific key size (in bits), as well as the origin of the class. */
|
||||
DataFlow::Node curveClassWithKeySize(int keySize, DataFlow::Node origin) {
|
||||
curveClassWithKeySize(DataFlow::TypeTracker::end(), keySize, origin).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */
|
||||
private DataFlow::LocalSourceNode curveClassInstanceWithKeySize(
|
||||
DataFlow::TypeTracker t, int keySize, DataFlow::Node origin
|
||||
) {
|
||||
t.start() and
|
||||
result.(DataFlow::CallCfgNode).getFunction() = curveClassWithKeySize(keySize) and
|
||||
origin = result
|
||||
result.(DataFlow::CallCfgNode).getFunction() = curveClassWithKeySize(keySize, origin)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = curveClassInstanceWithKeySize(t2, keySize, origin).track(t2, t)
|
||||
@@ -164,6 +180,8 @@ private module CryptographyModel {
|
||||
|
||||
override int getKeySizeWithOrigin(DataFlow::Node origin) {
|
||||
this.getCurveArg() = Ecc::curveClassInstanceWithKeySize(result, origin)
|
||||
or
|
||||
this.getCurveArg() = Ecc::curveClassWithKeySize(result, origin)
|
||||
}
|
||||
|
||||
// Note: There is not really a key-size argument, since it's always specified by the curve.
|
||||
|
||||
@@ -11,6 +11,8 @@ private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.PEP249
|
||||
private import semmle.python.regex
|
||||
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
|
||||
private import semmle.python.frameworks.internal.SelfRefMixin
|
||||
|
||||
/**
|
||||
* Provides models for the `django` PyPI package.
|
||||
@@ -1386,40 +1388,6 @@ private module PrivateDjango {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Gets the last decorator call for the function `func`, if `func` has decorators.
|
||||
*/
|
||||
private Expr lastDecoratorCall(Function func) {
|
||||
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
not exists(Call other_decorator | other_decorator.getArg(0) = result)
|
||||
}
|
||||
|
||||
/** Adds the `getASelfRef` member predicate when modeling a class. */
|
||||
abstract private class SelfRefMixin extends Class {
|
||||
/**
|
||||
* Gets a reference to instances of this class, originating from a self parameter of
|
||||
* a method defined on this class.
|
||||
*
|
||||
* Note: TODO: This doesn't take MRO into account
|
||||
* Note: TODO: This doesn't take staticmethod/classmethod into account
|
||||
*/
|
||||
private DataFlow::LocalSourceNode getASelfRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to instances of this class, originating from a self parameter of
|
||||
* a method defined on this class.
|
||||
*
|
||||
* Note: TODO: This doesn't take MRO into account
|
||||
* Note: TODO: This doesn't take staticmethod/classmethod into account
|
||||
*/
|
||||
DataFlow::Node getASelfRef() { this.getASelfRef(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Form and form field modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1487,46 +1455,6 @@ private module PrivateDjango {
|
||||
// ---------------------------------------------------------------------------
|
||||
// routing modeling
|
||||
// ---------------------------------------------------------------------------
|
||||
/**
|
||||
* Gets a reference to the Function `func`.
|
||||
*
|
||||
* The idea is that this function should be used as a route handler when setting up a
|
||||
* route, but currently it just tracks all functions, since we can't do type-tracking
|
||||
* backwards yet (TODO).
|
||||
*/
|
||||
private DataFlow::LocalSourceNode djangoRouteHandlerFunctionTracker(
|
||||
DataFlow::TypeTracker t, Function func
|
||||
) {
|
||||
t.start() and
|
||||
(
|
||||
not exists(func.getADecorator()) and
|
||||
result.asExpr() = func.getDefinition()
|
||||
or
|
||||
// If the function has decorators, we still want to model the function as being
|
||||
// the request handler for a route setup. In such situations, we must track the
|
||||
// last decorator call instead of the function itself.
|
||||
//
|
||||
// Note that this means that we blindly ignore what the decorator actually does to
|
||||
// the function, which seems like an OK tradeoff.
|
||||
result.asExpr() = lastDecoratorCall(func)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
result = djangoRouteHandlerFunctionTracker(t2, func).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the Function `func`.
|
||||
*
|
||||
* The idea is that this function should be used as a route handler when setting up a
|
||||
* route, but currently it just tracks all functions, since we can't do type-tracking
|
||||
* backwards yet (TODO).
|
||||
*/
|
||||
private DataFlow::Node djangoRouteHandlerFunctionTracker(Function func) {
|
||||
djangoRouteHandlerFunctionTracker(DataFlow::TypeTracker::end(), func).flowsTo(result)
|
||||
}
|
||||
|
||||
/**
|
||||
* In order to recognize a class as being a django view class, based on the `as_view`
|
||||
* call, we need to be able to track such calls on _any_ class. This is provided by
|
||||
@@ -1613,7 +1541,7 @@ private module PrivateDjango {
|
||||
*/
|
||||
private class DjangoRouteHandler extends Function {
|
||||
DjangoRouteHandler() {
|
||||
exists(DjangoRouteSetup route | route.getViewArg() = djangoRouteHandlerFunctionTracker(this))
|
||||
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
|
||||
or
|
||||
any(DjangoViewClass vc).getARequestHandler() = this
|
||||
}
|
||||
@@ -1663,7 +1591,7 @@ private module PrivateDjango {
|
||||
abstract DataFlow::Node getViewArg();
|
||||
|
||||
final override DjangoRouteHandler getARequestHandler() {
|
||||
djangoRouteHandlerFunctionTracker(result) = getViewArg()
|
||||
poorMansFunctionTracker(result) = getViewArg()
|
||||
or
|
||||
exists(DjangoViewClass vc |
|
||||
getViewArg() = vc.asViewResult() and
|
||||
|
||||
88
python/ql/src/semmle/python/frameworks/Multidict.qll
Normal file
88
python/ql/src/semmle/python/frameworks/Multidict.qll
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `multidict` PyPI package.
|
||||
* See https://multidict.readthedocs.io/en/stable/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `multidict` PyPI package.
|
||||
* See https://multidict.readthedocs.io/en/stable/.
|
||||
*/
|
||||
module Multidict {
|
||||
/**
|
||||
* Provides models for a `MultiDictProxy` class:
|
||||
* - `multidict.MultiDictProxy`
|
||||
* - `multidict.CIMultiDictProxy`
|
||||
*
|
||||
* See https://multidict.readthedocs.io/en/stable/multidict.html#multidictproxy
|
||||
*/
|
||||
module MultiDictProxy {
|
||||
/** Gets a reference to a `MultiDictProxy` class. */
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("multidict").getMember(["MultiDictProxy", "CIMultiDictProxy"])
|
||||
}
|
||||
|
||||
/**
|
||||
* A source of instances of `multidict.MultiDictProxy`, extend this class to model
|
||||
* new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use `MultiDictProxy::instance()` predicate to get
|
||||
* references to instances of `multidict.MultiDictProxy`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of a `MultiDictProxy` class. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a `MultiDictProxy` class. */
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a `MultiDictProxy` class. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Taint propagation for `multidict.MultiDictProxy`.
|
||||
*
|
||||
* See https://multidict.readthedocs.io/en/stable/multidict.html#multidictproxy
|
||||
*/
|
||||
class MultiDictProxyAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// class instantiation
|
||||
exists(ClassInstantiation call |
|
||||
nodeFrom = call.getArg(0) and
|
||||
nodeTo = call
|
||||
)
|
||||
or
|
||||
// Methods
|
||||
//
|
||||
// TODO: When we have tools that make it easy, model these properly to handle
|
||||
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
|
||||
// (since it allows us to at least capture the most common cases).
|
||||
nodeFrom = instance() and
|
||||
exists(DataFlow::AttrRead attr | attr.getObject() = nodeFrom |
|
||||
// methods (non-async)
|
||||
attr.getAttributeName() in ["getone", "getall"] and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import PEP249
|
||||
private import semmle.python.frameworks.PEP249
|
||||
|
||||
/**
|
||||
* Provides models for the `MySQLdb` PyPI package.
|
||||
|
||||
@@ -10,7 +10,7 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import PEP249
|
||||
private import semmle.python.frameworks.PEP249
|
||||
|
||||
/**
|
||||
* Provides models for the `mysql-connector-python` package.
|
||||
|
||||
@@ -10,7 +10,7 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import PEP249
|
||||
private import semmle.python.frameworks.PEP249
|
||||
|
||||
/**
|
||||
* Provides models for the `psycopg2` PyPI package.
|
||||
|
||||
@@ -8,7 +8,7 @@ private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import PEP249
|
||||
private import semmle.python.frameworks.PEP249
|
||||
|
||||
/**
|
||||
* Provides models for the `PyMySQL` PyPI package.
|
||||
|
||||
@@ -9,7 +9,7 @@ private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import PEP249
|
||||
private import semmle.python.frameworks.PEP249
|
||||
|
||||
/** Provides models for the Python standard library. */
|
||||
private module Stdlib {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `flask` package.
|
||||
* 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the PyYAML package (obtained
|
||||
* via `import yaml`)
|
||||
* Provides classes modeling security-relevant aspects of the `PyYAML` PyPI package
|
||||
* (imported as `yaml`)
|
||||
*
|
||||
* See
|
||||
* - https://pyyaml.org/wiki/PyYAMLDocumentation
|
||||
@@ -14,8 +14,8 @@ private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the PyYAML package (obtained
|
||||
* via `import yaml`)
|
||||
* Provides classes modeling security-relevant aspects of the `PyYAML` PyPI package
|
||||
* (imported as `yaml`)
|
||||
*
|
||||
* See
|
||||
* - https://pyyaml.org/wiki/PyYAMLDocumentation
|
||||
|
||||
122
python/ql/src/semmle/python/frameworks/Yarl.qll
Normal file
122
python/ql/src/semmle/python/frameworks/Yarl.qll
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `yarl` PyPI package.
|
||||
* See https://yarl.readthedocs.io/en/stable/.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
private import semmle.python.frameworks.Multidict
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides models for the `yarl` PyPI package.
|
||||
* See https://multidict.readthedocs.io/en/stable/.
|
||||
*/
|
||||
module Yarl {
|
||||
/**
|
||||
* Provides models for a the `yarl.URL` class:
|
||||
*
|
||||
* See https://yarl.readthedocs.io/en/stable/api.html#yarl.URL
|
||||
*/
|
||||
module Url {
|
||||
/**
|
||||
* A source of instances of `yarl.URL`, extend this class to model new instances.
|
||||
*
|
||||
* This can include instantiations of the class, return values from function
|
||||
* calls, or a special parameter that will be set when functions are called by an external
|
||||
* library.
|
||||
*
|
||||
* Use `Url::instance()` predicate to get references to instances of `yarl.URL`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `yarl.URL`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = API::moduleImport("yarl").getMember("URL").getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `yarl.URL`. */
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `yarl.URL`. */
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Taint propagation for `yarl.URL`.
|
||||
*
|
||||
* See https://yarl.readthedocs.io/en/stable/api.html#yarl.URL
|
||||
*/
|
||||
class YarlUrlAdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
// class instantiation
|
||||
exists(ClassInstantiation call |
|
||||
nodeFrom in [call.getArg(0), call.getArgByName("val")] and
|
||||
nodeTo = call
|
||||
)
|
||||
or
|
||||
// Methods
|
||||
//
|
||||
// TODO: When we have tools that make it easy, model these properly to handle
|
||||
// `meth = obj.meth; meth()`. Until then, we'll use this more syntactic approach
|
||||
// (since it allows us to at least capture the most common cases).
|
||||
exists(DataFlow::AttrRead attr |
|
||||
// methods (that replaces part of URL, taken as only arguments)
|
||||
attr.getAttributeName() in [
|
||||
"with_scheme", "with_user", "with_password", "with_host", "with_port", "with_path",
|
||||
"with_query", "with_query", "update_query", "update_query", "with_fragment",
|
||||
"with_name",
|
||||
// join is a bit different, but is still correct to add here :+1:
|
||||
"join"
|
||||
] and
|
||||
(
|
||||
// obj -> obj.meth()
|
||||
nodeFrom = instance() and
|
||||
attr.getObject() = nodeFrom and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
|
||||
or
|
||||
// argument of obj.meth() -> obj.meth()
|
||||
attr.getObject() = instance() and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr and
|
||||
nodeFrom in [
|
||||
nodeTo.(DataFlow::CallCfgNode).getArg(_),
|
||||
nodeTo.(DataFlow::CallCfgNode).getArgByName(_)
|
||||
]
|
||||
)
|
||||
or
|
||||
// other methods
|
||||
nodeFrom = instance() and
|
||||
attr.getObject() = nodeFrom and
|
||||
attr.getAttributeName() in ["human_repr"] and
|
||||
nodeTo.(DataFlow::CallCfgNode).getFunction() = attr
|
||||
)
|
||||
or
|
||||
// Attributes
|
||||
nodeFrom = instance() and
|
||||
nodeTo.(DataFlow::AttrRead).getObject() = nodeFrom and
|
||||
nodeTo.(DataFlow::AttrRead).getAttributeName() in [
|
||||
"user", "raw_user", "password", "raw_password", "host", "raw_host", "port",
|
||||
"explicit_port", "authority", "raw_authority", "path", "raw_path", "path_qs",
|
||||
"raw_path_qs", "query_string", "raw_query_string", "fragment", "raw_fragment", "parts",
|
||||
"raw_parts", "name", "raw_name", "query"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/** An attribute read on a `yarl.URL` that is a `MultiDictProxy` instance. */
|
||||
class YarlUrlMultiDictProxyInstance extends Multidict::MultiDictProxy::InstanceSource {
|
||||
YarlUrlMultiDictProxyInstance() {
|
||||
this.(DataFlow::AttrRead).getObject() = Yarl::Url::instance() and
|
||||
this.(DataFlow::AttrRead).getAttributeName() = "query"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Notice: The predicates provided in this module is a poor mans solution for function
|
||||
* resolution, and does not handle anything but the most simple cases.
|
||||
*
|
||||
* For example, in the code below, we're not able to tell anything about
|
||||
* `inst.my_method` (which is a bound-method)
|
||||
* ```py
|
||||
* class MyClass:
|
||||
* def my_method(self):
|
||||
* pass
|
||||
*
|
||||
* inst = MyClass()
|
||||
* print(inst.my_method)
|
||||
* ```
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/**
|
||||
* Gets the last decorator call for the function `func`, if `func` has decorators.
|
||||
*/
|
||||
private Expr lastDecoratorCall(Function func) {
|
||||
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
|
||||
not exists(Call other_decorator | other_decorator.getArg(0) = result)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the Function `func`.
|
||||
*
|
||||
* Notice: This is a poor mans solution for function resolution, and does not handle
|
||||
* anything but the most simple cases.
|
||||
*
|
||||
* For example, in the code below, we're not able to tell anything about
|
||||
* `inst.my_method` (which is a bound-method)
|
||||
* ```py
|
||||
* class MyClass:
|
||||
* def my_method(self):
|
||||
* pass
|
||||
*
|
||||
* inst = MyClass()
|
||||
* print(inst.my_method)
|
||||
* ```
|
||||
*/
|
||||
private DataFlow::LocalSourceNode poorMansFunctionTracker(DataFlow::TypeTracker t, Function func) {
|
||||
t.start() and
|
||||
(
|
||||
not exists(func.getADecorator()) and
|
||||
result.asExpr() = func.getDefinition()
|
||||
or
|
||||
// If the function has decorators, we still want to model the function as being
|
||||
// the request handler for a route setup. In such situations, we must track the
|
||||
// last decorator call instead of the function itself.
|
||||
//
|
||||
// Note that this means that we blindly ignore what the decorator actually does to
|
||||
// the function, which seems like an OK tradeoff.
|
||||
result.asExpr() = lastDecoratorCall(func)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = poorMansFunctionTracker(t2, func).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Gets a reference to the Function `func`.
|
||||
*
|
||||
* Notice: This is a poor mans solution for function resolution, and does not handle
|
||||
* anything but the most simple cases.
|
||||
*
|
||||
* For example, in the code below, we're not able to tell anything about
|
||||
* `inst.my_method` (which is a bound-method)
|
||||
* ```py
|
||||
* class MyClass:
|
||||
* def my_method(self):
|
||||
* pass
|
||||
*
|
||||
* inst = MyClass()
|
||||
* print(inst.my_method)
|
||||
* ```
|
||||
*/
|
||||
DataFlow::Node poorMansFunctionTracker(Function func) {
|
||||
poorMansFunctionTracker(DataFlow::TypeTracker::end(), func).flowsTo(result)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Provides the `SelfRefMixin` class.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
/**
|
||||
* INTERNAL: Do not use.
|
||||
*
|
||||
* Adds the `getASelfRef` member predicate when modeling a class.
|
||||
*/
|
||||
abstract class SelfRefMixin extends Class {
|
||||
/**
|
||||
* Gets a reference to instances of this class, originating from a self parameter of
|
||||
* a method defined on this class.
|
||||
*
|
||||
* Note: TODO: This doesn't take MRO into account
|
||||
* Note: TODO: This doesn't take staticmethod/classmethod into account
|
||||
*/
|
||||
private DataFlow::LocalSourceNode getASelfRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.(DataFlow::ParameterNode).getParameter() = this.getAMethod().getArg(0)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = this.getASelfRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to instances of this class, originating from a self parameter of
|
||||
* a method defined on this class.
|
||||
*
|
||||
* Note: TODO: This doesn't take MRO into account
|
||||
* Note: TODO: This doesn't take staticmethod/classmethod into account
|
||||
*/
|
||||
DataFlow::Node getASelfRef() { this.getASelfRef(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
| UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. |
|
||||
| expressions_test.py:3:5:3:21 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. |
|
||||
| expressions_test.py:2:5:2:21 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. |
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
|
||||
def use_of_apply(func, args):
|
||||
apply(func, args)
|
||||
|
||||
def use_of_input():
|
||||
return input()
|
||||
|
||||
def use_of_input():
|
||||
return input() # NOT OK
|
||||
|
||||
|
||||
def not_use_of_input():
|
||||
input = raw_input
|
||||
return input() # OK
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# if you enter 4+4 each time, you'll see that results are: 8, '4+4', 8
|
||||
print("result:", use_of_input())
|
||||
print("result:", not_use_of_input())
|
||||
print("result:", use_of_input())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
|
||||
string prettyExp(Expr e) {
|
||||
string prettyExpr(Expr e) {
|
||||
not e instanceof Num and
|
||||
not e instanceof StrConst and
|
||||
not e instanceof Subscript and
|
||||
@@ -15,17 +15,41 @@ string prettyExp(Expr e) {
|
||||
e.(StrConst).getPrefix() + e.(StrConst).getText() +
|
||||
e.(StrConst).getPrefix().regexpReplaceAll("[a-zA-Z]+", "")
|
||||
or
|
||||
result = prettyExp(e.(Subscript).getObject()) + "[" + prettyExp(e.(Subscript).getIndex()) + "]"
|
||||
result = prettyExpr(e.(Subscript).getObject()) + "[" + prettyExpr(e.(Subscript).getIndex()) + "]"
|
||||
or
|
||||
(
|
||||
if exists(e.(Call).getAnArg()) or exists(e.(Call).getANamedArg())
|
||||
then result = prettyExp(e.(Call).getFunc()) + "(..)"
|
||||
else result = prettyExp(e.(Call).getFunc()) + "()"
|
||||
then result = prettyExpr(e.(Call).getFunc()) + "(..)"
|
||||
else result = prettyExpr(e.(Call).getFunc()) + "()"
|
||||
)
|
||||
or
|
||||
result = prettyExp(e.(Attribute).getObject()) + "." + e.(Attribute).getName()
|
||||
result = prettyExpr(e.(Attribute).getObject()) + "." + e.(Attribute).getName()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets pretty-printed version of the DataFlow::Node `node`
|
||||
*/
|
||||
bindingset[node]
|
||||
string prettyNode(DataFlow::Node node) {
|
||||
if exists(node.asExpr()) then result = prettyExp(node.asExpr()) else result = node.toString()
|
||||
if exists(node.asExpr()) then result = prettyExpr(node.asExpr()) else result = node.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets pretty-printed version of the DataFlow::Node `node`, that is suitable for use
|
||||
* with `TestUtilities.InlineExpectationsTest` (that is, no spaces unless required).
|
||||
*/
|
||||
bindingset[node]
|
||||
string prettyNodeForInlineTest(DataFlow::Node node) {
|
||||
exists(node.asExpr()) and
|
||||
result = prettyExpr(node.asExpr())
|
||||
or
|
||||
exists(Expr e | e = node.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr() |
|
||||
// since PostUpdateNode both has space in the `[post <thing>]` annotation, and does
|
||||
// not pretty print the pre-update node, we do custom handling of this.
|
||||
result = "[post]" + prettyExpr(e)
|
||||
)
|
||||
or
|
||||
not exists(node.asExpr()) and
|
||||
not exists(Expr e | e = node.(DataFlow::PostUpdateNode).getPreUpdateNode().asExpr()) and
|
||||
result = node.toString()
|
||||
}
|
||||
|
||||
@@ -46,6 +46,6 @@ query predicate test_taint(string arg_location, string test_res, string scope_na
|
||||
arg_location = arg.getLocation().toString() and
|
||||
test_res = test_res and
|
||||
scope_name = call.getScope().getName() and
|
||||
repr = prettyExp(arg)
|
||||
repr = prettyExpr(arg)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Add taintlib to PATH so it can be imported during runtime without any hassle
|
||||
import sys; import os; sys.path.append(os.path.dirname(os.path.dirname((__file__))))
|
||||
from taintlib import *
|
||||
|
||||
# This has no runtime impact, but allows autocomplete to work
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from ..taintlib import *
|
||||
|
||||
|
||||
# Actual tests
|
||||
|
||||
from io import StringIO
|
||||
import json
|
||||
|
||||
def test():
|
||||
print("\n# test")
|
||||
ts = TAINTED_STRING
|
||||
|
||||
encoded = json.dumps(ts)
|
||||
|
||||
ensure_tainted(
|
||||
encoded, # $ tainted
|
||||
json.dumps(ts), # $ tainted
|
||||
json.dumps(obj=ts), # $ tainted
|
||||
json.loads(encoded), # $ tainted
|
||||
json.loads(s=encoded), # $ tainted
|
||||
)
|
||||
|
||||
# load/dump with file-like
|
||||
tainted_filelike = StringIO()
|
||||
json.dump(ts, tainted_filelike)
|
||||
|
||||
tainted_filelike.seek(0)
|
||||
ensure_tainted(
|
||||
tainted_filelike, # $ tainted
|
||||
json.load(tainted_filelike), # $ tainted
|
||||
)
|
||||
|
||||
# load/dump with file-like using keyword-args
|
||||
tainted_filelike = StringIO()
|
||||
json.dump(obj=ts, fp=tainted_filelike)
|
||||
|
||||
tainted_filelike.seek(0)
|
||||
ensure_tainted(
|
||||
tainted_filelike, # $ tainted
|
||||
json.load(fp=tainted_filelike), # $ tainted
|
||||
)
|
||||
|
||||
|
||||
# Make tests runable
|
||||
|
||||
test()
|
||||
@@ -30,8 +30,28 @@ def test_incompatible_types():
|
||||
x.field = str("Hello") # $str=field str SPURIOUS: int=field int
|
||||
expects_string(x) # $ str=field SPURIOUS: int=field
|
||||
|
||||
# set in different function
|
||||
def set_foo(some_class_instance): # $ tracked=foo
|
||||
some_class_instance.foo = tracked # $ tracked=foo tracked
|
||||
|
||||
def test_set_x():
|
||||
x = SomeClass() # $ MISSING: tracked=foo
|
||||
set_foo(x) # $ MISSING: tracked=foo
|
||||
print(x.foo) # $ MISSING: tracked=foo tracked
|
||||
|
||||
# return from a different function
|
||||
def create_with_foo():
|
||||
x = SomeClass() # $ tracked=foo
|
||||
x.foo = tracked # $ tracked=foo tracked
|
||||
return x # $ tracked=foo
|
||||
|
||||
def test_create_with_foo():
|
||||
x = create_with_foo() # $ tracked=foo
|
||||
print(x.foo) # $ tracked=foo tracked
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Attributes assigned statically to a class
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
class MyClass: # $tracked=field
|
||||
field = tracked # $tracked
|
||||
@@ -40,7 +60,9 @@ lookup = MyClass.field # $tracked tracked=field
|
||||
instance = MyClass() # $tracked=field
|
||||
lookup2 = instance.field # MISSING: tracked
|
||||
|
||||
## Dynamic attribute access
|
||||
# ------------------------------------------------------------------------------
|
||||
# Dynamic attribute access
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Via `getattr`/`setattr`
|
||||
|
||||
@@ -99,3 +121,41 @@ def dunder_dict_indirect_read():
|
||||
do_stuff(y) # $ MISSING: tracked
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Tracking of attribute on class instance
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# attribute set in method
|
||||
# inspired by https://github.com/github/codeql/pull/6023
|
||||
|
||||
class MyClass2(object):
|
||||
def __init__(self): # $ tracked=foo
|
||||
self.foo = tracked # $ tracked=foo tracked
|
||||
|
||||
def print_foo(self): # $ MISSING: tracked=foo
|
||||
print(self.foo) # $ MISSING: tracked=foo tracked
|
||||
|
||||
def possibly_uncalled_method(self): # $ MISSING: tracked=foo
|
||||
print(self.foo) # $ MISSING: tracked=foo tracked
|
||||
|
||||
instance = MyClass2()
|
||||
print(instance.foo) # $ MISSING: tracked=foo tracked
|
||||
instance.print_foo() # $ MISSING: tracked=foo
|
||||
|
||||
|
||||
# attribute set from outside of class
|
||||
|
||||
class MyClass3(object):
|
||||
def print_self(self): # $ tracked=foo
|
||||
print(self) # $ tracked=foo
|
||||
|
||||
def print_foo(self): # $ tracked=foo
|
||||
print(self.foo) # $ tracked=foo tracked
|
||||
|
||||
def possibly_uncalled_method(self): # $ MISSING: tracked=foo
|
||||
print(self.foo) # $ MISSING: tracked=foo tracked
|
||||
|
||||
instance = MyClass3() # $ tracked=foo
|
||||
instance.print_self() # $ tracked=foo
|
||||
instance.foo = tracked # $ tracked=foo tracked
|
||||
instance.print_foo() # $ tracked=foo
|
||||
|
||||
@@ -2,19 +2,7 @@ import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.Concepts
|
||||
import TestUtilities.InlineExpectationsTest
|
||||
|
||||
string value_from_expr(Expr e) {
|
||||
// TODO: This one is starting to look like `repr` predicate from TestTaintLib
|
||||
result =
|
||||
e.(StrConst).getPrefix() + e.(StrConst).getText() +
|
||||
e.(StrConst).getPrefix().regexpReplaceAll("[a-zA-Z]+", "")
|
||||
or
|
||||
result = e.(Name).getId()
|
||||
or
|
||||
not e instanceof StrConst and
|
||||
not e instanceof Name and
|
||||
result = e.toString()
|
||||
}
|
||||
import experimental.dataflow.TestUtil.PrintNode
|
||||
|
||||
class SystemCommandExecutionTest extends InlineExpectationsTest {
|
||||
SystemCommandExecutionTest() { this = "SystemCommandExecutionTest" }
|
||||
@@ -27,7 +15,7 @@ class SystemCommandExecutionTest extends InlineExpectationsTest {
|
||||
command = sce.getCommand() and
|
||||
location = command.getLocation() and
|
||||
element = command.toString() and
|
||||
value = value_from_expr(command.asExpr()) and
|
||||
value = prettyNodeForInlineTest(command) and
|
||||
tag = "getCommand"
|
||||
)
|
||||
}
|
||||
@@ -46,7 +34,7 @@ class DecodingTest extends InlineExpectationsTest {
|
||||
exists(DataFlow::Node data |
|
||||
location = data.getLocation() and
|
||||
element = data.toString() and
|
||||
value = value_from_expr(data.asExpr()) and
|
||||
value = prettyNodeForInlineTest(data) and
|
||||
(
|
||||
data = d.getAnInput() and
|
||||
tag = "decodeInput"
|
||||
@@ -84,7 +72,7 @@ class EncodingTest extends InlineExpectationsTest {
|
||||
exists(DataFlow::Node data |
|
||||
location = data.getLocation() and
|
||||
element = data.toString() and
|
||||
value = value_from_expr(data.asExpr()) and
|
||||
value = prettyNodeForInlineTest(data) and
|
||||
(
|
||||
data = e.getAnInput() and
|
||||
tag = "encodeInput"
|
||||
@@ -117,7 +105,7 @@ class CodeExecutionTest extends InlineExpectationsTest {
|
||||
code = ce.getCode() and
|
||||
location = code.getLocation() and
|
||||
element = code.toString() and
|
||||
value = value_from_expr(code.asExpr()) and
|
||||
value = prettyNodeForInlineTest(code) and
|
||||
tag = "getCode"
|
||||
)
|
||||
}
|
||||
@@ -135,7 +123,7 @@ class SqlExecutionTest extends InlineExpectationsTest {
|
||||
sql = e.getSql() and
|
||||
location = e.getLocation() and
|
||||
element = sql.toString() and
|
||||
value = value_from_expr(sql.asExpr()) and
|
||||
value = prettyNodeForInlineTest(sql) and
|
||||
tag = "getSql"
|
||||
)
|
||||
}
|
||||
@@ -218,7 +206,7 @@ class HttpServerHttpResponseTest extends InlineExpectationsTest {
|
||||
exists(HTTP::Server::HttpResponse response |
|
||||
location = response.getLocation() and
|
||||
element = response.toString() and
|
||||
value = value_from_expr(response.getBody().asExpr()) and
|
||||
value = prettyNodeForInlineTest(response.getBody()) and
|
||||
tag = "responseBody"
|
||||
)
|
||||
or
|
||||
@@ -257,7 +245,7 @@ class HttpServerHttpRedirectResponseTest extends InlineExpectationsTest {
|
||||
exists(HTTP::Server::HttpRedirectResponse redirect |
|
||||
location = redirect.getLocation() and
|
||||
element = redirect.toString() and
|
||||
value = value_from_expr(redirect.getRedirectLocation().asExpr()) and
|
||||
value = prettyNodeForInlineTest(redirect.getRedirectLocation()) and
|
||||
tag = "redirectLocation"
|
||||
)
|
||||
)
|
||||
@@ -275,7 +263,7 @@ class FileSystemAccessTest extends InlineExpectationsTest {
|
||||
path = a.getAPathArgument() and
|
||||
location = a.getLocation() and
|
||||
element = path.toString() and
|
||||
value = value_from_expr(path.asExpr()) and
|
||||
value = prettyNodeForInlineTest(path) and
|
||||
tag = "getAPathArgument"
|
||||
)
|
||||
}
|
||||
@@ -309,7 +297,7 @@ class SafeAccessCheckTest extends InlineExpectationsTest {
|
||||
location = c.getLocation() and
|
||||
(
|
||||
element = checks.toString() and
|
||||
value = value_from_expr(checks.asExpr()) and
|
||||
value = prettyNodeForInlineTest(checks) and
|
||||
tag = "checks"
|
||||
or
|
||||
element = branch.toString() and
|
||||
@@ -361,7 +349,7 @@ class CryptographicOperationTest extends InlineExpectationsTest {
|
||||
tag = "CryptographicOperation"
|
||||
or
|
||||
element = cryptoOperation.toString() and
|
||||
value = value_from_expr(cryptoOperation.getAnInput().asExpr()) and
|
||||
value = prettyNodeForInlineTest(cryptoOperation.getAnInput()) and
|
||||
tag = "CryptographicOperationInput"
|
||||
or
|
||||
element = cryptoOperation.toString() and
|
||||
|
||||
@@ -58,7 +58,7 @@ class InlineTaintTest extends InlineExpectationsTest {
|
||||
exists(DataFlow::Node sink |
|
||||
any(TestTaintTrackingConfiguration config).hasFlow(_, sink) and
|
||||
location = sink.getLocation() and
|
||||
element = prettyExp(sink.asExpr()) and
|
||||
element = prettyExpr(sink.asExpr()) and
|
||||
value = "" and
|
||||
tag = "tainted"
|
||||
)
|
||||
@@ -84,7 +84,7 @@ query predicate untaintedArgumentToEnsureTaintedNotMarkedAsMissing(
|
||||
error = "ERROR, you should add `# $ MISSING: tainted` annotation" and
|
||||
exists(DataFlow::Node sink |
|
||||
sink = shouldBeTainted() and
|
||||
element = prettyExp(sink.asExpr()) and
|
||||
element = prettyExpr(sink.asExpr()) and
|
||||
not any(TestTaintTrackingConfiguration config).hasFlow(_, sink) and
|
||||
location = sink.getLocation() and
|
||||
not exists(FalseNegativeExpectation missingResult |
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
edges
|
||||
| ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | ldap3_bad.py:13:17:13:28 | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | ldap3_bad.py:14:21:14:27 | ControlFlowNode for request |
|
||||
| ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | ldap3_bad.py:14:21:14:32 | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:13:17:13:28 | ControlFlowNode for Attribute | ldap3_bad.py:13:17:13:34 | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:13:17:13:34 | ControlFlowNode for Subscript | ldap3_bad.py:21:17:21:18 | ControlFlowNode for dn |
|
||||
| ldap3_bad.py:14:21:14:27 | ControlFlowNode for request | ldap3_bad.py:14:21:14:32 | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:14:21:14:32 | ControlFlowNode for Attribute | ldap3_bad.py:14:21:14:44 | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:14:21:14:44 | ControlFlowNode for Subscript | ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter |
|
||||
| ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | ldap3_bad.py:30:17:30:28 | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | ldap3_bad.py:31:21:31:27 | ControlFlowNode for request |
|
||||
| ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | ldap3_bad.py:31:21:31:32 | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:30:17:30:28 | ControlFlowNode for Attribute | ldap3_bad.py:30:17:30:34 | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:30:17:30:34 | ControlFlowNode for Subscript | ldap3_bad.py:38:9:38:10 | ControlFlowNode for dn |
|
||||
| ldap3_bad.py:31:21:31:27 | ControlFlowNode for request | ldap3_bad.py:31:21:31:32 | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:31:21:31:32 | ControlFlowNode for Attribute | ldap3_bad.py:31:21:31:44 | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:31:21:31:44 | ControlFlowNode for Subscript | ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter |
|
||||
| ldap_bad.py:13:17:13:23 | ControlFlowNode for request | ldap_bad.py:13:17:13:28 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:13:17:13:23 | ControlFlowNode for request | ldap_bad.py:14:21:14:27 | ControlFlowNode for request |
|
||||
| ldap_bad.py:13:17:13:23 | ControlFlowNode for request | ldap_bad.py:14:21:14:32 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:13:17:13:28 | ControlFlowNode for Attribute | ldap_bad.py:13:17:13:34 | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:13:17:13:34 | ControlFlowNode for Subscript | ldap_bad.py:21:9:21:10 | ControlFlowNode for dn |
|
||||
| ldap_bad.py:14:21:14:27 | ControlFlowNode for request | ldap_bad.py:14:21:14:32 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:14:21:14:32 | ControlFlowNode for Attribute | ldap_bad.py:14:21:14:44 | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:14:21:14:44 | ControlFlowNode for Subscript | ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter |
|
||||
| ldap_bad.py:30:17:30:23 | ControlFlowNode for request | ldap_bad.py:30:17:30:28 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:30:17:30:23 | ControlFlowNode for request | ldap_bad.py:31:21:31:27 | ControlFlowNode for request |
|
||||
| ldap_bad.py:30:17:30:23 | ControlFlowNode for request | ldap_bad.py:31:21:31:32 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:30:17:30:28 | ControlFlowNode for Attribute | ldap_bad.py:30:17:30:34 | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:30:17:30:34 | ControlFlowNode for Subscript | ldap_bad.py:37:9:37:10 | ControlFlowNode for dn |
|
||||
| ldap_bad.py:31:21:31:27 | ControlFlowNode for request | ldap_bad.py:31:21:31:32 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:31:21:31:32 | ControlFlowNode for Attribute | ldap_bad.py:31:21:31:44 | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:31:21:31:44 | ControlFlowNode for Subscript | ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter |
|
||||
| ldap_bad.py:47:17:47:23 | ControlFlowNode for request | ldap_bad.py:47:17:47:28 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:47:17:47:23 | ControlFlowNode for request | ldap_bad.py:48:21:48:27 | ControlFlowNode for request |
|
||||
| ldap_bad.py:47:17:47:23 | ControlFlowNode for request | ldap_bad.py:48:21:48:32 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:47:17:47:28 | ControlFlowNode for Attribute | ldap_bad.py:47:17:47:34 | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:47:17:47:34 | ControlFlowNode for Subscript | ldap_bad.py:55:9:55:10 | ControlFlowNode for dn |
|
||||
| ldap_bad.py:48:21:48:27 | ControlFlowNode for request | ldap_bad.py:48:21:48:32 | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:48:21:48:32 | ControlFlowNode for Attribute | ldap_bad.py:48:21:48:44 | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:48:21:48:44 | ControlFlowNode for Subscript | ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter |
|
||||
nodes
|
||||
| ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap3_bad.py:13:17:13:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:13:17:13:34 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:14:21:14:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap3_bad.py:14:21:14:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:14:21:14:44 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:21:17:21:18 | ControlFlowNode for dn | semmle.label | ControlFlowNode for dn |
|
||||
| ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | semmle.label | ControlFlowNode for search_filter |
|
||||
| ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap3_bad.py:30:17:30:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:30:17:30:34 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:31:21:31:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap3_bad.py:31:21:31:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap3_bad.py:31:21:31:44 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap3_bad.py:38:9:38:10 | ControlFlowNode for dn | semmle.label | ControlFlowNode for dn |
|
||||
| ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | semmle.label | ControlFlowNode for search_filter |
|
||||
| ldap_bad.py:13:17:13:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap_bad.py:13:17:13:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:13:17:13:34 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:14:21:14:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap_bad.py:14:21:14:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:14:21:14:44 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:21:9:21:10 | ControlFlowNode for dn | semmle.label | ControlFlowNode for dn |
|
||||
| ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | semmle.label | ControlFlowNode for search_filter |
|
||||
| ldap_bad.py:30:17:30:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap_bad.py:30:17:30:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:30:17:30:34 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:31:21:31:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap_bad.py:31:21:31:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:31:21:31:44 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:37:9:37:10 | ControlFlowNode for dn | semmle.label | ControlFlowNode for dn |
|
||||
| ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | semmle.label | ControlFlowNode for search_filter |
|
||||
| ldap_bad.py:47:17:47:23 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap_bad.py:47:17:47:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:47:17:47:34 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:48:21:48:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
|
||||
| ldap_bad.py:48:21:48:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
|
||||
| ldap_bad.py:48:21:48:44 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
|
||||
| ldap_bad.py:55:9:55:10 | ControlFlowNode for dn | semmle.label | ControlFlowNode for dn |
|
||||
| ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | semmle.label | ControlFlowNode for search_filter |
|
||||
#select
|
||||
| ldap3_bad.py:21:17:21:18 | ControlFlowNode for dn | ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | ldap3_bad.py:21:17:21:18 | ControlFlowNode for dn | $@ LDAP query parameter comes from $@. | ldap3_bad.py:21:17:21:18 | ControlFlowNode for dn | This | ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | This | ldap3_bad.py:13:17:13:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | ldap3_bad.py:14:21:14:27 | ControlFlowNode for request | ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap3_bad.py:21:21:21:33 | ControlFlowNode for search_filter | This | ldap3_bad.py:14:21:14:27 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap3_bad.py:38:9:38:10 | ControlFlowNode for dn | ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | ldap3_bad.py:38:9:38:10 | ControlFlowNode for dn | $@ LDAP query parameter comes from $@. | ldap3_bad.py:38:9:38:10 | ControlFlowNode for dn | This | ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | This | ldap3_bad.py:30:17:30:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | ldap3_bad.py:31:21:31:27 | ControlFlowNode for request | ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap3_bad.py:38:13:38:25 | ControlFlowNode for search_filter | This | ldap3_bad.py:31:21:31:27 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:21:9:21:10 | ControlFlowNode for dn | ldap_bad.py:13:17:13:23 | ControlFlowNode for request | ldap_bad.py:21:9:21:10 | ControlFlowNode for dn | $@ LDAP query parameter comes from $@. | ldap_bad.py:21:9:21:10 | ControlFlowNode for dn | This | ldap_bad.py:13:17:13:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | ldap_bad.py:13:17:13:23 | ControlFlowNode for request | ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | This | ldap_bad.py:13:17:13:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | ldap_bad.py:14:21:14:27 | ControlFlowNode for request | ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap_bad.py:21:33:21:45 | ControlFlowNode for search_filter | This | ldap_bad.py:14:21:14:27 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:37:9:37:10 | ControlFlowNode for dn | ldap_bad.py:30:17:30:23 | ControlFlowNode for request | ldap_bad.py:37:9:37:10 | ControlFlowNode for dn | $@ LDAP query parameter comes from $@. | ldap_bad.py:37:9:37:10 | ControlFlowNode for dn | This | ldap_bad.py:30:17:30:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | ldap_bad.py:30:17:30:23 | ControlFlowNode for request | ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | This | ldap_bad.py:30:17:30:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | ldap_bad.py:31:21:31:27 | ControlFlowNode for request | ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap_bad.py:37:33:37:45 | ControlFlowNode for search_filter | This | ldap_bad.py:31:21:31:27 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:55:9:55:10 | ControlFlowNode for dn | ldap_bad.py:47:17:47:23 | ControlFlowNode for request | ldap_bad.py:55:9:55:10 | ControlFlowNode for dn | $@ LDAP query parameter comes from $@. | ldap_bad.py:55:9:55:10 | ControlFlowNode for dn | This | ldap_bad.py:47:17:47:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | ldap_bad.py:47:17:47:23 | ControlFlowNode for request | ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | This | ldap_bad.py:47:17:47:23 | ControlFlowNode for request | a user-provided value |
|
||||
| ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | ldap_bad.py:48:21:48:27 | ControlFlowNode for request | ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | $@ LDAP query parameter comes from $@. | ldap_bad.py:55:43:55:55 | ControlFlowNode for search_filter | This | ldap_bad.py:48:21:48:27 | ControlFlowNode for request | a user-provided value |
|
||||
@@ -0,0 +1 @@
|
||||
experimental/Security/CWE-090/LDAPInjection.ql
|
||||
@@ -0,0 +1,41 @@
|
||||
from flask import request, Flask
|
||||
import ldap3
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
"""
|
||||
A RemoteFlowSource is used directly as DN and search filter
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
srv = ldap3.Server('ldap://127.0.0.1')
|
||||
conn = ldap3.Connection(srv, user=dn, auto_bind=True)
|
||||
conn.search(dn, search_filter)
|
||||
|
||||
|
||||
@app.route("/direct")
|
||||
def direct():
|
||||
"""
|
||||
A RemoteFlowSource is used directly as DN and search filter using a oneline call to .search
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
srv = ldap3.Server('ldap://127.0.0.1')
|
||||
conn = ldap3.Connection(srv, user=dn, auto_bind=True).search(
|
||||
dn, search_filter)
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app.run(debug=True)
|
||||
@@ -0,0 +1,49 @@
|
||||
from flask import request, Flask
|
||||
import ldap3
|
||||
from ldap3.utils.dn import escape_rdn
|
||||
from ldap3.utils.conv import escape_filter_chars
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
"""
|
||||
A RemoteFlowSource is sanitized and used as DN and search filter
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = escape_rdn(unsafe_dc)
|
||||
safe_filter = escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
srv = ldap3.Server('ldap://127.0.0.1')
|
||||
conn = ldap3.Connection(srv, user=dn, auto_bind=True)
|
||||
conn.search(dn, search_filter)
|
||||
|
||||
|
||||
@app.route("/direct")
|
||||
def direct():
|
||||
"""
|
||||
A RemoteFlowSource is sanitized and used as DN and search filter using a oneline call to .search
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = escape_rdn(unsafe_dc)
|
||||
safe_filter = escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
srv = ldap3.Server('ldap://127.0.0.1')
|
||||
conn = ldap3.Connection(srv, user=dn, auto_bind=True).search(
|
||||
dn, search_filter)
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app.run(debug=True)
|
||||
@@ -0,0 +1,59 @@
|
||||
from flask import request, Flask
|
||||
import ldap
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
"""
|
||||
A RemoteFlowSource is used directly as DN and search filter
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
ldap_connection = ldap.initialize("ldap://127.0.0.1")
|
||||
user = ldap_connection.search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, search_filter)
|
||||
|
||||
|
||||
@app.route("/direct")
|
||||
def direct():
|
||||
"""
|
||||
A RemoteFlowSource is used directly as DN and search filter using a oneline call to .search_s
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
user = ldap.initialize("ldap://127.0.0.1").search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, search_filter)
|
||||
|
||||
|
||||
@app.route("/normal_argbyname")
|
||||
def normal_argbyname():
|
||||
"""
|
||||
A RemoteFlowSource is used directly as DN and search filter, while the search filter is specified as
|
||||
an argument by name
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
dn = "dc={}".format(unsafe_dc)
|
||||
search_filter = "(user={})".format(unsafe_filter)
|
||||
|
||||
ldap_connection = ldap.initialize("ldap://127.0.0.1")
|
||||
user = ldap_connection.search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, filterstr=search_filter)
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app.run(debug=True)
|
||||
@@ -0,0 +1,70 @@
|
||||
from flask import request, Flask
|
||||
import ldap
|
||||
import ldap.filter
|
||||
import ldap.dn
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route("/normal")
|
||||
def normal():
|
||||
"""
|
||||
A RemoteFlowSource is sanitized and used as DN and search filter
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = ldap.dn.escape_dn_chars(unsafe_dc)
|
||||
safe_filter = ldap.filter.escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
ldap_connection = ldap.initialize("ldap://127.0.0.1")
|
||||
user = ldap_connection.search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, search_filter)
|
||||
|
||||
|
||||
@app.route("/direct")
|
||||
def direct():
|
||||
"""
|
||||
A RemoteFlowSource is sanitized and used as DN and search filter using a oneline call to .search_s
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = ldap.dn.escape_dn_chars(unsafe_dc)
|
||||
safe_filter = ldap.filter.escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
user = ldap.initialize("ldap://127.0.0.1").search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, search_filter, ["testAttr1", "testAttr2"])
|
||||
|
||||
|
||||
@app.route("/normal_argbyname")
|
||||
def normal_argbyname():
|
||||
"""
|
||||
A RemoteFlowSource is sanitized and used as DN and search filter, while the search filter is specified as
|
||||
an argument by name
|
||||
"""
|
||||
|
||||
unsafe_dc = request.args['dc']
|
||||
unsafe_filter = request.args['username']
|
||||
|
||||
safe_dc = ldap.dn.escape_dn_chars(unsafe_dc)
|
||||
safe_filter = ldap.filter.escape_filter_chars(unsafe_filter)
|
||||
|
||||
dn = "dc={}".format(safe_dc)
|
||||
search_filter = "(user={})".format(safe_filter)
|
||||
|
||||
ldap_connection = ldap.initialize("ldap://127.0.0.1")
|
||||
user = ldap_connection.search_s(
|
||||
dn, ldap.SCOPE_SUBTREE, filterstr=search_filter)
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app.run(debug=True)
|
||||
@@ -0,0 +1,12 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
|
||||
class DedicatedResponseTest extends HttpServerHttpResponseTest {
|
||||
DedicatedResponseTest() { file.getShortName() = "response_test.py" }
|
||||
}
|
||||
|
||||
class OtherResponseTest extends HttpServerHttpResponseTest {
|
||||
OtherResponseTest() { not this instanceof DedicatedResponseTest }
|
||||
|
||||
override string getARelevantTag() { result = "HttpResponse" }
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
failures
|
||||
@@ -0,0 +1 @@
|
||||
import experimental.meta.InlineTaintTest
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
This file is a test of an extra data-flow step that we want to have for
|
||||
aiohttp.web.Application
|
||||
|
||||
We don't really have an established way to test extra data-flow steps in external
|
||||
libraries right now, so for now I've just used our normal taint-flow testing ¯\_(ツ)_/¯
|
||||
|
||||
see https://docs.aiohttp.org/en/stable/web_advanced.html#application-s-config
|
||||
"""
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
# to make code runable
|
||||
TAINTED_STRING = "TAINTED_STRING"
|
||||
def ensure_tainted(*args, **kwargs):
|
||||
pass
|
||||
|
||||
ensure_tainted(
|
||||
TAINTED_STRING # $ tainted
|
||||
)
|
||||
|
||||
|
||||
async def example(request: web.Request): # $ requestHandler
|
||||
return web.Response(text=f'example {request.app["foo"]=}') # $ HttpResponse
|
||||
|
||||
|
||||
async def also_works(request: web.Request): # $ requestHandler
|
||||
return web.Response(text=f'also_works {request.config_dict["foo"]=}') # $ HttpResponse
|
||||
|
||||
|
||||
async def taint_test(request: web.Request): # $ requestHandler
|
||||
ensure_tainted(
|
||||
request.app["ts"], # $ MISSING: tainted
|
||||
request.config_dict["ts"], # $ MISSING: tainted
|
||||
)
|
||||
return web.Response(text="ok") # $ HttpResponse
|
||||
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_get("", example) # $ routeSetup=""
|
||||
app.router.add_get("/also-works", also_works) # $ routeSetup="/also-works"
|
||||
app.router.add_get("/taint-test", taint_test) # $ routeSetup="/taint-test"
|
||||
app["foo"] = 42
|
||||
app["ts"] = TAINTED_STRING
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
web.run_app(app)
|
||||
1
python/ql/test/library-tests/frameworks/aiohttp/options
Normal file
1
python/ql/test/library-tests/frameworks/aiohttp/options
Normal file
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --max-import-depth=1 --lang=3
|
||||
@@ -0,0 +1,72 @@
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
|
||||
@routes.get("/raw_text") # $ routeSetup="/raw_text"
|
||||
async def raw_text(request): # $ requestHandler
|
||||
return web.Response(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
|
||||
|
||||
|
||||
@routes.get("/raw_body") # $ routeSetup="/raw_body"
|
||||
async def raw_body(request): # $ requestHandler
|
||||
return web.Response(body=b"foo") # $ HttpResponse mimetype=application/octet-stream responseBody=b"foo"
|
||||
|
||||
|
||||
@routes.get("/html_text") # $ routeSetup="/html_text"
|
||||
async def html_text(request): # $ requestHandler
|
||||
return web.Response(text="foo", content_type="text/html") # $ HttpResponse mimetype=text/html responseBody="foo"
|
||||
|
||||
|
||||
@routes.get("/html_body") # $ routeSetup="/html_body"
|
||||
async def html_body(request): # $ requestHandler
|
||||
return web.Response(body=b"foo", content_type="text/html") # $ HttpResponse mimetype=text/html responseBody=b"foo"
|
||||
|
||||
|
||||
@routes.get("/html_body_set_later") # $ routeSetup="/html_body_set_later"
|
||||
async def html_body_set_later(request): # $ requestHandler
|
||||
resp = web.Response(body=b"foo") # $ HttpResponse mimetype=application/octet-stream responseBody=b"foo"
|
||||
resp.content_type = "text/html" # $ MISSING: mimetype=text/html
|
||||
return resp
|
||||
|
||||
# Each HTTP status code has an exception
|
||||
# see https://docs.aiohttp.org/en/stable/web_quickstart.html#exceptions
|
||||
|
||||
@routes.get("/through_200_exception") # $ routeSetup="/through_200_exception"
|
||||
async def through_200_exception(request): # $ requestHandler
|
||||
raise web.HTTPOk(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
|
||||
|
||||
|
||||
@routes.get("/through_200_exception_html") # $ routeSetup="/through_200_exception_html"
|
||||
async def through_200_exception(request): # $ requestHandler
|
||||
exception = web.HTTPOk(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
|
||||
exception.content_type = "text/html" # $ MISSING: mimetype=text/html
|
||||
raise exception
|
||||
|
||||
|
||||
@routes.get("/through_404_exception") # $ routeSetup="/through_404_exception"
|
||||
async def through_404_exception(request): # $ requestHandler
|
||||
raise web.HTTPNotFound(text="foo") # $ HttpResponse mimetype=text/plain responseBody="foo"
|
||||
|
||||
|
||||
@routes.get("/redirect_301") # $ routeSetup="/redirect_301"
|
||||
async def redirect_301(request): # $ requestHandler
|
||||
if not "kwarg" in request.url.query:
|
||||
raise web.HTTPMovedPermanently("/login") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/login"
|
||||
else:
|
||||
raise web.HTTPMovedPermanently(location="/logout") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/logout"
|
||||
|
||||
|
||||
@routes.get("/redirect_302") # $ routeSetup="/redirect_302"
|
||||
async def redirect_302(request): # $ requestHandler
|
||||
if not "kwarg" in request.url.query:
|
||||
raise web.HTTPFound("/login") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/login"
|
||||
else:
|
||||
raise web.HTTPFound(location="/logout") # $ HttpResponse HttpRedirectResponse mimetype=application/octet-stream redirectLocation="/logout"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = web.Application()
|
||||
app.add_routes(routes)
|
||||
web.run_app(app)
|
||||
199
python/ql/test/library-tests/frameworks/aiohttp/routing_test.py
Normal file
199
python/ql/test/library-tests/frameworks/aiohttp/routing_test.py
Normal file
@@ -0,0 +1,199 @@
|
||||
# Inspired by https://docs.aiohttp.org/en/stable/web_quickstart.html
|
||||
# and https://docs.aiohttp.org/en/stable/web_quickstart.html#resources-and-routes
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
|
||||
app = web.Application()
|
||||
|
||||
|
||||
## ================================= ##
|
||||
## Ways to specify routes / handlers ##
|
||||
## ================================= ##
|
||||
|
||||
## Using coroutines
|
||||
if True:
|
||||
|
||||
# `app.add_routes` with list
|
||||
async def foo(request): # $ requestHandler
|
||||
return web.Response(text="foo") # $ HttpResponse
|
||||
|
||||
async def foo2(request): # $ requestHandler
|
||||
return web.Response(text="foo2") # $ HttpResponse
|
||||
|
||||
async def foo3(request): # $ requestHandler
|
||||
return web.Response(text="foo3") # $ HttpResponse
|
||||
|
||||
app.add_routes([
|
||||
web.get("/foo", foo), # $ routeSetup="/foo"
|
||||
web.route("*", "/foo2", foo2), # $ routeSetup="/foo2"
|
||||
web.get(path="/foo3", handler=foo3), # $ routeSetup="/foo3"
|
||||
])
|
||||
|
||||
|
||||
# using decorator
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
@routes.get("/bar") # $ routeSetup="/bar"
|
||||
async def bar(request): # $ requestHandler
|
||||
return web.Response(text="bar") # $ HttpResponse
|
||||
|
||||
@routes.route("*", "/bar2") # $ routeSetup="/bar2"
|
||||
async def bar2(request): # $ requestHandler
|
||||
return web.Response(text="bar2") # $ HttpResponse
|
||||
|
||||
@routes.get(path="/bar3") # $ routeSetup="/bar3"
|
||||
async def bar3(request): # $ requestHandler
|
||||
return web.Response(text="bar3") # $ HttpResponse
|
||||
|
||||
app.add_routes(routes)
|
||||
|
||||
|
||||
# `app.router.add_get` / `app.router.add_route`
|
||||
async def baz(request): # $ requestHandler
|
||||
return web.Response(text="baz") # $ HttpResponse
|
||||
|
||||
app.router.add_get("/baz", baz) # $ routeSetup="/baz"
|
||||
|
||||
async def baz2(request): # $ requestHandler
|
||||
return web.Response(text="baz2") # $ HttpResponse
|
||||
|
||||
app.router.add_route("*", "/baz2", baz2) # $ routeSetup="/baz2"
|
||||
|
||||
async def baz3(request): # $ requestHandler
|
||||
return web.Response(text="baz3") # $ HttpResponse
|
||||
|
||||
app.router.add_get(path="/baz3", handler=baz3) # $ routeSetup="/baz3"
|
||||
|
||||
|
||||
## Using classes / views
|
||||
if True:
|
||||
# see https://docs.aiohttp.org/en/stable/web_quickstart.html#organizing-handlers-in-classes
|
||||
|
||||
class MyCustomHandlerClass:
|
||||
|
||||
async def foo_handler(self, request): # $ MISSING: requestHandler
|
||||
return web.Response(text="MyCustomHandlerClass.foo") # $ HttpResponse
|
||||
|
||||
my_custom_handler = MyCustomHandlerClass()
|
||||
app.router.add_get("/MyCustomHandlerClass/foo", my_custom_handler.foo_handler) # $ routeSetup="/MyCustomHandlerClass/foo"
|
||||
|
||||
# Using `web.View`
|
||||
# ---------------
|
||||
|
||||
# `app.add_routes` with list
|
||||
class MyWebView1(web.View):
|
||||
async def get(self): # $ requestHandler
|
||||
return web.Response(text="MyWebView1.get") # $ HttpResponse
|
||||
|
||||
app.add_routes([
|
||||
web.view("/MyWebView1", MyWebView1) # $ routeSetup="/MyWebView1"
|
||||
])
|
||||
|
||||
|
||||
# using decorator
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
@routes.view("/MyWebView2") # $ routeSetup="/MyWebView2"
|
||||
class MyWebView2(web.View):
|
||||
async def get(self): # $ requestHandler
|
||||
return web.Response(text="MyWebView2.get") # $ HttpResponse
|
||||
|
||||
app.add_routes(routes)
|
||||
|
||||
|
||||
# `app.router.add_view`
|
||||
class MyWebView3(web.View):
|
||||
async def get(self): # $ requestHandler
|
||||
return web.Response(text="MyWebView3.get") # $ HttpResponse
|
||||
|
||||
app.router.add_view("/MyWebView3", MyWebView3) # $ routeSetup="/MyWebView3"
|
||||
|
||||
# no route-setup
|
||||
class MyWebViewNoRoute(web.View):
|
||||
async def get(self): # $ requestHandler
|
||||
return web.Response(text="MyWebViewNoRoute.get") # $ HttpResponse
|
||||
|
||||
if len(__name__) < 0: # avoid running, but fool analysis to not consider dead code
|
||||
# no explicit-view subclass (but route-setup)
|
||||
class MyWebViewNoSubclassButRoute(somelib.someclass):
|
||||
async def get(self): # $ requestHandler
|
||||
return web.Response(text="MyWebViewNoSubclassButRoute.get") # $ HttpResponse
|
||||
|
||||
app.router.add_view("/MyWebViewNoSubclassButRoute", MyWebViewNoSubclassButRoute) # $ routeSetup="/MyWebViewNoSubclassButRoute"
|
||||
|
||||
|
||||
# Apparently there is no enforcement that `add_view` is only for views, and vice-versa
|
||||
# for `add_get` only being for async functions.
|
||||
if True:
|
||||
async def no_rules(request): # $ requestHandler
|
||||
return web.Response(text="no_rules") # $ HttpResponse
|
||||
|
||||
app.router.add_view("/no_rules", no_rules) # $ routeSetup="/no_rules"
|
||||
|
||||
|
||||
class NoRulesView(web.View):
|
||||
async def get(self): # $ requestHandler
|
||||
return web.Response(text="NoRulesView.get") # $ HttpResponse
|
||||
|
||||
app.router.add_get("/NoRulesView", NoRulesView) # $ routeSetup="/NoRulesView"
|
||||
|
||||
|
||||
## =================== ##
|
||||
## "Routed parameters" ##
|
||||
## =================== ##
|
||||
|
||||
if True:
|
||||
# see https://docs.aiohttp.org/en/stable/web_quickstart.html#variable-resources
|
||||
|
||||
async def matching(request: web.Request): # $ requestHandler
|
||||
name = request.match_info['name']
|
||||
number = request.match_info['number']
|
||||
return web.Response(text="matching name={} number={}".format(name, number)) # $ HttpResponse
|
||||
|
||||
app.router.add_get(r"/matching/{name}/{number:\d+}", matching) # $ routeSetup="/matching/{name}/{number:\d+}"
|
||||
|
||||
## ======= ##
|
||||
## subapps ##
|
||||
## ======= ##
|
||||
|
||||
if True:
|
||||
subapp = web.Application()
|
||||
|
||||
async def subapp_handler(request): # $ requestHandler
|
||||
return web.Response(text="subapp_handler") # $ HttpResponse
|
||||
|
||||
subapp.router.add_get("/subapp_handler", subapp_handler) # $ routeSetup="/subapp_handler"
|
||||
|
||||
app.add_subapp("/my_subapp", subapp)
|
||||
|
||||
# similar behavior is possible with `app.add_domain`, but since I don't think we'll have special handling
|
||||
# for any kind of subapps, I have not created a test for this.
|
||||
|
||||
|
||||
## ================================ ##
|
||||
## Constructing UrlDispatcher first ##
|
||||
## ================================ ##
|
||||
|
||||
if True:
|
||||
async def manual_dispatcher_instance(request): # $ requestHandler
|
||||
return web.Response(text="manual_dispatcher_instance") # $ HttpResponse
|
||||
|
||||
url_dispatcher = web.UrlDispatcher()
|
||||
url_dispatcher.add_get("/manual_dispatcher_instance", manual_dispatcher_instance) # $ routeSetup="/manual_dispatcher_instance"
|
||||
|
||||
subapp2 = web.Application(router=url_dispatcher)
|
||||
app.add_subapp("/manual_dispatcher_instance_app", subapp2)
|
||||
|
||||
|
||||
## =========== ##
|
||||
## Run the app ##
|
||||
## =========== ##
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("For auto-reloading server you can use:")
|
||||
print(f"aiohttp-devtools runserver {__file__}")
|
||||
print("after doing `pip install aiohttp-devtools`")
|
||||
print()
|
||||
|
||||
web.run_app(app)
|
||||
152
python/ql/test/library-tests/frameworks/aiohttp/taint_test.py
Normal file
152
python/ql/test/library-tests/frameworks/aiohttp/taint_test.py
Normal file
@@ -0,0 +1,152 @@
|
||||
from aiohttp import web
|
||||
|
||||
async def test_taint(request: web.Request): # $ requestHandler
|
||||
|
||||
ensure_tainted(
|
||||
# see https://docs.aiohttp.org/en/stable/web_reference.html#request-and-base-request
|
||||
request, # $ tainted
|
||||
|
||||
# yarl.URL (see `yarl` framework tests)
|
||||
request.url, # $ tainted
|
||||
request.url.human_repr(), # $ tainted
|
||||
request.rel_url, # $ tainted
|
||||
request.rel_url.human_repr(), # $ tainted
|
||||
|
||||
request.forwarded, # $ tainted
|
||||
|
||||
request.host, # $ tainted
|
||||
request.remote, # $ tainted
|
||||
request.path, # $ tainted
|
||||
request.path_qs, # $ tainted
|
||||
request.raw_path, # $ tainted
|
||||
|
||||
# dict-like for captured parts of the URL
|
||||
request.match_info, # $ tainted
|
||||
request.match_info["key"], # $ tainted
|
||||
request.match_info.get("key"), # $ tainted
|
||||
|
||||
# multidict.MultiDictProxy[str] (see `multidict` framework tests)
|
||||
request.query, # $ tainted
|
||||
request.query.getone("key"), # $ tainted
|
||||
|
||||
# multidict.CIMultiDictProxy[str] (see `multidict` framework tests)
|
||||
request.headers, # $ tainted
|
||||
request.headers.getone("key"), # $ tainted
|
||||
|
||||
|
||||
|
||||
# dict-like (readonly)
|
||||
request.cookies, # $ tainted
|
||||
request.cookies["key"], # $ tainted
|
||||
request.cookies.get("key"), # $ tainted
|
||||
request.cookies.keys(), # $ MISSING: tainted
|
||||
request.cookies.values(), # $ tainted
|
||||
request.cookies.items(), # $ tainted
|
||||
list(request.cookies), # $ tainted
|
||||
iter(request.cookies), # $ tainted
|
||||
|
||||
|
||||
# aiohttp.StreamReader
|
||||
# see https://docs.aiohttp.org/en/stable/streams.html#aiohttp.StreamReader
|
||||
request.content, # $ tainted
|
||||
await request.content.read(), # $ tainted
|
||||
await request.content.readany(), # $ tainted
|
||||
await request.content.readexactly(42), # $ tainted
|
||||
await request.content.readline(), # $ tainted
|
||||
await request.content.readchunk(), # $ tainted
|
||||
(await request.content.readchunk())[0], # $ tainted
|
||||
[line async for line in request.content], # $ MISSING: tainted
|
||||
[data async for data in request.content.iter_chunked(1024)], # $ MISSING: tainted
|
||||
[data async for data in request.content.iter_any()], # $ MISSING: tainted
|
||||
[data async for data, _ in request.content.iter_chunks()], # $ MISSING: tainted
|
||||
request.content.read_nowait(), # $ tainted
|
||||
|
||||
# aiohttp.StreamReader
|
||||
request._payload, # $ tainted
|
||||
await request._payload.readany(), # $ tainted
|
||||
|
||||
request.content_type, # $ tainted
|
||||
request.charset, # $ tainted
|
||||
|
||||
request.http_range, # $ tainted
|
||||
|
||||
# Optional[datetime]
|
||||
request.if_modified_since, # $ tainted
|
||||
request.if_unmodified_since, # $ tainted
|
||||
request.if_range, # $ tainted
|
||||
|
||||
request.clone(scheme="https"), # $ tainted
|
||||
|
||||
# asyncio.Transport
|
||||
# https://docs.python.org/3/library/asyncio-protocol.html#asyncio-transport
|
||||
# example given in https://docs.aiohttp.org/en/stable/web_reference.html#aiohttp.web.BaseRequest.transport
|
||||
# uses `peername` to get IP address of client
|
||||
request.transport, # $ tainted
|
||||
request.transport.get_extra_info("key"), # $ MISSING: tainted
|
||||
|
||||
# Like request.transport.get_extra_info
|
||||
request.get_extra_info("key"), # $ tainted
|
||||
|
||||
# Like request.transport.get_extra_info
|
||||
request.protocol.transport.get_extra_info("key"), # $ MISSING: tainted
|
||||
|
||||
# bytes
|
||||
await request.read(), # $ tainted
|
||||
|
||||
# str
|
||||
await request.text(), # $ tainted
|
||||
|
||||
# obj
|
||||
await request.json(), # $ tainted
|
||||
|
||||
# aiohttp.multipart.MultipartReader
|
||||
await request.multipart(), # $ tainted
|
||||
|
||||
# multidict.MultiDictProxy[str] (see `multidict` framework tests)
|
||||
await request.post(), # $ tainted
|
||||
(await request.post()).getone("key"), # $ tainted
|
||||
)
|
||||
|
||||
import yarl
|
||||
assert isinstance(request.url, yarl.URL)
|
||||
assert isinstance(request.rel_url, yarl.URL)
|
||||
|
||||
|
||||
# things that are technically controlled by sender of request,
|
||||
# but doesn't seem that likely for exploitation.
|
||||
ensure_not_tainted(
|
||||
request.method,
|
||||
request.version,
|
||||
request.scheme,
|
||||
request.secure,
|
||||
request.keep_alive,
|
||||
|
||||
request.content_length,
|
||||
request.body_exists,
|
||||
request.has_body,
|
||||
request.can_read_body,
|
||||
)
|
||||
|
||||
ensure_not_tainted(
|
||||
request.loop,
|
||||
|
||||
request.app,
|
||||
request.config_dict,
|
||||
)
|
||||
|
||||
|
||||
class TaintTestClass(web.View):
|
||||
def get(self): # $ requestHandler
|
||||
ensure_tainted(
|
||||
self.request, # $ tainted
|
||||
self.request.url # $ tainted
|
||||
)
|
||||
|
||||
|
||||
app = web.Application()
|
||||
app.router.add_get(r"/test_taint/{name}/{number:\d+}", test_taint) # $ routeSetup="/test_taint/{name}/{number:\d+}"
|
||||
app.router.add_view(r"/test_taint_class", TaintTestClass) # $ routeSetup="/test_taint_class"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
web.run_app(app)
|
||||
@@ -0,0 +1,5 @@
|
||||
| ec_keygen_origin.py:8:1:8:45 | ControlFlowNode for Attribute() | 384 | ec_keygen_origin.py:8:31:8:42 | ControlFlowNode for Attribute |
|
||||
| ec_keygen_origin.py:9:1:9:43 | ControlFlowNode for Attribute() | 384 | ec_keygen_origin.py:9:31:9:42 | ControlFlowNode for Attribute |
|
||||
| ec_keygen_origin.py:12:1:12:36 | ControlFlowNode for Attribute() | 384 | ec_keygen_origin.py:11:9:11:20 | ControlFlowNode for Attribute |
|
||||
| ec_keygen_origin.py:15:1:15:39 | ControlFlowNode for Attribute() | 384 | ec_keygen_origin.py:11:9:11:20 | ControlFlowNode for Attribute |
|
||||
| ec_keygen_origin.py:20:1:20:32 | ControlFlowNode for Attribute() | 384 | ec_keygen_origin.py:6:58:6:66 | ControlFlowNode for ImportMember |
|
||||
@@ -0,0 +1,8 @@
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import semmle.python.Concepts
|
||||
|
||||
from Cryptography::PublicKey::KeyGeneration keyGen, int keySize, DataFlow::Node origin
|
||||
where
|
||||
keyGen.getLocation().getFile().getShortName() = "ec_keygen_origin.py" and
|
||||
keySize = keyGen.getKeySizeWithOrigin(origin)
|
||||
select keyGen, keySize, origin
|
||||
@@ -0,0 +1,20 @@
|
||||
# Since key-size is not specified explicitly as an integer for the predefined
|
||||
# classes in the `cryptography.hazmat.primitives.asymmetric.ec` module, we need
|
||||
# special handling of the origin... this test is simply to show off how we handle this.
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.asymmetric.ec import SECP384R1
|
||||
|
||||
ec.generate_private_key(curve=ec.SECP384R1()) # $ PublicKeyGeneration keySize=384
|
||||
ec.generate_private_key(curve=ec.SECP384R1) # $ PublicKeyGeneration keySize=384
|
||||
|
||||
alias = ec.SECP384R1
|
||||
ec.generate_private_key(curve=alias) # $ PublicKeyGeneration keySize=384
|
||||
|
||||
instance = alias()
|
||||
ec.generate_private_key(curve=instance) # $ PublicKeyGeneration keySize=384
|
||||
|
||||
|
||||
x = SECP384R1
|
||||
y = x
|
||||
ec.generate_private_key(curve=y) # $ PublicKeyGeneration keySize=384
|
||||
@@ -6,6 +6,7 @@ from cryptography.exceptions import InvalidSignature
|
||||
|
||||
|
||||
private_key = ec.generate_private_key(curve=ec.SECP384R1()) # $ PublicKeyGeneration keySize=384
|
||||
private_key = ec.generate_private_key(curve=ec.SECP384R1) # $ PublicKeyGeneration keySize=384
|
||||
public_key = private_key.public_key()
|
||||
|
||||
HASH_ALGORITHM = hashes.SHA256()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import dill
|
||||
|
||||
dill.loads(payload) # $decodeInput=payload decodeOutput=Attribute() decodeFormat=dill decodeMayExecuteInput
|
||||
dill.loads(payload) # $decodeInput=payload decodeOutput=dill.loads(..) decodeFormat=dill decodeMayExecuteInput
|
||||
|
||||
@@ -74,20 +74,20 @@ class CustomRedirectView(RedirectView):
|
||||
|
||||
# Ensure that simple subclasses are still vuln to XSS
|
||||
def xss__not_found(request):
|
||||
return HttpResponseNotFound(request.GET.get("name")) # $HttpResponse mimetype=text/html responseBody=Attribute()
|
||||
return HttpResponseNotFound(request.GET.get("name")) # $HttpResponse mimetype=text/html responseBody=request.GET.get(..)
|
||||
|
||||
# Ensure we still have an XSS sink when manually setting the content_type to HTML
|
||||
def xss__manual_response_type(request):
|
||||
return HttpResponse(request.GET.get("name"), content_type="text/html; charset=utf-8") # $HttpResponse mimetype=text/html responseBody=Attribute()
|
||||
return HttpResponse(request.GET.get("name"), content_type="text/html; charset=utf-8") # $HttpResponse mimetype=text/html responseBody=request.GET.get(..)
|
||||
|
||||
def xss__write(request):
|
||||
response = HttpResponse() # $HttpResponse mimetype=text/html
|
||||
response.write(request.GET.get("name")) # $HttpResponse mimetype=text/html responseBody=Attribute()
|
||||
response.write(request.GET.get("name")) # $HttpResponse mimetype=text/html responseBody=request.GET.get(..)
|
||||
|
||||
# This is safe but probably a bug if the argument to `write` is not a result of `json.dumps` or similar.
|
||||
def safe__write_json(request):
|
||||
response = JsonResponse() # $HttpResponse mimetype=application/json
|
||||
response.write(request.GET.get("name")) # $HttpResponse mimetype=application/json responseBody=Attribute()
|
||||
response.write(request.GET.get("name")) # $HttpResponse mimetype=application/json responseBody=request.GET.get(..)
|
||||
|
||||
# Ensure manual subclasses are vulnerable
|
||||
class CustomResponse(HttpResponse):
|
||||
@@ -95,7 +95,7 @@ class CustomResponse(HttpResponse):
|
||||
super().__init__(content, *args, content_type="text/html", **kwargs)
|
||||
|
||||
def xss__custom_response(request):
|
||||
return CustomResponse("ACME Responses", request.GET("name")) # $HttpResponse MISSING: mimetype=text/html responseBody=Attribute() SPURIOUS: responseBody="ACME Responses"
|
||||
return CustomResponse("ACME Responses", request.GET("name")) # $HttpResponse MISSING: mimetype=text/html responseBody=request.GET.get(..) SPURIOUS: responseBody="ACME Responses"
|
||||
|
||||
class CustomJsonResponse(JsonResponse):
|
||||
def __init__(self, banner, content, *args, **kwargs):
|
||||
|
||||
@@ -13,7 +13,7 @@ https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent #$ getAPathArgument=Path()
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent #$ getAPathArgument=Path(..)
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
|
||||
@@ -5,9 +5,9 @@ def test_idna():
|
||||
tb = TAINTED_BYTES
|
||||
|
||||
ensure_tainted(
|
||||
idna.encode(ts), # $ tainted encodeInput=ts encodeOutput=Attribute() encodeFormat=IDNA
|
||||
idna.encode(s=ts), # $ tainted encodeInput=ts encodeOutput=Attribute() encodeFormat=IDNA
|
||||
idna.encode(ts), # $ tainted encodeInput=ts encodeOutput=idna.encode(..) encodeFormat=IDNA
|
||||
idna.encode(s=ts), # $ tainted encodeInput=ts encodeOutput=idna.encode(..) encodeFormat=IDNA
|
||||
|
||||
idna.decode(tb), # $ tainted decodeInput=tb decodeOutput=Attribute() decodeFormat=IDNA
|
||||
idna.decode(s=tb), # $ tainted decodeInput=tb decodeOutput=Attribute() decodeFormat=IDNA
|
||||
idna.decode(tb), # $ tainted decodeInput=tb decodeOutput=idna.decode(..) decodeFormat=IDNA
|
||||
idna.decode(s=tb), # $ tainted decodeInput=tb decodeOutput=idna.decode(..) decodeFormat=IDNA
|
||||
)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import python
|
||||
import experimental.meta.ConceptsTest
|
||||
@@ -0,0 +1,3 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
failures
|
||||
@@ -0,0 +1 @@
|
||||
import experimental.meta.InlineTaintTest
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user