Merge branch 'main' into promote-clickhouse

This commit is contained in:
Rasmus Wriedt Larsen
2021-06-15 11:30:19 +02:00
903 changed files with 24781 additions and 8649 deletions

View File

@@ -0,0 +1,2 @@
lgtm,codescanning
* Added modeling of sources/sinks when using the `aiohttp.web` web framework.

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -5,6 +5,7 @@
* @kind path-problem
* @precision low
* @problem.severity error
* @security-severity 5.9
* @tags security external/cwe/cwe-20
*/

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -5,6 +5,7 @@
* @tags security
* correctness
* @problem.severity error
* @security-severity 4.2
* @sub-severity high
* @precision low
* @id py/use-of-exec

View File

@@ -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
*/

View File

@@ -7,6 +7,7 @@
* be counted as user written code.
* @kind metric
* @tags summary
* lines-of-code
* @id py/summary/lines-of-user-code
*/

View File

@@ -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>

View 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"

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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() }
}

View File

@@ -3,3 +3,4 @@
*/
private import experimental.semmle.python.frameworks.Stdlib
private import experimental.semmle.python.frameworks.LDAP

View 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) }
}
}
}

View File

@@ -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()
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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")]
}
}
}

View File

@@ -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.

View File

@@ -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

View 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
)
}
}
}
}

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View 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"
}
}
}
}

View File

@@ -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)
}

View File

@@ -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) }
}

View File

@@ -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'. |

View File

@@ -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())

View File

@@ -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()
}

View File

@@ -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)
)
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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 |

View File

@@ -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 |

View File

@@ -0,0 +1 @@
experimental/Security/CWE-090/LDAPInjection.ql

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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" }
}

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

View File

@@ -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)

View File

@@ -0,0 +1 @@
semmle-extractor-options: --max-import-depth=1 --lang=3

View File

@@ -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)

View 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)

View 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)

View File

@@ -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 |

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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
)

View File

@@ -0,0 +1,2 @@
import python
import experimental.meta.ConceptsTest

View File

@@ -0,0 +1,3 @@
argumentToEnsureNotTaintedNotMarkedAsSpurious
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
failures

View File

@@ -0,0 +1 @@
import experimental.meta.InlineTaintTest

Some files were not shown because too many files have changed in this diff Show More