mirror of
https://github.com/github/codeql.git
synced 2026-04-29 02:35:15 +02:00
Merge branch 'github:main' into jorgectf/python/ldapimproperauth
This commit is contained in:
@@ -31,7 +31,7 @@ updated to use a context manager.</p>
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Effbot: <a href="http://effbot.org/zone/python-with-statement.htm">Python with statement</a>.</li>
|
||||
<li>Effbot: <a href="https://web.archive.org/web/20201012110738/http://effbot.org/zone/python-with-statement.htm">Python with statement</a>.</li>
|
||||
<li>Python Standard Library: <a href="http://docs.python.org/library/stdtypes.html#context-manager-types">Context manager
|
||||
</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2.7/reference/datamodel.html#with-statement-context-managers">
|
||||
|
||||
23
python/ql/src/Diagnostics/ExtractionErrors.ql
Normal file
23
python/ql/src/Diagnostics/ExtractionErrors.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Python extraction errors
|
||||
* @description List all extraction errors for Python files in the source code directory.
|
||||
* @kind diagnostic
|
||||
* @id py/diagnostics/extraction-errors
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
/**
|
||||
* Gets the SARIF severity for errors.
|
||||
*
|
||||
* See point 3.27.10 in https://docs.oasis-open.org/sarif/sarif/v2.0/sarif-v2.0.html for
|
||||
* what error means.
|
||||
*/
|
||||
int getErrorSeverity() { result = 2 }
|
||||
|
||||
from SyntaxError error, File file
|
||||
where
|
||||
file = error.getFile() and
|
||||
exists(file.getRelativePath())
|
||||
select error, "Extraction failed in " + file + " with error " + error.getMessage(),
|
||||
getErrorSeverity()
|
||||
15
python/ql/src/Diagnostics/SuccessfullyExtractedFiles.ql
Normal file
15
python/ql/src/Diagnostics/SuccessfullyExtractedFiles.ql
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @name Successfully extracted Python files
|
||||
* @description Lists all Python files in the source code directory that were extracted
|
||||
* without encountering an error.
|
||||
* @kind diagnostic
|
||||
* @id py/diagnostics/successfully-extracted-files
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
from File file
|
||||
where
|
||||
not exists(SyntaxError e | e.getFile() = file) and
|
||||
exists(file.getRelativePath())
|
||||
select file, ""
|
||||
@@ -36,7 +36,7 @@ function with a default of <code>default=None</code>, check if the parameter is
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>Effbot: <a href="http://effbot.org/zone/default-values.htm">Default Parameter Values in Python</a>.</li>
|
||||
<li>Effbot: <a href="https://web.archive.org/web/20201112004749/http://effbot.org/zone/default-values.htm">Default Parameter Values in Python</a>.</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/compound_stmts.html#function-definitions">Function definitions</a>.</li>
|
||||
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import that.
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/simple_stmts.html#import">The import statement</a>.</li>
|
||||
<li>Python: <a href="http://docs.python.org/2/tutorial/modules.html">Modules</a>.</li>
|
||||
<li> Effbot: <a href="http://effbot.org/zone/import-confusion.htm">Import Confusion</a>.</li>
|
||||
<li> Effbot: <a href="https://web.archive.org/web/20200917011425/https://effbot.org/zone/import-confusion.htm">Import Confusion</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
|
||||
@@ -33,7 +33,7 @@ import that.
|
||||
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/2/reference/simple_stmts.html#import">The import statement</a>.</li>
|
||||
<li>Python: <a href="http://docs.python.org/2/tutorial/modules.html">Modules</a>.</li>
|
||||
<li> Effbot: <a href="http://effbot.org/zone/import-confusion.htm">Import Confusion</a>.</li>
|
||||
<li> Effbot: <a href="https://web.archive.org/web/20200917011425/https://effbot.org/zone/import-confusion.htm">Import Confusion</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
|
||||
@@ -49,7 +49,7 @@ so the general technique is quite widely applicable.
|
||||
|
||||
|
||||
<li>
|
||||
IBM developerWorks: <a href="http://www.ibm.com/developerworks/library/j-eaed6/">Evolutionary architecture and emergent design: Emergent design through metrics</a>.
|
||||
IBM developerWorks: <a href="https://web.archive.org/web/20190919085934/https://www.ibm.com/developerworks/library/j-eaed6/">Evolutionary architecture and emergent design: Emergent design through metrics</a>.
|
||||
</li>
|
||||
<li>
|
||||
R. Martin, <em>Agile Software Development: Principles, Patterns and Practices</em>. Pearson, 2011.
|
||||
|
||||
@@ -29,7 +29,7 @@ You can reduce efferent coupling by splitting up a module so that each part depe
|
||||
|
||||
|
||||
<li>
|
||||
IBM developerWorks: <a href="http://www.ibm.com/developerworks/library/j-eaed6/">Evolutionary architecture and emergent design: Emergent design through metrics</a>.
|
||||
IBM developerWorks: <a href="https://web.archive.org/web/20190919085934/https://www.ibm.com/developerworks/library/j-eaed6/">Evolutionary architecture and emergent design: Emergent design through metrics</a>.
|
||||
</li>
|
||||
<li>
|
||||
R. Martin, <em>Agile Software Development: Principles, Patterns and Practices</em>. Pearson, 2011.
|
||||
|
||||
@@ -32,21 +32,7 @@ private DataFlow::LocalSourceNode vulnerableHostnameRef(DataFlow::TypeTracker t,
|
||||
result.asExpr() = allInterfacesStrConst
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `vulnerableHostnameRef(t2, hostname).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
vulnerableHostnameRef_first_join(t2, hostname, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate vulnerableHostnameRef_first_join(
|
||||
DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(vulnerableHostnameRef(t2, hostname), res, summary)
|
||||
exists(DataFlow::TypeTracker t2 | result = vulnerableHostnameRef(t2, hostname).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a hostname that can be used to bind to all interfaces. */
|
||||
@@ -59,21 +45,7 @@ private DataFlow::LocalSourceNode vulnerableAddressTuple(DataFlow::TypeTracker t
|
||||
t.start() and
|
||||
result.asExpr() = any(Tuple tup | tup.getElt(0) = vulnerableHostnameRef(hostname).asExpr())
|
||||
or
|
||||
// Due to bad performance when using normal setup with `vulnerableAddressTuple(t2, hostname).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
vulnerableAddressTuple_first_join(t2, hostname, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate vulnerableAddressTuple_first_join(
|
||||
DataFlow::TypeTracker t2, string hostname, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(vulnerableAddressTuple(t2, hostname), res, summary)
|
||||
exists(DataFlow::TypeTracker t2 | result = vulnerableAddressTuple(t2, hostname).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to a tuple for which the first element is a hostname that can be used to bind to all interfaces. */
|
||||
|
||||
103
python/ql/src/Security/CWE-327/FluentApiModel.qll
Normal file
103
python/ql/src/Security/CWE-327/FluentApiModel.qll
Normal file
@@ -0,0 +1,103 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
import TlsLibraryModel
|
||||
|
||||
/**
|
||||
* Configuration to determine the state of a context being used to create
|
||||
* a connection. There is one configuration for each pair of `TlsLibrary` and `ProtocolVersion`,
|
||||
* such that a single configuration only tracks contexts where a specific `ProtocolVersion` is allowed.
|
||||
*
|
||||
* The state is in terms of whether a specific protocol is allowed. This is
|
||||
* either true or false when the context is created and can then be modified
|
||||
* later by either restricting or unrestricting the protocol (see the predicates
|
||||
* `isRestriction` and `isUnrestriction`).
|
||||
*
|
||||
* Since we are interested in the final state, we want the flow to start from
|
||||
* the last unrestriction, so we disallow flow into unrestrictions. We also
|
||||
* model the creation as an unrestriction of everything it allows, to account
|
||||
* for the common case where the creation plays the role of "last unrestriction".
|
||||
*
|
||||
* Since we really want "the last unrestriction, not nullified by a restriction",
|
||||
* we also disallow flow into restrictions.
|
||||
*/
|
||||
class InsecureContextConfiguration extends DataFlow::Configuration {
|
||||
TlsLibrary library;
|
||||
ProtocolVersion tracked_version;
|
||||
|
||||
InsecureContextConfiguration() {
|
||||
this = library + "Allows" + tracked_version and
|
||||
tracked_version.isInsecure()
|
||||
}
|
||||
|
||||
ProtocolVersion getTrackedVersion() { result = tracked_version }
|
||||
|
||||
override predicate isSource(DataFlow::Node source) { this.isUnrestriction(source) }
|
||||
|
||||
override predicate isSink(DataFlow::Node sink) {
|
||||
sink = library.connection_creation().getContext()
|
||||
}
|
||||
|
||||
override predicate isBarrierIn(DataFlow::Node node) {
|
||||
this.isRestriction(node)
|
||||
or
|
||||
this.isUnrestriction(node)
|
||||
}
|
||||
|
||||
private predicate isRestriction(DataFlow::Node node) {
|
||||
exists(ProtocolRestriction r |
|
||||
r = library.protocol_restriction() and
|
||||
r.getRestriction() = tracked_version
|
||||
|
|
||||
node = r.getContext()
|
||||
)
|
||||
}
|
||||
|
||||
private predicate isUnrestriction(DataFlow::Node node) {
|
||||
exists(ProtocolUnrestriction pu |
|
||||
pu = library.protocol_unrestriction() and
|
||||
pu.getUnrestriction() = tracked_version
|
||||
|
|
||||
node = pu.getContext()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `conectionCreation` marks the creation of a connetion based on the contex
|
||||
* found at `contextOrigin` and allowing `insecure_version`.
|
||||
*
|
||||
* `specific` is true iff the context is configured for a specific protocol version (`ssl.PROTOCOL_TLSv1_2`) rather
|
||||
* than for a family of protocols (`ssl.PROTOCOL_TLS`).
|
||||
*/
|
||||
predicate unsafe_connection_creation_with_context(
|
||||
DataFlow::Node connectionCreation, ProtocolVersion insecure_version, DataFlow::Node contextOrigin,
|
||||
boolean specific
|
||||
) {
|
||||
// Connection created from a context allowing `insecure_version`.
|
||||
exists(InsecureContextConfiguration c | c.hasFlow(contextOrigin, connectionCreation) |
|
||||
insecure_version = c.getTrackedVersion() and
|
||||
specific = false
|
||||
)
|
||||
or
|
||||
// Connection created from a context specifying `insecure_version`.
|
||||
exists(TlsLibrary l |
|
||||
connectionCreation = l.insecure_connection_creation(insecure_version) and
|
||||
contextOrigin = connectionCreation and
|
||||
specific = true
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `conectionCreation` marks the creation of a connetion witout reference to a context
|
||||
* and allowing `insecure_version`.
|
||||
*/
|
||||
predicate unsafe_connection_creation_without_context(
|
||||
DataFlow::CallCfgNode connectionCreation, string insecure_version
|
||||
) {
|
||||
exists(TlsLibrary l | connectionCreation = l.insecure_connection_creation(insecure_version))
|
||||
}
|
||||
|
||||
/** Holds if `contextCreation` is creating a context tied to a specific insecure version. */
|
||||
predicate unsafe_context_creation(DataFlow::CallCfgNode contextCreation, string insecure_version) {
|
||||
exists(TlsLibrary l | contextCreation = l.insecure_context_creation(insecure_version))
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
<p>
|
||||
Ensure that a modern, strong protocol is used. All versions of SSL,
|
||||
and TLS 1.0 are known to be vulnerable to attacks. Using TLS 1.1 or
|
||||
above is strongly recommended.
|
||||
and TLS versions 1.0 and 1.1 are known to be vulnerable to attacks.
|
||||
Using TLS 1.2 or above is strongly recommended.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
@@ -30,20 +30,35 @@
|
||||
|
||||
<p>
|
||||
All cases should be updated to use a secure protocol, such as
|
||||
<code>PROTOCOL_TLSv1_1</code>.
|
||||
<code>PROTOCOL_TLSv1_2</code>.
|
||||
</p>
|
||||
<p>
|
||||
Note that <code>ssl.wrap_socket</code> has been deprecated in
|
||||
Python 3.7. A preferred alternative is to use
|
||||
<code>ssl.SSLContext</code>, which is supported in Python 2.7.9 and
|
||||
3.2 and later versions.
|
||||
Python 3.7. The recommended alternatives are:
|
||||
</p>
|
||||
<ul>
|
||||
<li><code>ssl.SSLContext</code> - supported in Python 2.7.9,
|
||||
3.2, and later versions</li>
|
||||
<li><code>ssl.create_default_context</code> - a convenience function,
|
||||
supported in Python 3.4 and later versions.</li>
|
||||
</ul>
|
||||
<p>
|
||||
Even when you use these alternatives, you should
|
||||
ensure that a safe protocol is used. The following code illustrates
|
||||
how to use flags (available since Python 3.2) or the `minimum_version`
|
||||
field (favored since Python 3.7) to restrict the protocols accepted when
|
||||
creating a connection.
|
||||
</p>
|
||||
|
||||
<sample src="examples/secure_default_protocol.py" />
|
||||
</example>
|
||||
|
||||
<references>
|
||||
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security"> Transport Layer Security</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.SSLContext"> class ssl.SSLContext</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl.wrap_socket"> ssl.wrap_socket</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#functions-constants-and-exceptions"> notes on context creation</a>.</li>
|
||||
<li>Python 3 documentation: <a href="https://docs.python.org/3/library/ssl.html#ssl-security"> notes on security considerations</a>.</li>
|
||||
<li>pyOpenSSL documentation: <a href="https://pyopenssl.org/en/stable/api/ssl.html"> An interface to the SSL-specific parts of OpenSSL</a>.</li>
|
||||
</references>
|
||||
|
||||
|
||||
@@ -10,86 +10,77 @@
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.dataflow.new.DataFlow
|
||||
import FluentApiModel
|
||||
|
||||
private ModuleValue the_ssl_module() { result = Module::named("ssl") }
|
||||
|
||||
FunctionValue ssl_wrap_socket() { result = the_ssl_module().attr("wrap_socket") }
|
||||
|
||||
ClassValue ssl_Context_class() { result = the_ssl_module().attr("SSLContext") }
|
||||
|
||||
private ModuleValue the_pyOpenSSL_module() { result = Value::named("pyOpenSSL.SSL") }
|
||||
|
||||
ClassValue the_pyOpenSSL_Context_class() { result = Value::named("pyOpenSSL.SSL.Context") }
|
||||
|
||||
string insecure_version_name() {
|
||||
// For `pyOpenSSL.SSL`
|
||||
result = "SSLv2_METHOD" or
|
||||
result = "SSLv23_METHOD" or
|
||||
result = "SSLv3_METHOD" or
|
||||
result = "TLSv1_METHOD" or
|
||||
// For the `ssl` module
|
||||
result = "PROTOCOL_SSLv2" or
|
||||
result = "PROTOCOL_SSLv3" or
|
||||
result = "PROTOCOL_SSLv23" or
|
||||
result = "PROTOCOL_TLS" or
|
||||
result = "PROTOCOL_TLSv1"
|
||||
}
|
||||
|
||||
/*
|
||||
* A syntactic check for cases where points-to analysis cannot infer the presence of
|
||||
* a protocol constant, e.g. if it has been removed in later versions of the `ssl`
|
||||
* library.
|
||||
*/
|
||||
|
||||
bindingset[named_argument]
|
||||
predicate probable_insecure_ssl_constant(
|
||||
CallNode call, string insecure_version, string named_argument
|
||||
) {
|
||||
exists(ControlFlowNode arg |
|
||||
arg = call.getArgByName(named_argument) or
|
||||
arg = call.getArg(0)
|
||||
|
|
||||
arg.(AttrNode).getObject(insecure_version).pointsTo(the_ssl_module())
|
||||
// Helper for pretty printer `configName`.
|
||||
// This is a consequence of missing pretty priting.
|
||||
// We do not want to evaluate our bespoke pretty printer
|
||||
// for all `DataFlow::Node`s so we define a sub class of interesting ones.
|
||||
class ProtocolConfiguration extends DataFlow::Node {
|
||||
ProtocolConfiguration() {
|
||||
unsafe_connection_creation_with_context(_, _, this, _)
|
||||
or
|
||||
arg.(NameNode).getId() = insecure_version and
|
||||
exists(Import imp |
|
||||
imp.getAnImportedModuleName() = "ssl" and
|
||||
imp.getAName().getAsname().(Name).getId() = insecure_version
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
predicate unsafe_ssl_wrap_socket_call(
|
||||
CallNode call, string method_name, string insecure_version, string named_argument
|
||||
) {
|
||||
(
|
||||
call = ssl_wrap_socket().getACall() and
|
||||
method_name = "deprecated method ssl.wrap_socket" and
|
||||
named_argument = "ssl_version"
|
||||
unsafe_connection_creation_without_context(this, _)
|
||||
or
|
||||
call = ssl_Context_class().getACall() and
|
||||
named_argument = "protocol" and
|
||||
method_name = "ssl.SSLContext"
|
||||
) and
|
||||
insecure_version = insecure_version_name() and
|
||||
(
|
||||
call.getArgByName(named_argument).pointsTo(the_ssl_module().attr(insecure_version))
|
||||
unsafe_context_creation(this, _)
|
||||
}
|
||||
|
||||
AstNode getNode() { result = this.asCfgNode().(CallNode).getFunction().getNode() }
|
||||
}
|
||||
|
||||
// Helper for pretty printer `callName`.
|
||||
// This is a consequence of missing pretty priting.
|
||||
// We do not want to evaluate our bespoke pretty printer
|
||||
// for all `AstNode`s so we define a sub class of interesting ones.
|
||||
//
|
||||
// Note that AstNode is abstract and AstNode_ is a library class, so
|
||||
// we have to extend @py_ast_node.
|
||||
class Nameable extends @py_ast_node {
|
||||
Nameable() {
|
||||
this = any(ProtocolConfiguration pc).getNode()
|
||||
or
|
||||
probable_insecure_ssl_constant(call, insecure_version, named_argument)
|
||||
)
|
||||
exists(Nameable attr | this = attr.(Attribute).getObject())
|
||||
}
|
||||
|
||||
string toString() { result = "AstNode" }
|
||||
}
|
||||
|
||||
predicate unsafe_pyOpenSSL_Context_call(CallNode call, string insecure_version) {
|
||||
call = the_pyOpenSSL_Context_class().getACall() and
|
||||
insecure_version = insecure_version_name() and
|
||||
call.getArg(0).pointsTo(the_pyOpenSSL_module().attr(insecure_version))
|
||||
}
|
||||
|
||||
from CallNode call, string method_name, string insecure_version
|
||||
where
|
||||
unsafe_ssl_wrap_socket_call(call, method_name, insecure_version, _)
|
||||
string callName(Nameable call) {
|
||||
result = call.(Name).getId()
|
||||
or
|
||||
unsafe_pyOpenSSL_Context_call(call, insecure_version) and method_name = "pyOpenSSL.SSL.Context"
|
||||
select call,
|
||||
"Insecure SSL/TLS protocol version " + insecure_version + " specified in call to " + method_name +
|
||||
"."
|
||||
exists(Attribute a | a = call | result = callName(a.getObject()) + "." + a.getName())
|
||||
}
|
||||
|
||||
string configName(ProtocolConfiguration protocolConfiguration) {
|
||||
result =
|
||||
"call to " + callName(protocolConfiguration.asCfgNode().(CallNode).getFunction().getNode())
|
||||
or
|
||||
not protocolConfiguration.asCfgNode() instanceof CallNode and
|
||||
not protocolConfiguration instanceof ContextCreation and
|
||||
result = "context modification"
|
||||
}
|
||||
|
||||
string verb(boolean specific) {
|
||||
specific = true and result = "specified"
|
||||
or
|
||||
specific = false and result = "allowed"
|
||||
}
|
||||
|
||||
from
|
||||
DataFlow::Node connectionCreation, string insecure_version, DataFlow::Node protocolConfiguration,
|
||||
boolean specific
|
||||
where
|
||||
unsafe_connection_creation_with_context(connectionCreation, insecure_version,
|
||||
protocolConfiguration, specific)
|
||||
or
|
||||
unsafe_connection_creation_without_context(connectionCreation, insecure_version) and
|
||||
protocolConfiguration = connectionCreation and
|
||||
specific = true
|
||||
or
|
||||
unsafe_context_creation(protocolConfiguration, insecure_version) and
|
||||
connectionCreation = protocolConfiguration and
|
||||
specific = true
|
||||
select connectionCreation,
|
||||
"Insecure SSL/TLS protocol version " + insecure_version + " " + verb(specific) + " by $@ ",
|
||||
protocolConfiguration, configName(protocolConfiguration)
|
||||
|
||||
83
python/ql/src/Security/CWE-327/PyOpenSSL.qll
Normal file
83
python/ql/src/Security/CWE-327/PyOpenSSL.qll
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Provides modeling of SSL/TLS functionality of the `OpenSSL` module from the `pyOpenSSL` PyPI package.
|
||||
* See https://www.pyopenssl.org/en/stable/
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
import TlsLibraryModel
|
||||
|
||||
class PyOpenSSLContextCreation extends ContextCreation, DataFlow::CallCfgNode {
|
||||
PyOpenSSLContextCreation() {
|
||||
this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Context").getACall()
|
||||
}
|
||||
|
||||
override string getProtocol() {
|
||||
exists(ControlFlowNode protocolArg, PyOpenSSL pyo |
|
||||
protocolArg in [node.getArg(0), node.getArgByName("method")]
|
||||
|
|
||||
protocolArg =
|
||||
[pyo.specific_version(result).getAUse(), pyo.unspecific_version(result).getAUse()]
|
||||
.asCfgNode()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ConnectionCall extends ConnectionCreation, DataFlow::CallCfgNode {
|
||||
ConnectionCall() {
|
||||
this = API::moduleImport("OpenSSL").getMember("SSL").getMember("Connection").getACall()
|
||||
}
|
||||
|
||||
override DataFlow::CfgNode getContext() {
|
||||
result.getNode() in [node.getArg(0), node.getArgByName("context")]
|
||||
}
|
||||
}
|
||||
|
||||
// This cannot be used to unrestrict,
|
||||
// see https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_options
|
||||
class SetOptionsCall extends ProtocolRestriction, DataFlow::CallCfgNode {
|
||||
SetOptionsCall() { node.getFunction().(AttrNode).getName() = "set_options" }
|
||||
|
||||
override DataFlow::CfgNode getContext() {
|
||||
result.getNode() = node.getFunction().(AttrNode).getObject()
|
||||
}
|
||||
|
||||
override ProtocolVersion getRestriction() {
|
||||
API::moduleImport("OpenSSL").getMember("SSL").getMember("OP_NO_" + result).getAUse().asCfgNode() in [
|
||||
node.getArg(0), node.getArgByName("options")
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificPyOpenSSLContextCreation extends PyOpenSSLContextCreation, UnspecificContextCreation {
|
||||
UnspecificPyOpenSSLContextCreation() { library instanceof PyOpenSSL }
|
||||
}
|
||||
|
||||
class PyOpenSSL extends TlsLibrary {
|
||||
PyOpenSSL() { this = "pyOpenSSL" }
|
||||
|
||||
override string specific_version_name(ProtocolVersion version) { result = version + "_METHOD" }
|
||||
|
||||
override string unspecific_version_name(ProtocolFamily family) {
|
||||
// `"TLS_METHOD"` is not actually available in pyOpenSSL yet, but should be coming soon..
|
||||
result = family + "_METHOD"
|
||||
}
|
||||
|
||||
override API::Node version_constants() { result = API::moduleImport("OpenSSL").getMember("SSL") }
|
||||
|
||||
override ContextCreation default_context_creation() { none() }
|
||||
|
||||
override ContextCreation specific_context_creation() {
|
||||
result instanceof PyOpenSSLContextCreation
|
||||
}
|
||||
|
||||
override DataFlow::Node insecure_connection_creation(ProtocolVersion version) { none() }
|
||||
|
||||
override ConnectionCreation connection_creation() { result instanceof ConnectionCall }
|
||||
|
||||
override ProtocolRestriction protocol_restriction() { result instanceof SetOptionsCall }
|
||||
|
||||
override ProtocolUnrestriction protocol_unrestriction() {
|
||||
result instanceof UnspecificPyOpenSSLContextCreation
|
||||
}
|
||||
}
|
||||
24
python/ql/src/Security/CWE-327/README.md
Normal file
24
python/ql/src/Security/CWE-327/README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Current status (Feb 2021)
|
||||
|
||||
This should be kept up to date; the world is moving fast and protocols are being broken.
|
||||
|
||||
## Protocols
|
||||
|
||||
- All versions of SSL are insecure
|
||||
- TLS 1.0 and TLS 1.1 are insecure
|
||||
- TLS 1.2 have some issues. but TLS 1.3 is not widely supported
|
||||
|
||||
## Conection methods
|
||||
|
||||
- `ssl.wrap_socket` is creating insecure connections, use `SSLContext.wrap_socket` instead. [link](https://docs.python.org/3/library/ssl.html#ssl.wrap_socket)
|
||||
> Deprecated since version 3.7: Since Python 3.2 and 2.7.9, it is recommended to use the `SSLContext.wrap_socket()` instead of `wrap_socket()`. The top-level function is limited and creates an insecure client socket without server name indication or hostname matching.
|
||||
- Default constructors are fine, a fluent API is used to constrain possible protocols later.
|
||||
|
||||
## Current recomendation
|
||||
|
||||
TLS 1.2 or TLS 1.3
|
||||
|
||||
## Queries
|
||||
|
||||
- `InsecureProtocol` detects uses of insecure protocols.
|
||||
- `InsecureDefaultProtocol` detect default constructions, this is no longer unsafe.
|
||||
214
python/ql/src/Security/CWE-327/Ssl.qll
Normal file
214
python/ql/src/Security/CWE-327/Ssl.qll
Normal file
@@ -0,0 +1,214 @@
|
||||
/**
|
||||
* Provides modeling of SSL/TLS functionality of the `ssl` module from the standard library.
|
||||
* See https://docs.python.org/3.9/library/ssl.html
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
import TlsLibraryModel
|
||||
|
||||
class SSLContextCreation extends ContextCreation, DataFlow::CallCfgNode {
|
||||
SSLContextCreation() { this = API::moduleImport("ssl").getMember("SSLContext").getACall() }
|
||||
|
||||
override string getProtocol() {
|
||||
exists(ControlFlowNode protocolArg, Ssl ssl |
|
||||
protocolArg in [node.getArg(0), node.getArgByName("protocol")]
|
||||
|
|
||||
protocolArg =
|
||||
[ssl.specific_version(result).getAUse(), ssl.unspecific_version(result).getAUse()]
|
||||
.asCfgNode()
|
||||
)
|
||||
or
|
||||
not exists(node.getAnArg()) and
|
||||
result = "TLS"
|
||||
}
|
||||
}
|
||||
|
||||
class SSLDefaultContextCreation extends ContextCreation {
|
||||
SSLDefaultContextCreation() {
|
||||
this = API::moduleImport("ssl").getMember("create_default_context").getACall()
|
||||
}
|
||||
|
||||
// Allowed insecure versions are "TLSv1" and "TLSv1_1"
|
||||
// see https://docs.python.org/3/library/ssl.html#context-creation
|
||||
override string getProtocol() { result = "TLS" }
|
||||
}
|
||||
|
||||
/** Gets a reference to an `ssl.Context` instance. */
|
||||
API::Node sslContextInstance() {
|
||||
result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getReturn()
|
||||
}
|
||||
|
||||
class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode {
|
||||
WrapSocketCall() { this = sslContextInstance().getMember("wrap_socket").getACall() }
|
||||
|
||||
override DataFlow::Node getContext() {
|
||||
result = this.getFunction().(DataFlow::AttrRead).getObject()
|
||||
}
|
||||
}
|
||||
|
||||
class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode {
|
||||
ProtocolVersion restriction;
|
||||
|
||||
OptionsAugOr() {
|
||||
exists(AugAssign aa, AttrNode attr, Expr flag |
|
||||
aa.getOperation().getOp() instanceof BitOr and
|
||||
aa.getTarget() = attr.getNode() and
|
||||
attr.getName() = "options" and
|
||||
attr.getObject() = node and
|
||||
flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
|
||||
(
|
||||
aa.getValue() = flag
|
||||
or
|
||||
impliesBitSet(aa.getValue(), flag, false, false)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getRestriction() { result = restriction }
|
||||
}
|
||||
|
||||
class OptionsAugAndNot extends ProtocolUnrestriction, DataFlow::CfgNode {
|
||||
ProtocolVersion restriction;
|
||||
|
||||
OptionsAugAndNot() {
|
||||
exists(AugAssign aa, AttrNode attr, Expr flag, UnaryExpr notFlag |
|
||||
aa.getOperation().getOp() instanceof BitAnd and
|
||||
aa.getTarget() = attr.getNode() and
|
||||
attr.getName() = "options" and
|
||||
attr.getObject() = node and
|
||||
notFlag.getOp() instanceof Invert and
|
||||
notFlag.getOperand() = flag and
|
||||
flag = API::moduleImport("ssl").getMember("OP_NO_" + restriction).getAUse().asExpr() and
|
||||
(
|
||||
aa.getValue() = notFlag
|
||||
or
|
||||
impliesBitSet(aa.getValue(), notFlag, true, true)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getUnrestriction() { result = restriction }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if
|
||||
* for every bit, _b_:
|
||||
* `wholeHasBitSet` represents that _b_ is set in `whole`
|
||||
* implies
|
||||
* `partHasBitSet` represents that _b_ is set in `part`
|
||||
*
|
||||
* As an example take `whole` = `part1 & part2`. Then
|
||||
* `impliesBitSet(whole, part1, true, true)` holds
|
||||
* because for any bit in `whole`, if that bit is set it must also be set in `part1`.
|
||||
*
|
||||
* Similarly for `whole` = `part1 | part2`. Here
|
||||
* `impliesBitSet(whole, part1, false, false)` holds
|
||||
* because for any bit in `whole`, if that bit is not set, it cannot be set in `part1`.
|
||||
*/
|
||||
predicate impliesBitSet(BinaryExpr whole, Expr part, boolean partHasBitSet, boolean wholeHasBitSet) {
|
||||
whole.getOp() instanceof BitAnd and
|
||||
(
|
||||
wholeHasBitSet = true and partHasBitSet = true and part in [whole.getLeft(), whole.getRight()]
|
||||
or
|
||||
wholeHasBitSet = true and
|
||||
impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
|
||||
)
|
||||
or
|
||||
whole.getOp() instanceof BitOr and
|
||||
(
|
||||
wholeHasBitSet = false and partHasBitSet = false and part in [whole.getLeft(), whole.getRight()]
|
||||
or
|
||||
wholeHasBitSet = false and
|
||||
impliesBitSet([whole.getLeft(), whole.getRight()], part, partHasBitSet, wholeHasBitSet)
|
||||
)
|
||||
}
|
||||
|
||||
class ContextSetVersion extends ProtocolRestriction, ProtocolUnrestriction, DataFlow::CfgNode {
|
||||
ProtocolVersion restriction;
|
||||
|
||||
ContextSetVersion() {
|
||||
exists(DataFlow::AttrWrite aw |
|
||||
aw.getObject().asCfgNode() = node and
|
||||
aw.getAttributeName() = "minimum_version" and
|
||||
aw.getValue() =
|
||||
API::moduleImport("ssl").getMember("TLSVersion").getMember(restriction).getAUse()
|
||||
)
|
||||
}
|
||||
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getRestriction() { result.lessThan(restriction) }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
restriction = result or restriction.lessThan(result)
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificSSLContextCreation extends SSLContextCreation, UnspecificContextCreation {
|
||||
UnspecificSSLContextCreation() { library instanceof Ssl }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
result = UnspecificContextCreation.super.getUnrestriction() and
|
||||
// These are turned off by default since Python 3.6
|
||||
// see https://docs.python.org/3.6/library/ssl.html#ssl.SSLContext
|
||||
not result in ["SSLv2", "SSLv3"]
|
||||
}
|
||||
}
|
||||
|
||||
class UnspecificSSLDefaultContextCreation extends SSLDefaultContextCreation, ProtocolUnrestriction {
|
||||
override DataFlow::Node getContext() { result = this }
|
||||
|
||||
// see https://docs.python.org/3/library/ssl.html#ssl.create_default_context
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
result in ["TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
}
|
||||
|
||||
class Ssl extends TlsLibrary {
|
||||
Ssl() { this = "ssl" }
|
||||
|
||||
override string specific_version_name(ProtocolVersion version) { result = "PROTOCOL_" + version }
|
||||
|
||||
override string unspecific_version_name(ProtocolFamily family) {
|
||||
family = "SSLv23" and result = "PROTOCOL_" + family
|
||||
or
|
||||
family = "TLS" and result = "PROTOCOL_" + family + ["", "_CLIENT", "_SERVER"]
|
||||
}
|
||||
|
||||
override API::Node version_constants() { result = API::moduleImport("ssl") }
|
||||
|
||||
override ContextCreation default_context_creation() {
|
||||
result instanceof SSLDefaultContextCreation
|
||||
}
|
||||
|
||||
override ContextCreation specific_context_creation() { result instanceof SSLContextCreation }
|
||||
|
||||
override DataFlow::CallCfgNode insecure_connection_creation(ProtocolVersion version) {
|
||||
result = API::moduleImport("ssl").getMember("wrap_socket").getACall() and
|
||||
this.specific_version(version).getAUse() = result.getArgByName("ssl_version") and
|
||||
version.isInsecure()
|
||||
}
|
||||
|
||||
override ConnectionCreation connection_creation() { result instanceof WrapSocketCall }
|
||||
|
||||
override ProtocolRestriction protocol_restriction() {
|
||||
result instanceof OptionsAugOr
|
||||
or
|
||||
result instanceof ContextSetVersion
|
||||
}
|
||||
|
||||
override ProtocolUnrestriction protocol_unrestriction() {
|
||||
result instanceof OptionsAugAndNot
|
||||
or
|
||||
result instanceof ContextSetVersion
|
||||
or
|
||||
result instanceof UnspecificSSLContextCreation
|
||||
or
|
||||
result instanceof UnspecificSSLDefaultContextCreation
|
||||
}
|
||||
}
|
||||
137
python/ql/src/Security/CWE-327/TlsLibraryModel.qll
Normal file
137
python/ql/src/Security/CWE-327/TlsLibraryModel.qll
Normal file
@@ -0,0 +1,137 @@
|
||||
private import python
|
||||
private import semmle.python.ApiGraphs
|
||||
import Ssl
|
||||
import PyOpenSSL
|
||||
|
||||
/**
|
||||
* A specific protocol version of SSL or TLS.
|
||||
*/
|
||||
class ProtocolVersion extends string {
|
||||
ProtocolVersion() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"] }
|
||||
|
||||
/** Gets a `ProtocolVersion` that is less than this `ProtocolVersion`, if any. */
|
||||
predicate lessThan(ProtocolVersion version) {
|
||||
this = "SSLv2" and version = "SSLv3"
|
||||
or
|
||||
this = "TLSv1" and version = ["TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
or
|
||||
this = ["TLSv1", "TLSv1_1"] and version = ["TLSv1_2", "TLSv1_3"]
|
||||
or
|
||||
this = ["TLSv1", "TLSv1_1", "TLSv1_2"] and version = "TLSv1_3"
|
||||
}
|
||||
|
||||
/** Holds if this protocol version is known to be insecure. */
|
||||
predicate isInsecure() { this in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1"] }
|
||||
}
|
||||
|
||||
/** An unspecific protocol version */
|
||||
class ProtocolFamily extends string {
|
||||
ProtocolFamily() { this in ["SSLv23", "TLS"] }
|
||||
}
|
||||
|
||||
/** The creation of a context. */
|
||||
abstract class ContextCreation extends DataFlow::Node {
|
||||
/** Gets the protocol version or family for this context. */
|
||||
abstract string getProtocol();
|
||||
}
|
||||
|
||||
/** The creation of a connection from a context. */
|
||||
abstract class ConnectionCreation extends DataFlow::Node {
|
||||
/** Gets the context used to create the connection. */
|
||||
abstract DataFlow::Node getContext();
|
||||
}
|
||||
|
||||
/** A context is being restricted on which protocols it can accepts. */
|
||||
abstract class ProtocolRestriction extends DataFlow::Node {
|
||||
/** Gets the context being restricted. */
|
||||
abstract DataFlow::Node getContext();
|
||||
|
||||
/** Gets the protocol version being disallowed. */
|
||||
abstract ProtocolVersion getRestriction();
|
||||
}
|
||||
|
||||
/** A context is being relaxed on which protocols it can accepts. */
|
||||
abstract class ProtocolUnrestriction extends DataFlow::Node {
|
||||
/** Gets the context being relaxed. */
|
||||
abstract DataFlow::Node getContext();
|
||||
|
||||
/** Gets the protocol version being allowed. */
|
||||
abstract ProtocolVersion getUnrestriction();
|
||||
}
|
||||
|
||||
/**
|
||||
* A context is being created with a range of allowed protocols.
|
||||
* This also serves as unrestricting these protocols.
|
||||
*/
|
||||
abstract class UnspecificContextCreation extends ContextCreation, ProtocolUnrestriction {
|
||||
TlsLibrary library;
|
||||
ProtocolFamily family;
|
||||
|
||||
UnspecificContextCreation() { this.getProtocol() = family }
|
||||
|
||||
override DataFlow::CfgNode getContext() { result = this }
|
||||
|
||||
override ProtocolVersion getUnrestriction() {
|
||||
// There is only one family, the two names are aliases in OpenSSL.
|
||||
// see https://github.com/openssl/openssl/blob/13888e797c5a3193e91d71e5f5a196a2d68d266f/include/openssl/ssl.h.in#L1953-L1955
|
||||
family in ["SSLv23", "TLS"] and
|
||||
// see https://docs.python.org/3/library/ssl.html#ssl-contexts
|
||||
result in ["SSLv2", "SSLv3", "TLSv1", "TLSv1_1", "TLSv1_2", "TLSv1_3"]
|
||||
}
|
||||
}
|
||||
|
||||
/** A model of a SSL/TLS library. */
|
||||
abstract class TlsLibrary extends string {
|
||||
bindingset[this]
|
||||
TlsLibrary() { any() }
|
||||
|
||||
/** The name of a specific protocol version. */
|
||||
abstract string specific_version_name(ProtocolVersion version);
|
||||
|
||||
/** Gets a name, which is a member of `version_constants`, that can be used to specify the protocol family `family`. */
|
||||
abstract string unspecific_version_name(ProtocolFamily family);
|
||||
|
||||
/** Gets an API node representing the module or class holding the version constants. */
|
||||
abstract API::Node version_constants();
|
||||
|
||||
/** Gets an API node representing a specific protocol version. */
|
||||
API::Node specific_version(ProtocolVersion version) {
|
||||
result = version_constants().getMember(specific_version_name(version))
|
||||
}
|
||||
|
||||
/** Gets an API node representing the protocol family `family`. */
|
||||
API::Node unspecific_version(ProtocolFamily family) {
|
||||
result = version_constants().getMember(unspecific_version_name(family))
|
||||
}
|
||||
|
||||
/** Gets a creation of a context with a default protocol. */
|
||||
abstract ContextCreation default_context_creation();
|
||||
|
||||
/** Gets a creation of a context with a specific protocol. */
|
||||
abstract ContextCreation specific_context_creation();
|
||||
|
||||
/** Gets a creation of a context with a specific protocol version, known to be insecure. */
|
||||
ContextCreation insecure_context_creation(ProtocolVersion version) {
|
||||
result in [specific_context_creation(), default_context_creation()] and
|
||||
result.getProtocol() = version and
|
||||
version.isInsecure()
|
||||
}
|
||||
|
||||
/** Gets a context that was created using `family`, known to have insecure instances. */
|
||||
ContextCreation unspecific_context_creation(ProtocolFamily family) {
|
||||
result in [specific_context_creation(), default_context_creation()] and
|
||||
result.getProtocol() = family
|
||||
}
|
||||
|
||||
/** Gets a dataflow node representing a connection being created in an insecure manner, not from a context. */
|
||||
abstract DataFlow::Node insecure_connection_creation(ProtocolVersion version);
|
||||
|
||||
/** Gets a dataflow node representing a connection being created from a context. */
|
||||
abstract ConnectionCreation connection_creation();
|
||||
|
||||
/** Gets a dataflow node representing a context being restricted on which protocols it can accepts. */
|
||||
abstract ProtocolRestriction protocol_restriction();
|
||||
|
||||
/** Gets a dataflow node representing a context being relaxed on which protocols it can accepts. */
|
||||
abstract ProtocolUnrestriction protocol_unrestriction();
|
||||
}
|
||||
13
python/ql/src/Summary/LinesOfCode.ql
Normal file
13
python/ql/src/Summary/LinesOfCode.ql
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @name Total lines of Python code in the database
|
||||
* @description The total number of lines of Python code across all files, including
|
||||
* external libraries and auto-generated files. This is a useful metric of the size of a
|
||||
* database. This query counts the lines of code, excluding whitespace or comments.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
* @id py/summary/lines-of-code
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
select sum(Module m | | m.getMetrics().getNumberOfLinesOfCode())
|
||||
21
python/ql/src/Summary/LinesOfUserCode.ql
Normal file
21
python/ql/src/Summary/LinesOfUserCode.ql
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* @name Total lines of user written Python code in the database
|
||||
* @description The total number of lines of Python code from the source code directory,
|
||||
* excluding auto-generated files. This query counts the lines of code, excluding
|
||||
* whitespace or comments. Note: If external libraries are included in the codebase
|
||||
* either in a checked-in virtual environment or as vendored code, that will currently
|
||||
* be counted as user written code.
|
||||
* @kind metric
|
||||
* @tags summary
|
||||
* @id py/summary/lines-of-user-code
|
||||
*/
|
||||
|
||||
import python
|
||||
import semmle.python.filters.GeneratedCode
|
||||
|
||||
select sum(Module m |
|
||||
exists(m.getFile().getRelativePath()) and
|
||||
not m.getFile() instanceof GeneratedFile
|
||||
|
|
||||
m.getMetrics().getNumberOfLinesOfCode()
|
||||
)
|
||||
@@ -26,9 +26,8 @@ variable should be renamed to make the code easier to interpret.</p>
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>J. Lusth, <i>The Art and Craft of Programming - Python Edition</i>, Section: Scope. University of Alabama, 2012. (<a href="http://troll.cs.ua.edu/ACP-PY/index_13.html">Published online</a>).</li>
|
||||
<li>New Mexico Tech Computer Center: <a href="http://infohost.nmt.edu/tcc/help/pubs/python/web/global-statement.html">The global
|
||||
statement: Declare access to a global name</a>.</li>
|
||||
<li>J. Lusth, <i>The Art and Craft of Programming - Python Edition</i>, Section: Scope. University of Alabama, 2012. (<a href="https://web.archive.org/web/20190919091129/http://troll.cs.ua.edu/ACP-PY/index_13.html">Published online</a>).</li>
|
||||
<li>Python Language Reference: <a href="http://docs.python.org/reference/simple_stmts.html#the-global-statement">The global statement</a>.</li>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>A class name that begins with a lowercase letter does not follow standard
|
||||
naming conventions. This decreases code readability. For example, <code>class background</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Write the class name beginning with an uppercase letter. For example, <code>class Background</code>.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<references>
|
||||
|
||||
<li>
|
||||
Guido van Rossum, Barry Warsaw, Nick Coghlan <em>PEP 8 -- Style Guide for Python Code</em>
|
||||
<a href="https://www.python.org/dev/peps/pep-0008/#class-names">Python Class Names</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name Misnamed class
|
||||
* @description A class name that begins with a lowercase letter decreases readability.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id py/misnamed-class
|
||||
* @tags maintainability
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate lower_case_class(Class c) {
|
||||
exists(string first_char |
|
||||
first_char = c.getName().prefix(1) and
|
||||
not first_char = first_char.toUpperCase()
|
||||
)
|
||||
}
|
||||
|
||||
from Class c
|
||||
where
|
||||
c.inSource() and
|
||||
lower_case_class(c) and
|
||||
not exists(Class c1 |
|
||||
c1 != c and
|
||||
c1.getLocation().getFile() = c.getLocation().getFile() and
|
||||
lower_case_class(c1)
|
||||
)
|
||||
select c, "Class names should start in uppercase."
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
|
||||
|
||||
<overview>
|
||||
<p>A function name that begins with an uppercase letter does not follow standard
|
||||
naming conventions. This decreases code readability. For example, <code>Jump</code>.
|
||||
</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>
|
||||
Write the function name beginning with an lowercase letter. For example, <code>jump</code>.
|
||||
</p>
|
||||
|
||||
</recommendation>
|
||||
|
||||
<references>
|
||||
|
||||
<li>
|
||||
Guido van Rossum, Barry Warsaw, Nick Coghlan <em>PEP 8 -- Style Guide for Python Code</em>
|
||||
<a href="https://www.python.org/dev/peps/pep-0008/#function-and-variable-names">Python Function and Variable Names</a>
|
||||
</li>
|
||||
|
||||
</references>
|
||||
|
||||
</qhelp>
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* @name Misnamed function
|
||||
* @description A function name that begins with an uppercase letter decreases readability.
|
||||
* @kind problem
|
||||
* @problem.severity recommendation
|
||||
* @id py/misnamed-function
|
||||
* @tags maintainability
|
||||
*/
|
||||
|
||||
import python
|
||||
|
||||
predicate upper_case_function(Function func) {
|
||||
exists(string first_char |
|
||||
first_char = func.getName().prefix(1) and
|
||||
not first_char = first_char.toLowerCase()
|
||||
)
|
||||
}
|
||||
|
||||
from Function func
|
||||
where
|
||||
func.inSource() and
|
||||
upper_case_function(func) and
|
||||
not exists(Function func1 |
|
||||
func1 != func and
|
||||
func1.getLocation().getFile() = func.getLocation().getFile() and
|
||||
upper_case_function(func1)
|
||||
)
|
||||
select func, "Function names should start in lowercase."
|
||||
420
python/ql/src/experimental/typetracking/TypeTracker.qll
Normal file
420
python/ql/src/experimental/typetracking/TypeTracker.qll
Normal file
@@ -0,0 +1,420 @@
|
||||
/** Step Summaries and Type Tracking */
|
||||
|
||||
private import TypeTrackerSpecific
|
||||
|
||||
/**
|
||||
* Any string that may appear as the name of a piece of content. This will usually include things like:
|
||||
* - Attribute names (in Python)
|
||||
* - Property names (in JavaScript)
|
||||
*
|
||||
* In general, this can also be used to model things like stores to specific list indices. To ensure
|
||||
* correctness, it is important that
|
||||
*
|
||||
* - different types of content do not have overlapping names, and
|
||||
* - the empty string `""` is not a valid piece of content, as it is used to indicate the absence of
|
||||
* content instead.
|
||||
*/
|
||||
class ContentName extends string {
|
||||
ContentName() { this = getPossibleContentName() }
|
||||
}
|
||||
|
||||
/** Either a content name, or the empty string (representing no content). */
|
||||
class OptionalContentName extends string {
|
||||
OptionalContentName() { this instanceof ContentName or this = "" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A description of a step on an inter-procedural data flow path.
|
||||
*/
|
||||
private newtype TStepSummary =
|
||||
LevelStep() or
|
||||
CallStep() or
|
||||
ReturnStep() or
|
||||
StoreStep(ContentName content) or
|
||||
LoadStep(ContentName content)
|
||||
|
||||
/**
|
||||
* 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 content | this = StoreStep(content) | result = "store " + content)
|
||||
or
|
||||
exists(string content | this = LoadStep(content) | result = "load " + content)
|
||||
}
|
||||
}
|
||||
|
||||
/** 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, LocalSourceNode 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 content |
|
||||
localSourceStoreStep(nodeFrom, nodeTo, content) and
|
||||
summary = StoreStep(content)
|
||||
or
|
||||
basicLoadStep(nodeFrom, nodeTo, content) and summary = LoadStep(content)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
|
||||
*
|
||||
* Note that `nodeTo` will always be a local source node that flows to the place where the content
|
||||
* is written in `basicStoreStep`. This may lead to the flow of information going "back in time"
|
||||
* from the point of view of the execution of the program.
|
||||
*
|
||||
* For instance, if we interpret attribute writes in Python as writing to content with the same
|
||||
* name as the attribute and consider the following snippet
|
||||
*
|
||||
* ```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 `content` 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 localSourceStoreStep(Node nodeFrom, LocalSourceNode nodeTo, string content) {
|
||||
exists(Node obj | nodeTo.flowsTo(obj) and basicStoreStep(nodeFrom, obj, content))
|
||||
}
|
||||
}
|
||||
|
||||
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalContentName content)
|
||||
|
||||
/**
|
||||
* Summary of the steps needed to track a value to a given dataflow node.
|
||||
*
|
||||
* This can be used to track objects that implement a certain API in order to
|
||||
* recognize calls to that API. Note that type-tracking does not by itself provide a
|
||||
* source/sink relation, that is, it may determine that a node has a given type,
|
||||
* but it won't determine where that type came from.
|
||||
*
|
||||
* It is recommended that all uses of this type are written in the following form,
|
||||
* for tracking some type `myType`:
|
||||
* ```ql
|
||||
* DataFlow::LocalSourceNode myType(DataFlow::TypeTracker t) {
|
||||
* t.start() and
|
||||
* result = < source of myType >
|
||||
* or
|
||||
* exists (DataFlow::TypeTracker t2 |
|
||||
* result = myType(t2).track(t2, t)
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* DataFlow::Node myType() { myType(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
* ```
|
||||
*
|
||||
* 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;
|
||||
OptionalContentName content;
|
||||
|
||||
TypeTracker() { this = MkTypeTracker(hasCall, content) }
|
||||
|
||||
/** 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, content)
|
||||
or
|
||||
step = ReturnStep() and hasCall = false and result = this
|
||||
or
|
||||
step = LoadStep(content) and result = MkTypeTracker(hasCall, "")
|
||||
or
|
||||
exists(string p | step = StoreStep(p) and content = "" and result = MkTypeTracker(hasCall, p))
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withCall, string withContent |
|
||||
(if hasCall = true then withCall = "with" else withCall = "without") and
|
||||
(if content != "" then withContent = " with content " + content else withContent = "") and
|
||||
result = "type tracker " + withCall + " call steps" + withContent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasCall = false and content = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking, and the value starts in the content named `contentName`.
|
||||
* The type tracking only ends after the content has been loaded.
|
||||
*/
|
||||
predicate startInContent(ContentName contentName) { hasCall = false and content = contentName }
|
||||
|
||||
/**
|
||||
* 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 content = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { content = "" }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Holds if this type has been tracked into a call.
|
||||
*/
|
||||
boolean hasCall() { result = hasCall }
|
||||
|
||||
/**
|
||||
* INTERNAL. DO NOT USE.
|
||||
*
|
||||
* Gets the content associated with this type tracker.
|
||||
*/
|
||||
string getContent() { result = content }
|
||||
|
||||
/**
|
||||
* Gets a type tracker that starts where this one has left off to allow continued
|
||||
* tracking.
|
||||
*
|
||||
* This predicate is only defined if the type is not associated to a piece of content.
|
||||
*/
|
||||
TypeTracker continue() { content = "" 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
|
||||
}
|
||||
}
|
||||
|
||||
/** Provides predicates for implementing custom `TypeTracker`s. */
|
||||
module TypeTracker {
|
||||
/**
|
||||
* Gets a valid end point of type tracking.
|
||||
*/
|
||||
TypeTracker end() { result.end() }
|
||||
}
|
||||
|
||||
private newtype TTypeBackTracker = MkTypeBackTracker(Boolean hasReturn, OptionalContentName content)
|
||||
|
||||
/**
|
||||
* 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`:
|
||||
*
|
||||
* ```ql
|
||||
* 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 content;
|
||||
|
||||
TypeBackTracker() { this = MkTypeBackTracker(hasReturn, content) }
|
||||
|
||||
/** 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, content)
|
||||
or
|
||||
exists(string p |
|
||||
step = LoadStep(p) and content = "" and result = MkTypeBackTracker(hasReturn, p)
|
||||
)
|
||||
or
|
||||
step = StoreStep(content) and result = MkTypeBackTracker(hasReturn, "")
|
||||
}
|
||||
|
||||
/** Gets a textual representation of this summary. */
|
||||
string toString() {
|
||||
exists(string withReturn, string withContent |
|
||||
(if hasReturn = true then withReturn = "with" else withReturn = "without") and
|
||||
(if content != "" then withContent = " with content " + content else withContent = "") and
|
||||
result = "type back-tracker " + withReturn + " return steps" + withContent
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if this is the starting point of type tracking.
|
||||
*/
|
||||
predicate start() { hasReturn = false and content = "" }
|
||||
|
||||
/**
|
||||
* Holds if this is the end point of type tracking.
|
||||
*/
|
||||
predicate end() { content = "" }
|
||||
|
||||
/**
|
||||
* 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 a piece of content.
|
||||
*/
|
||||
TypeBackTracker continue() { content = "" 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() }
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Provides Python-specific definitions for use in the type tracker library.
|
||||
*/
|
||||
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPublic as DataFlowPublic
|
||||
private import semmle.python.dataflow.new.internal.DataFlowPrivate as DataFlowPrivate
|
||||
|
||||
class Node = DataFlowPublic::Node;
|
||||
|
||||
class LocalSourceNode = DataFlowPublic::LocalSourceNode;
|
||||
|
||||
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStep/2;
|
||||
|
||||
predicate jumpStep = DataFlowPrivate::jumpStep/2;
|
||||
|
||||
/**
|
||||
* Gets the name of a possible piece of content. For Python, this is currently only attribute names,
|
||||
* using the name of the attribute for the corresponding content.
|
||||
*/
|
||||
string getPossibleContentName() { result = any(DataFlowPublic::AttrRef a).getAttributeName() }
|
||||
|
||||
/**
|
||||
* 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 DataFlowPrivate::DataFlowCallable getCallableForArgument(
|
||||
DataFlowPublic::ArgumentNode nodeFrom, int i
|
||||
) {
|
||||
exists(DataFlowPrivate::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(DataFlowPublic::ArgumentNode nodeFrom, DataFlowPublic::ParameterNode nodeTo) {
|
||||
// TODO: Support special methods?
|
||||
exists(DataFlowPrivate::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(DataFlowPrivate::ReturnNode nodeFrom, Node nodeTo) {
|
||||
exists(DataFlowPrivate::DataFlowCall call |
|
||||
nodeFrom.getEnclosingCallable() = call.getCallable() and nodeTo.asCfgNode() = call.getNode()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeFrom` is being written to the `content` content of the object in `nodeTo`.
|
||||
*/
|
||||
predicate basicStoreStep(Node nodeFrom, Node nodeTo, string content) {
|
||||
exists(DataFlowPublic::AttrWrite a |
|
||||
a.mayHaveAttributeName(content) and
|
||||
nodeFrom = a.getValue() and
|
||||
nodeTo = a.getObject()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `nodeTo` is the result of accessing the `content` content of `nodeFrom`.
|
||||
*/
|
||||
predicate basicLoadStep(Node nodeFrom, Node nodeTo, string content) {
|
||||
exists(DataFlowPublic::AttrRead a |
|
||||
a.mayHaveAttributeName(content) and
|
||||
nodeFrom = a.getObject() and
|
||||
nodeTo = a
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility class that is equivalent to `boolean` but does not require type joining.
|
||||
*/
|
||||
class Boolean extends boolean {
|
||||
Boolean() { this = true or this = false }
|
||||
}
|
||||
@@ -25,7 +25,7 @@ duplicate classes.</p>
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="http://www4.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="https://wwwbroy.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -25,7 +25,7 @@ importing that module into the original module.</p>
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="http://www4.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="https://wwwbroy.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -19,7 +19,7 @@ of the shared code into its own module and import that module into the original.
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="http://www4.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="https://wwwbroy.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
2
python/ql/src/external/SimilarFunction.qhelp
vendored
2
python/ql/src/external/SimilarFunction.qhelp
vendored
@@ -25,7 +25,7 @@ almost all of their lines are the same, then consider extracting the same lines
|
||||
</recommendation>
|
||||
<references>
|
||||
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="http://www4.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
<li>E. Juergens, F. Deissenboeck, B. Hummel and S. Wagner, <em>Do Code Clones Matter?</em>, 2009. (<a href="https://wwwbroy.in.tum.de/~juergens/publications/ICSE2009_RP_0110_juergens.pdf">available online</a>).</li>
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
|
||||
@@ -363,7 +363,7 @@ module API {
|
||||
n.isGlobal() and
|
||||
n.isLoad() and
|
||||
name = n.getId() and
|
||||
name = any(Builtins::Builtin b).getName()
|
||||
name in [any(Builtins::Builtin b).getName(), "None", "True", "False"]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -422,9 +422,9 @@ module API {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node to which `nd`, which is a use of an API-graph node, flows.
|
||||
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
|
||||
*
|
||||
* The flow from `nd` to that node may be inter-procedural.
|
||||
* The flow from `src` to that node may be inter-procedural.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackUseNode(
|
||||
DataFlow::LocalSourceNode src, DataFlow::TypeTracker t
|
||||
@@ -433,30 +433,26 @@ module API {
|
||||
use(_, src) and
|
||||
result = src
|
||||
or
|
||||
// Due to bad performance when using `trackUseNode(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::StepSummary summary |
|
||||
t = trackUseNode_first_join(src, result, summary).append(summary)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private DataFlow::TypeTracker trackUseNode_first_join(
|
||||
DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(trackUseNode(src, result), res, summary)
|
||||
exists(DataFlow::TypeTracker t2 | result = trackUseNode(src, t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data-flow node to which `src`, which is a use of an API-graph node, flows.
|
||||
*
|
||||
* The flow from `src` to that node may be inter-procedural.
|
||||
*/
|
||||
cached
|
||||
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
|
||||
result = trackUseNode(src, DataFlow::TypeTracker::end())
|
||||
result = trackUseNode(src, DataFlow::TypeTracker::end()) and
|
||||
// We exclude module variable nodes, as these do not correspond to real uses.
|
||||
not result instanceof DataFlow::ModuleVariableNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
|
||||
*/
|
||||
cached
|
||||
predicate edge(Node pred, string lbl, Node succ) {
|
||||
predicate edge(TApiNode pred, string lbl, TApiNode succ) {
|
||||
/* There's an edge from the root node for each imported module. */
|
||||
exists(string m |
|
||||
pred = MkRoot() and
|
||||
|
||||
@@ -570,21 +570,7 @@ module Cryptography {
|
||||
arg = any(KeyGeneration::Range r).getKeySizeArg() and
|
||||
result = arg.getALocalSource()
|
||||
or
|
||||
// Due to bad performance when using normal setup with we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeBackTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
keysizeBacktracker_first_join(t2, arg, result, summary) and
|
||||
t = t2.prepend(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate keysizeBacktracker_first_join(
|
||||
DataFlow::TypeBackTracker t2, DataFlow::Node arg, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(res, keysizeBacktracker(t2, arg), summary)
|
||||
exists(DataFlow::TypeBackTracker t2 | result = keysizeBacktracker(t2, arg).backtrack(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a back-reference to the keysize argument `arg` that was used to generate a new key-pair. */
|
||||
|
||||
@@ -72,6 +72,33 @@ class File extends Container {
|
||||
* are specified to be extracted.
|
||||
*/
|
||||
string getContents() { file_contents(this, result) }
|
||||
|
||||
/** Holds if this file is likely to get executed directly, and thus act as an entry point for execution. */
|
||||
predicate isPossibleEntryPoint() {
|
||||
// Only consider files in the source code, and not things like the standard library
|
||||
exists(this.getRelativePath()) and
|
||||
(
|
||||
// The file doesn't have the extension `.py` but still contains Python statements
|
||||
not this.getExtension().matches("py%") and
|
||||
exists(Stmt s | s.getLocation().getFile() = this)
|
||||
or
|
||||
// The file contains the usual `if __name__ == '__main__':` construction
|
||||
exists(If i, Name name, StrConst main, Cmpop op |
|
||||
i.getScope().(Module).getFile() = this and
|
||||
op instanceof Eq and
|
||||
i.getTest().(Compare).compares(name, op, main) and
|
||||
name.getId() = "__name__" and
|
||||
main.getText() = "__main__"
|
||||
)
|
||||
or
|
||||
// The file contains a `#!` line referencing the python interpreter
|
||||
exists(Comment c |
|
||||
c.getLocation().getFile() = this and
|
||||
c.getLocation().getStartLine() = 1 and
|
||||
c.getText().regexpMatch("^#! */.*python(2|3)?[ \\\\t]*$")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private predicate occupied_line(File f, int n) {
|
||||
|
||||
@@ -129,7 +129,11 @@ class Module extends Module_, Scope, AstNode {
|
||||
a.defines(all) and
|
||||
a.getScope() = this and
|
||||
all.getId() = "__all__" and
|
||||
a.getValue().(List).getAnElt().(StrConst).getText() = name
|
||||
(
|
||||
a.getValue().(List).getAnElt().(StrConst).getText() = name
|
||||
or
|
||||
a.getValue().(Tuple).getAnElt().(StrConst).getText() = name
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -201,11 +205,38 @@ private string moduleNameFromBase(Container file) {
|
||||
file instanceof File and result = file.getStem()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `file` may be transitively imported from a file that may serve as the entry point of
|
||||
* the execution.
|
||||
*/
|
||||
private predicate transitively_imported_from_entry_point(File file) {
|
||||
file.getExtension().matches("%py%") and
|
||||
exists(File importer |
|
||||
// Only consider files that are in the source archive
|
||||
exists(importer.getRelativePath()) and
|
||||
importer.getParent() = file.getParent() and
|
||||
exists(ImportExpr i |
|
||||
i.getLocation().getFile() = importer and
|
||||
i.getName() = file.getStem() and
|
||||
// Disregard relative imports
|
||||
i.getLevel() = 0
|
||||
)
|
||||
|
|
||||
importer.isPossibleEntryPoint() or transitively_imported_from_entry_point(importer)
|
||||
)
|
||||
}
|
||||
|
||||
string moduleNameFromFile(Container file) {
|
||||
exists(string basename |
|
||||
basename = moduleNameFromBase(file) and
|
||||
legalShortName(basename) and
|
||||
legalShortName(basename)
|
||||
|
|
||||
result = moduleNameFromFile(file.getParent()) + "." + basename
|
||||
or
|
||||
// If `file` is a transitive import of a file that's executed directly, we allow references
|
||||
// to it by its `basename`.
|
||||
transitively_imported_from_entry_point(file) and
|
||||
result = basename
|
||||
)
|
||||
or
|
||||
isPotentialSourcePackage(file) and
|
||||
|
||||
@@ -51,8 +51,8 @@ module StepSummary {
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
cached
|
||||
predicate step(LocalSourceNode nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
exists(Node mid | typePreservingStep*(nodeFrom, mid) and smallstep(mid, nodeTo, summary))
|
||||
predicate step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo, StepSummary summary) {
|
||||
exists(Node mid | nodeFrom.flowsTo(mid) and smallstep(mid, nodeTo, summary))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ module StepSummary {
|
||||
* type-preserving steps.
|
||||
*/
|
||||
predicate smallstep(Node nodeFrom, Node nodeTo, StepSummary summary) {
|
||||
typePreservingStep(nodeFrom, nodeTo) and
|
||||
jumpStep(nodeFrom, nodeTo) and
|
||||
summary = LevelStep()
|
||||
or
|
||||
callStep(nodeFrom, nodeTo) and summary = CallStep()
|
||||
@@ -80,12 +80,6 @@ module StepSummary {
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if it's reasonable to expect the data flow step from `nodeFrom` to `nodeTo` to preserve types. */
|
||||
private predicate typePreservingStep(Node nodeFrom, Node nodeTo) {
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) or
|
||||
jumpStep(nodeFrom, nodeTo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a callable for the call where `nodeFrom` is used as the `i`'th argument.
|
||||
*
|
||||
@@ -274,10 +268,10 @@ class TypeTracker extends TTypeTracker {
|
||||
* heap and/or inter-procedural step from `nodeFrom` to `nodeTo`.
|
||||
*/
|
||||
pragma[inline]
|
||||
TypeTracker step(LocalSourceNode nodeFrom, Node nodeTo) {
|
||||
TypeTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, nodeTo, summary) and
|
||||
result = this.append(summary)
|
||||
StepSummary::step(nodeFrom, pragma[only_bind_out](nodeTo), pragma[only_bind_into](summary)) and
|
||||
result = this.append(pragma[only_bind_into](summary))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -312,7 +306,7 @@ class TypeTracker extends TTypeTracker {
|
||||
result = this.append(summary)
|
||||
)
|
||||
or
|
||||
typePreservingStep(nodeFrom, nodeTo) and
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
result = this
|
||||
}
|
||||
}
|
||||
@@ -417,8 +411,8 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
pragma[inline]
|
||||
TypeBackTracker step(LocalSourceNode nodeFrom, LocalSourceNode nodeTo) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, nodeTo, summary) and
|
||||
this = result.prepend(summary)
|
||||
StepSummary::step(pragma[only_bind_out](nodeFrom), nodeTo, pragma[only_bind_into](summary)) and
|
||||
this = result.prepend(pragma[only_bind_into](summary))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -453,7 +447,7 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
this = result.prepend(summary)
|
||||
)
|
||||
or
|
||||
typePreservingStep(nodeFrom, nodeTo) and
|
||||
simpleLocalFlowStep(nodeFrom, nodeTo) and
|
||||
this = result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -2133,11 +2133,8 @@ private module Stage4 {
|
||||
|
||||
bindingset[node, cc, config]
|
||||
private LocalCc getLocalCc(Node node, Cc cc, Configuration config) {
|
||||
exists(Cc cc0 |
|
||||
cc = pragma[only_bind_into](cc0) and
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(cc0, getNodeEnclosingCallable(node))
|
||||
)
|
||||
localFlowEntry(node, config) and
|
||||
result = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(node))
|
||||
}
|
||||
|
||||
private predicate localStep(
|
||||
@@ -3132,7 +3129,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt
|
||||
conf = mid.getConfiguration() and
|
||||
cc = mid.getCallContext() and
|
||||
sc = mid.getSummaryCtx() and
|
||||
localCC = getLocalCallContext(cc, getNodeEnclosingCallable(midnode)) and
|
||||
localCC = getLocalCallContext(pragma[only_bind_out](cc), getNodeEnclosingCallable(midnode)) and
|
||||
ap0 = mid.getAp()
|
||||
|
|
||||
localFlowBigStep(midnode, node, true, _, conf, localCC) and
|
||||
|
||||
@@ -31,7 +31,7 @@ predicate accessPathCostLimits(int apLimit, int tupleLimit) {
|
||||
* currently excludes read-steps, store-steps, and flow-through.
|
||||
*
|
||||
* The analysis uses non-linear recursion: When computing a flow path in or out
|
||||
* of a call, we use the results of the analysis recursively to resolve lamba
|
||||
* of a call, we use the results of the analysis recursively to resolve lambda
|
||||
* calls. For this reason, we cannot reuse the code from `DataFlowImpl.qll` directly.
|
||||
*/
|
||||
private module LambdaFlow {
|
||||
|
||||
@@ -1617,5 +1617,5 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
|
||||
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
|
||||
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
|
||||
|
||||
/** Extra data-flow steps needed for lamba flow analysis. */
|
||||
/** Extra data-flow steps needed for lambda flow analysis. */
|
||||
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
|
||||
|
||||
@@ -119,22 +119,6 @@ class Node extends TNode {
|
||||
/** Gets the expression corresponding to this node, if any. */
|
||||
Expr asExpr() { none() }
|
||||
|
||||
/**
|
||||
* Gets a node that this node may flow to using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeTracker` for more details about how to use this.
|
||||
*/
|
||||
pragma[inline]
|
||||
Node track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a node that may flow into this one using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeBackTracker` for more details about how to use this.
|
||||
*/
|
||||
pragma[inline]
|
||||
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
|
||||
|
||||
/**
|
||||
* Gets a local source node from which data may flow to this node in zero or more local data-flow steps.
|
||||
*/
|
||||
@@ -180,7 +164,7 @@ class CfgNode extends Node, TCfgNode {
|
||||
}
|
||||
|
||||
/** A data-flow node corresponding to a `CallNode` in the control-flow graph. */
|
||||
class CallCfgNode extends CfgNode {
|
||||
class CallCfgNode extends CfgNode, LocalSourceNode {
|
||||
override CallNode node;
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,23 +10,37 @@ import python
|
||||
import DataFlowPublic
|
||||
private import DataFlowPrivate
|
||||
|
||||
private predicate comes_from_cfgnode(Node node) {
|
||||
exists(CfgNode first, Node second |
|
||||
simpleLocalFlowStep(first, second) and
|
||||
simpleLocalFlowStep*(second, node)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A data flow node that is a source of local flow. This includes things like
|
||||
* - Expressions
|
||||
* - Function parameters
|
||||
*
|
||||
*
|
||||
* Local source nodes and the `flowsTo` relation should be thought of in terms of the reference
|
||||
* semantics of the underlying object. For instance, in the following snippet of code
|
||||
*
|
||||
* ```python
|
||||
* x = []
|
||||
* x.append(1)
|
||||
* x.append(2)
|
||||
* ```
|
||||
*
|
||||
* the local source node corresponding to the occurrences of `x` is the empty list that is assigned to `x`
|
||||
* originally. Even though the two `append` calls modify the value of `x`, they do not change the fact that
|
||||
* `x` still points to the same object. If, however, we next do `x = x + [3]`, then the expression `x + [3]`
|
||||
* will be the new local source of what `x` now points to.
|
||||
*/
|
||||
class LocalSourceNode extends Node {
|
||||
cached
|
||||
LocalSourceNode() {
|
||||
not comes_from_cfgnode(this) and
|
||||
not this instanceof ModuleVariableNode
|
||||
not simpleLocalFlowStep(_, this) and
|
||||
// Currently, we create synthetic post-update nodes for
|
||||
// - arguments to calls that may modify said argument
|
||||
// - direct reads a writes of object attributes
|
||||
// Both of these preserve the identity of the underlying pointer, and hence we exclude these as
|
||||
// local source nodes.
|
||||
// We do, however, allow the post-update nodes that arise from object creation (which are non-synthetic).
|
||||
not this instanceof SyntheticPostUpdateNode
|
||||
or
|
||||
this = any(ModuleVariableNode mvn).getARead()
|
||||
}
|
||||
@@ -63,6 +77,22 @@ class LocalSourceNode extends Node {
|
||||
* Gets a call to this node.
|
||||
*/
|
||||
CallCfgNode getACall() { Cached::call(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a node that this node may flow to using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeTracker` for more details about how to use this.
|
||||
*/
|
||||
pragma[inline]
|
||||
LocalSourceNode track(TypeTracker t2, TypeTracker t) { t = t2.step(this, result) }
|
||||
|
||||
/**
|
||||
* Gets a node that may flow into this one using one heap and/or interprocedural step.
|
||||
*
|
||||
* See `TypeBackTracker` for more details about how to use this.
|
||||
*/
|
||||
pragma[inline]
|
||||
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -83,23 +83,11 @@ private module CryptographyModel {
|
||||
result.(DataFlow::CallCfgNode).getFunction() = curveClassWithKeySize(keySize) and
|
||||
origin = result
|
||||
or
|
||||
// Due to bad performance when using normal setup with we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
curveClassInstanceWithKeySize_first_join(t2, keySize, origin, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
result = curveClassInstanceWithKeySize(t2, keySize, origin).track(t2, t)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate curveClassInstanceWithKeySize_first_join(
|
||||
DataFlow::TypeTracker t2, int keySize, DataFlow::Node origin, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(curveClassInstanceWithKeySize(t2, keySize, origin), res, summary)
|
||||
}
|
||||
|
||||
/** Gets a reference to a predefined curve class instance with a specific key size (in bits), as well as the origin of the class. */
|
||||
DataFlow::Node curveClassInstanceWithKeySize(int keySize, DataFlow::Node origin) {
|
||||
curveClassInstanceWithKeySize(DataFlow::TypeTracker::end(), keySize, origin).flowsTo(result)
|
||||
|
||||
@@ -7,50 +7,19 @@ private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
|
||||
private module Dill {
|
||||
/** Gets a reference to the `dill` module. */
|
||||
private DataFlow::Node dill(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("dill")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = dill(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `dill` module. */
|
||||
DataFlow::Node dill() { result = dill(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Provides models for the `dill` module. */
|
||||
module dill {
|
||||
/** Gets a reference to the `dill.loads` function. */
|
||||
private DataFlow::Node loads(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("dill.loads")
|
||||
or
|
||||
t.startInAttr("loads") and
|
||||
result = dill()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = loads(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `dill.loads` function. */
|
||||
DataFlow::Node loads() { result = loads(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
}
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* A call to `dill.loads`
|
||||
* See https://pypi.org/project/dill/ (which currently refers you
|
||||
* to https://docs.python.org/3/library/pickle.html#pickle.loads)
|
||||
*/
|
||||
private class DillLoadsCall extends Decoding::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
DillLoadsCall() { node.getFunction() = Dill::dill::loads().asCfgNode() }
|
||||
private class DillLoadsCall extends Decoding::Range, DataFlow::CallCfgNode {
|
||||
DillLoadsCall() { this = API::moduleImport("dill").getMember("loads").getACall() }
|
||||
|
||||
override predicate mayExecuteInput() { any() }
|
||||
|
||||
override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) }
|
||||
override DataFlow::Node getAnInput() { result = this.getArg(0) }
|
||||
|
||||
override DataFlow::Node getOutput() { result = this }
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides classes modeling security-relevant aspects of the `fabric` PyPI package, for
|
||||
@@ -20,54 +21,7 @@ private import semmle.python.Concepts
|
||||
*/
|
||||
private module FabricV1 {
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
private DataFlow::Node fabric(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = fabric(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
DataFlow::Node fabric() { result = fabric(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["api"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = fabric()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `fabric_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
fabric_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fabric_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(fabric_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(string attr_name) {
|
||||
result = fabric_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
API::Node fabric() { result = API::moduleImport("fabric") }
|
||||
|
||||
/** Provides models for the `fabric` module. */
|
||||
module fabric {
|
||||
@@ -75,50 +29,10 @@ private module FabricV1 {
|
||||
// fabric.api
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.api` module. */
|
||||
DataFlow::Node api() { result = fabric_attr("api") }
|
||||
API::Node api() { result = fabric().getMember("api") }
|
||||
|
||||
/** Provides models for the `fabric.api` module */
|
||||
module api {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.api` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node api_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["run", "local", "sudo"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.api" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = api()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `api_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
api_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate api_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(api_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.api` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node api_attr(string attr_name) {
|
||||
result = api_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call to either
|
||||
* - `fabric.api.local`
|
||||
@@ -130,12 +44,8 @@ private module FabricV1 {
|
||||
* - https://docs.fabfile.org/en/1.14/api/core/operations.html#fabric.operations.sudo
|
||||
*/
|
||||
private class FabricApiLocalRunSudoCall extends SystemCommandExecution::Range,
|
||||
DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
FabricApiLocalRunSudoCall() {
|
||||
node.getFunction() = api_attr(["local", "run", "sudo"]).asCfgNode()
|
||||
}
|
||||
DataFlow::CallCfgNode {
|
||||
FabricApiLocalRunSudoCall() { this = api().getMember(["local", "run", "sudo"]).getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("command")]
|
||||
@@ -153,61 +63,7 @@ private module FabricV1 {
|
||||
*/
|
||||
private module FabricV2 {
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
private DataFlow::Node fabric(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = fabric(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric` module. */
|
||||
DataFlow::Node fabric() { result = fabric(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in [
|
||||
// connection.py
|
||||
"connection", "Connection",
|
||||
// group.py
|
||||
"group", "SerialGroup", "ThreadingGroup",
|
||||
// tasks.py
|
||||
"tasks", "task"
|
||||
] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = fabric()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `fabric_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
fabric_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate fabric_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(fabric_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node fabric_attr(string attr_name) {
|
||||
result = fabric_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
API::Node fabric() { result = API::moduleImport("fabric") }
|
||||
|
||||
/** Provides models for the `fabric` module. */
|
||||
module fabric {
|
||||
@@ -215,50 +71,10 @@ private module FabricV2 {
|
||||
// fabric.connection
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.connection` module. */
|
||||
DataFlow::Node connection() { result = fabric_attr("connection") }
|
||||
API::Node connection() { result = fabric().getMember("connection") }
|
||||
|
||||
/** Provides models for the `fabric.connection` module */
|
||||
module connection {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.connection` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node connection_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["Connection"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.connection" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = connection()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `connection_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
connection_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate connection_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(connection_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.connection` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node connection_attr(string attr_name) {
|
||||
result = connection_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fabric.connection.Connection` class
|
||||
*
|
||||
@@ -266,20 +82,12 @@ private module FabricV2 {
|
||||
*/
|
||||
module Connection {
|
||||
/** Gets a reference to the `fabric.connection.Connection` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = connection_attr("Connection")
|
||||
API::Node classRef() {
|
||||
result = fabric().getMember("Connection")
|
||||
or
|
||||
// handle `fabric.Connection` alias
|
||||
t.start() and
|
||||
result = fabric_attr("Connection")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
result = connection().getMember("Connection")
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.connection.Connection` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* A source of instances of `fabric.connection.Connection`, extend this class to model new instances.
|
||||
*
|
||||
@@ -289,16 +97,14 @@ private module FabricV2 {
|
||||
*
|
||||
* Use the predicate `Connection::instance()` to get references to instances of `fabric.connection.Connection`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `fabric.connection.Connection`. */
|
||||
private DataFlow::Node instance(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
@@ -306,7 +112,7 @@ private module FabricV2 {
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `fabric.connection.Connection`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/**
|
||||
* Gets a reference to either `run`, `sudo`, or `local` method on a
|
||||
@@ -317,7 +123,7 @@ private module FabricV2 {
|
||||
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.sudo
|
||||
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
|
||||
*/
|
||||
private DataFlow::Node instanceRunMethods(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instanceRunMethods(DataFlow::TypeTracker t) {
|
||||
t.startInAttr(["run", "sudo", "local"]) and
|
||||
result = instance()
|
||||
or
|
||||
@@ -334,7 +140,7 @@ private module FabricV2 {
|
||||
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
|
||||
*/
|
||||
DataFlow::Node instanceRunMethods() {
|
||||
result = instanceRunMethods(DataFlow::TypeTracker::end())
|
||||
instanceRunMethods(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -347,11 +153,9 @@ private module FabricV2 {
|
||||
* - https://docs.fabfile.org/en/2.5/api/connection.html#fabric.connection.Connection.local
|
||||
*/
|
||||
private class FabricConnectionRunSudoLocalCall extends SystemCommandExecution::Range,
|
||||
DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
DataFlow::CallCfgNode {
|
||||
FabricConnectionRunSudoLocalCall() {
|
||||
node.getFunction() = fabric::connection::Connection::instanceRunMethods().asCfgNode()
|
||||
this.getFunction() = fabric::connection::Connection::instanceRunMethods()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
@@ -363,34 +167,19 @@ private module FabricV2 {
|
||||
// fabric.tasks
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.tasks` module. */
|
||||
DataFlow::Node tasks() { result = fabric_attr("tasks") }
|
||||
API::Node tasks() { result = fabric().getMember("tasks") }
|
||||
|
||||
/** Provides models for the `fabric.tasks` module */
|
||||
module tasks {
|
||||
/** Gets a reference to the `fabric.tasks.task` decorator. */
|
||||
private DataFlow::Node task(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.tasks.task")
|
||||
or
|
||||
t.startInAttr("task") and
|
||||
result = tasks()
|
||||
or
|
||||
// Handle `fabric.task` alias
|
||||
t.startInAttr("task") and
|
||||
result = fabric()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = task(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.tasks.task` decorator. */
|
||||
DataFlow::Node task() { result = task(DataFlow::TypeTracker::end()) }
|
||||
API::Node task() { result in [tasks().getMember("task"), fabric().getMember("task")] }
|
||||
}
|
||||
|
||||
class FabricTaskFirstParamConnectionInstance extends fabric::connection::Connection::InstanceSource,
|
||||
DataFlow::ParameterNode {
|
||||
FabricTaskFirstParamConnectionInstance() {
|
||||
exists(Function func |
|
||||
func.getADecorator() = fabric::tasks::task().asExpr() and
|
||||
func.getADecorator() = fabric::tasks::task().getAUse().asExpr() and
|
||||
this.getParameter() = func.getArg(0)
|
||||
)
|
||||
}
|
||||
@@ -400,50 +189,10 @@ private module FabricV2 {
|
||||
// fabric.group
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `fabric.group` module. */
|
||||
DataFlow::Node group() { result = fabric_attr("group") }
|
||||
API::Node group() { result = fabric().getMember("group") }
|
||||
|
||||
/** Provides models for the `fabric.group` module */
|
||||
module group {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.group` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node group_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["SerialGroup", "ThreadingGroup"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("fabric.group" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = group()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `group_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
group_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate group_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(group_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `fabric.group` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node group_attr(string attr_name) {
|
||||
result = group_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `fabric.group.Group` class and its subclasses.
|
||||
*
|
||||
@@ -465,41 +214,20 @@ private module FabricV2 {
|
||||
*
|
||||
* Use `Group::subclassInstance()` predicate to get references to an instance of a subclass of `fabric.group.Group`.
|
||||
*/
|
||||
abstract class SubclassInstanceSource extends DataFlow::Node { }
|
||||
|
||||
/** Gets a reference to an instance of a subclass of `fabric.group.Group`. */
|
||||
private DataFlow::Node subclassInstance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof SubclassInstanceSource
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = subclassInstance(t2).track(t2, t))
|
||||
abstract class ModeledSubclass extends API::Node {
|
||||
/** Gets a string representation of this element. */
|
||||
override string toString() { result = this.(API::Node).toString() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of a subclass of `fabric.group.Group`. */
|
||||
DataFlow::Node subclassInstance() {
|
||||
result = subclassInstance(DataFlow::TypeTracker::end())
|
||||
}
|
||||
API::Node subclassInstance() { result = any(ModeledSubclass m).getASubclass*().getReturn() }
|
||||
|
||||
/**
|
||||
* Gets a reference to the `run` method on an instance of a subclass of `fabric.group.Group`.
|
||||
*
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run
|
||||
*/
|
||||
private DataFlow::Node subclassInstanceRunMethod(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("run") and
|
||||
result = subclassInstance()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = subclassInstanceRunMethod(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `run` method on an instance of a subclass of `fabric.group.Group`.
|
||||
*
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run
|
||||
*/
|
||||
DataFlow::Node subclassInstanceRunMethod() {
|
||||
result = subclassInstanceRunMethod(DataFlow::TypeTracker::end())
|
||||
}
|
||||
API::Node subclassInstanceRunMethod() { result = subclassInstance().getMember("run") }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -507,12 +235,8 @@ private module FabricV2 {
|
||||
*
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.Group.run
|
||||
*/
|
||||
private class FabricGroupRunCall extends SystemCommandExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
FabricGroupRunCall() {
|
||||
node.getFunction() = fabric::group::Group::subclassInstanceRunMethod().asCfgNode()
|
||||
}
|
||||
private class FabricGroupRunCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
|
||||
FabricGroupRunCall() { this = fabric::group::Group::subclassInstanceRunMethod().getACall() }
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
result.asCfgNode() = [node.getArg(0), node.getArgByName("command")]
|
||||
@@ -525,25 +249,12 @@ private module FabricV2 {
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.SerialGroup.
|
||||
*/
|
||||
module SerialGroup {
|
||||
/** Gets a reference to the `fabric.group.SerialGroup` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = group_attr("SerialGroup")
|
||||
or
|
||||
// Handle `fabric.SerialGroup` alias
|
||||
t.start() and
|
||||
result = fabric_attr("SerialGroup")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.group.SerialGroup` class. */
|
||||
private DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
private class ClassInstantiation extends Group::SubclassInstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
private class ClassInstantiation extends Group::ModeledSubclass {
|
||||
ClassInstantiation() {
|
||||
this = group().getMember("SerialGroup")
|
||||
or
|
||||
this = fabric().getMember("SerialGroup")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,25 +264,12 @@ private module FabricV2 {
|
||||
* See https://docs.fabfile.org/en/2.5/api/group.html#fabric.group.ThreadingGroup.
|
||||
*/
|
||||
module ThreadingGroup {
|
||||
/** Gets a reference to the `fabric.group.ThreadingGroup` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = group_attr("ThreadingGroup")
|
||||
or
|
||||
// Handle `fabric.ThreadingGroup` alias
|
||||
t.start() and
|
||||
result = fabric_attr("ThreadingGroup")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `fabric.group.ThreadingGroup` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
private class ClassInstantiation extends Group::SubclassInstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
private class ClassInstantiation extends Group::ModeledSubclass {
|
||||
ClassInstantiation() {
|
||||
this = group().getMember("ThreadingGroup")
|
||||
or
|
||||
this = fabric().getMember("ThreadingGroup")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,13 +401,15 @@ module Flask {
|
||||
}
|
||||
}
|
||||
|
||||
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSource {
|
||||
private class RequestAttrMultiDict extends Werkzeug::werkzeug::datastructures::MultiDict::InstanceSourceApiNode {
|
||||
string attr_name;
|
||||
|
||||
RequestAttrMultiDict() {
|
||||
attr_name in ["args", "values", "form", "files"] and
|
||||
this = request().getMember(attr_name).getAnImmediateUse()
|
||||
this = request().getMember(attr_name)
|
||||
}
|
||||
|
||||
override string toString() { result = this.(API::Node).toString() }
|
||||
}
|
||||
|
||||
private class RequestAttrFiles extends RequestAttrMultiDict {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `invoke` PyPI package.
|
||||
@@ -16,102 +17,44 @@ private module Invoke {
|
||||
// invoke
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `invoke` module. */
|
||||
private DataFlow::Node invoke(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("invoke")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = invoke(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `invoke` module. */
|
||||
DataFlow::Node invoke() { result = invoke(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `invoke` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node invoke_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["run", "sudo", "context", "Context", "task"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("invoke." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = DataFlow::importNode("invoke")
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `invoke_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
invoke_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate invoke_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(invoke_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `invoke` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node invoke_attr(string attr_name) {
|
||||
result = invoke_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
API::Node invoke() { result = API::moduleImport("invoke") }
|
||||
|
||||
/** Provides models for the `invoke` module. */
|
||||
module invoke {
|
||||
/** Gets a reference to the `invoke.context` module. */
|
||||
DataFlow::Node context() { result = invoke_attr("context") }
|
||||
API::Node context() { result = invoke().getMember("context") }
|
||||
|
||||
/** Provides models for the `invoke.context` module */
|
||||
module context {
|
||||
/** Provides models for the `invoke.context.Context` class */
|
||||
module Context {
|
||||
/** Gets a reference to the `invoke.context.Context` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("invoke.context.Context")
|
||||
API::Node classRef() {
|
||||
result = API::moduleImport("invoke").getMember("context").getMember("Context")
|
||||
or
|
||||
t.startInAttr("Context") and
|
||||
result = invoke::context()
|
||||
or
|
||||
// handle invoke.Context alias
|
||||
t.start() and
|
||||
result = invoke_attr("Context")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
result = API::moduleImport("invoke").getMember("Context")
|
||||
}
|
||||
|
||||
/** Gets a reference to the `invoke.context.Context` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** Gets a reference to an instance of `invoke.context.Context`. */
|
||||
private DataFlow::Node instance(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.asCfgNode().(CallNode).getFunction() =
|
||||
invoke::context::Context::classRef().asCfgNode()
|
||||
or
|
||||
t.start() and
|
||||
exists(Function func |
|
||||
func.getADecorator() = invoke_attr("task").asExpr() and
|
||||
result.(DataFlow::ParameterNode).getParameter() = func.getArg(0)
|
||||
(
|
||||
result = invoke::context::Context::classRef().getACall()
|
||||
or
|
||||
exists(Function func |
|
||||
func.getADecorator() = invoke().getMember("task").getAUse().asExpr() and
|
||||
result.(DataFlow::ParameterNode).getParameter() = func.getArg(0)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `invoke.context.Context`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** Gets a reference to the `run` or `sudo` methods on a `invoke.context.Context` instance. */
|
||||
private DataFlow::Node instanceRunMethods(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instanceRunMethods(DataFlow::TypeTracker t) {
|
||||
t.startInAttr(["run", "sudo"]) and
|
||||
result = invoke::context::Context::instance()
|
||||
or
|
||||
@@ -120,7 +63,7 @@ private module Invoke {
|
||||
|
||||
/** Gets a reference to the `run` or `sudo` methods on a `invoke.context.Context` instance. */
|
||||
DataFlow::Node instanceRunMethods() {
|
||||
result = instanceRunMethods(DataFlow::TypeTracker::end())
|
||||
instanceRunMethods(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,15 +74,10 @@ private module Invoke {
|
||||
* - `invoke.run` or `invoke.sudo` functions (http://docs.pyinvoke.org/en/stable/api/__init__.html)
|
||||
* - `run` or `sudo` methods on a `invoke.context.Context` instance (http://docs.pyinvoke.org/en/stable/api/context.html#invoke.context.Context.run)
|
||||
*/
|
||||
private class InvokeRunCommandCall extends SystemCommandExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
private class InvokeRunCommandCall extends SystemCommandExecution::Range, DataFlow::CallCfgNode {
|
||||
InvokeRunCommandCall() {
|
||||
exists(DataFlow::Node callFunction | node.getFunction() = callFunction.asCfgNode() |
|
||||
callFunction = invoke_attr(["run", "sudo"])
|
||||
or
|
||||
callFunction = invoke::context::Context::instanceRunMethods()
|
||||
)
|
||||
this = invoke().getMember(["run", "sudo"]).getACall() or
|
||||
this.getFunction() = invoke::context::Context::instanceRunMethods()
|
||||
}
|
||||
|
||||
override DataFlow::Node getCommand() {
|
||||
|
||||
@@ -9,6 +9,7 @@ private import python
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -21,19 +22,8 @@ private module MySQLdb {
|
||||
// ---------------------------------------------------------------------------
|
||||
// MySQLdb
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `MySQLdb` module. */
|
||||
private DataFlow::Node moduleMySQLdb(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("MySQLdb")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = moduleMySQLdb(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `MySQLdb` module. */
|
||||
DataFlow::Node moduleMySQLdb() { result = moduleMySQLdb(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** MySQLdb implements PEP 249, providing ways to execute SQL statements against a database. */
|
||||
class MySQLdb extends PEP249Module {
|
||||
MySQLdb() { this = moduleMySQLdb() }
|
||||
class MySQLdb extends PEP249ModuleApiNode {
|
||||
MySQLdb() { this = API::moduleImport("MySQLdb") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ private import python
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -21,64 +22,14 @@ private module MysqlConnectorPython {
|
||||
// ---------------------------------------------------------------------------
|
||||
// mysql
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `mysql` module. */
|
||||
private DataFlow::Node mysql(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("mysql")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = mysql(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `mysql` module. */
|
||||
DataFlow::Node mysql() { result = mysql(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `mysql` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node mysql_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["connector"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("mysql" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = mysql()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `mysql_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
mysql_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate mysql_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(mysql_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `mysql` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node mysql_attr(string attr_name) {
|
||||
result = mysql_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/** Provides models for the `mysql` module. */
|
||||
module mysql {
|
||||
/**
|
||||
* The mysql.connector module
|
||||
* See https://dev.mysql.com/doc/connector-python/en/connector-python-example-connecting.html
|
||||
*/
|
||||
class MysqlConnector extends PEP249Module {
|
||||
MysqlConnector() { this = mysql_attr("connector") }
|
||||
class MysqlConnector extends PEP249ModuleApiNode {
|
||||
MysqlConnector() { this = API::moduleImport("mysql").getMember("connector") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,26 @@ private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.RemoteFlowSources
|
||||
private import semmle.python.Concepts
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/** A module implementing PEP 249. Extend this class for implementations. */
|
||||
abstract class PEP249Module extends DataFlow::Node { }
|
||||
/**
|
||||
* A module implementing PEP 249. Extend this class for implementations.
|
||||
*
|
||||
* DEPRECATED: Extend `PEP249ModuleApiNode` instead.
|
||||
*/
|
||||
abstract deprecated class PEP249Module extends DataFlow::Node { }
|
||||
|
||||
/** Gets a reference to a connect call. */
|
||||
private DataFlow::Node connect(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("connect") and
|
||||
result instanceof PEP249Module
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = connect(t2).track(t2, t))
|
||||
/**
|
||||
* An abstract class encompassing API graph nodes that implement PEP 249.
|
||||
* Extend this class for implementations.
|
||||
*/
|
||||
abstract class PEP249ModuleApiNode extends API::Node {
|
||||
/** Gets a string representation of this element. */
|
||||
override string toString() { result = this.(API::Node).toString() }
|
||||
}
|
||||
|
||||
/** Gets a reference to a connect call. */
|
||||
DataFlow::Node connect() { result = connect(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node connect() { result = any(PEP249ModuleApiNode a).getMember("connect").getAUse() }
|
||||
|
||||
/**
|
||||
* Provides models for the `db.Connection` class
|
||||
@@ -43,14 +49,12 @@ module Connection {
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
/** A direct instantiation of `db.Connection`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = connect().asCfgNode() }
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this.getFunction() = connect() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `db.Connection`. */
|
||||
private DataFlow::Node instance(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
@@ -58,7 +62,7 @@ module Connection {
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `db.Connection`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,7 +71,7 @@ module Connection {
|
||||
*/
|
||||
module cursor {
|
||||
/** Gets a reference to the `cursor` method on a connection. */
|
||||
private DataFlow::Node methodRef(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode methodRef(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("cursor") and
|
||||
result = Connection::instance()
|
||||
or
|
||||
@@ -75,10 +79,10 @@ module cursor {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `cursor` method on a connection. */
|
||||
DataFlow::Node methodRef() { result = methodRef(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node methodRef() { methodRef(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** Gets a reference to a result of calling the `cursor` method on a connection. */
|
||||
private DataFlow::Node methodResult(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode methodResult(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.asCfgNode().(CallNode).getFunction() = methodRef().asCfgNode()
|
||||
or
|
||||
@@ -86,7 +90,7 @@ module cursor {
|
||||
}
|
||||
|
||||
/** Gets a reference to a result of calling the `cursor` method on a connection. */
|
||||
DataFlow::Node methodResult() { result = methodResult(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node methodResult() { methodResult(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +101,7 @@ module cursor {
|
||||
*
|
||||
* See https://www.python.org/dev/peps/pep-0249/#id15.
|
||||
*/
|
||||
private DataFlow::Node execute(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode execute(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("execute") and
|
||||
result in [cursor::methodResult(), Connection::instance()]
|
||||
or
|
||||
@@ -112,13 +116,11 @@ private DataFlow::Node execute(DataFlow::TypeTracker t) {
|
||||
*
|
||||
* See https://www.python.org/dev/peps/pep-0249/#id15.
|
||||
*/
|
||||
DataFlow::Node execute() { result = execute(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node execute() { execute(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** A call to the `execute` method on a cursor (or on a connection). */
|
||||
private class ExecuteCall extends SqlExecution::Range, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ExecuteCall() { node.getFunction() = execute().asCfgNode() }
|
||||
private class ExecuteCall extends SqlExecution::Range, DataFlow::CallCfgNode {
|
||||
ExecuteCall() { this.getFunction() = execute() }
|
||||
|
||||
override DataFlow::Node getSql() {
|
||||
result.asCfgNode() in [node.getArg(0), node.getArgByName("sql")]
|
||||
|
||||
@@ -9,6 +9,7 @@ private import python
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -21,19 +22,8 @@ private module Psycopg2 {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Psycopg
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `psycopg2` module. */
|
||||
private DataFlow::Node psycopg2(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("psycopg2")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = psycopg2(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `psycopg2` module. */
|
||||
DataFlow::Node psycopg2() { result = psycopg2(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** psycopg2 implements PEP 249, providing ways to execute SQL statements against a database. */
|
||||
class Psycopg2 extends PEP249Module {
|
||||
Psycopg2() { this = psycopg2() }
|
||||
class Psycopg2 extends PEP249ModuleApiNode {
|
||||
Psycopg2() { this = API::moduleImport("psycopg2") }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ private import python
|
||||
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
|
||||
|
||||
/**
|
||||
@@ -14,19 +15,8 @@ private import PEP249
|
||||
* See https://pypi.org/project/PyMySQL/
|
||||
*/
|
||||
private module PyMySQL {
|
||||
/** Gets a reference to the `pymysql` module. */
|
||||
private DataFlow::Node pymysql(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("pymysql")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = pymysql(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `pymysql` module. */
|
||||
DataFlow::Node pymysql() { result = pymysql(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/** PyMySQL implements PEP 249, providing ways to execute SQL statements against a database. */
|
||||
class PyMySQLPEP249 extends PEP249Module {
|
||||
PyMySQLPEP249() { this = pymysql() }
|
||||
class PyMySQLPEP249 extends PEP249ModuleApiNode {
|
||||
PyMySQLPEP249() { this = API::moduleImport("pymysql") }
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ 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.regex
|
||||
|
||||
/**
|
||||
@@ -19,54 +20,7 @@ private module Tornado {
|
||||
// tornado
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Gets a reference to the `tornado` module. */
|
||||
private DataFlow::Node tornado(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = DataFlow::importNode("tornado")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = tornado(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `tornado` module. */
|
||||
DataFlow::Node tornado() { result = tornado(DataFlow::TypeTracker::end()) }
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `tornado` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node tornado_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["web", "httputil"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("tornado" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = tornado()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `tornado_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
tornado_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate tornado_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(tornado_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `tornado` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node tornado_attr(string attr_name) {
|
||||
result = tornado_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
API::Node tornado() { result = API::moduleImport("tornado") }
|
||||
|
||||
/** Provides models for the `tornado` module. */
|
||||
module tornado {
|
||||
@@ -74,50 +28,10 @@ private module Tornado {
|
||||
// tornado.web
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `tornado.web` module. */
|
||||
DataFlow::Node web() { result = tornado_attr("web") }
|
||||
API::Node web() { result = tornado().getMember("web") }
|
||||
|
||||
/** Provides models for the `tornado.web` module */
|
||||
module web {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `tornado.web` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node web_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["RequestHandler", "Application"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("tornado.web" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = web()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `web_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
web_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate web_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(web_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `tornado.web` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node web_attr(string attr_name) {
|
||||
result = web_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `tornado.web.RequestHandler` class and subclasses.
|
||||
*
|
||||
@@ -125,22 +39,11 @@ private module Tornado {
|
||||
*/
|
||||
module RequestHandler {
|
||||
/** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */
|
||||
private DataFlow::Node subclassRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = web_attr("RequestHandler")
|
||||
or
|
||||
// subclasses in project code
|
||||
result.asExpr().(ClassExpr).getABase() = subclassRef(t.continue()).asExpr()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = subclassRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `tornado.web.RequestHandler` class or any subclass. */
|
||||
DataFlow::Node subclassRef() { result = subclassRef(DataFlow::TypeTracker::end()) }
|
||||
API::Node subclassRef() { result = web().getMember("RequestHandler").getASubclass*() }
|
||||
|
||||
/** A RequestHandler class (most likely in project code). */
|
||||
class RequestHandlerClass extends Class {
|
||||
RequestHandlerClass() { this.getParent() = subclassRef().asExpr() }
|
||||
RequestHandlerClass() { this.getParent() = subclassRef().getAUse().asExpr() }
|
||||
|
||||
/** Gets a function that could handle incoming requests, if any. */
|
||||
Function getARequestHandler() {
|
||||
@@ -151,7 +54,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to this class. */
|
||||
private DataFlow::Node getARef(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode getARef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result.asExpr().(ClassExpr) = this.getParent()
|
||||
or
|
||||
@@ -159,7 +62,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to this class. */
|
||||
DataFlow::Node getARef() { result = this.getARef(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node getARef() { this.getARef(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +74,7 @@ private module Tornado {
|
||||
*
|
||||
* Use the predicate `RequestHandler::instance()` to get references to instances of the `tornado.web.RequestHandler` class or any subclass.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** The `self` parameter in a method on the `tornado.web.RequestHandler` class or any subclass. */
|
||||
private class SelfParam extends InstanceSource, RemoteFlowSource::Range,
|
||||
@@ -184,7 +87,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of the `tornado.web.RequestHandler` class or any subclass. */
|
||||
private DataFlow::Node instance(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
@@ -192,10 +95,10 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of the `tornado.web.RequestHandler` class or any subclass. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** Gets a reference to one of the methods `get_argument`, `get_body_argument`, `get_query_argument`. */
|
||||
private DataFlow::Node argumentMethod(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode argumentMethod(DataFlow::TypeTracker t) {
|
||||
t.startInAttr(["get_argument", "get_body_argument", "get_query_argument"]) and
|
||||
result = instance()
|
||||
or
|
||||
@@ -203,10 +106,12 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to one of the methods `get_argument`, `get_body_argument`, `get_query_argument`. */
|
||||
DataFlow::Node argumentMethod() { result = argumentMethod(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node argumentMethod() {
|
||||
argumentMethod(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference to one of the methods `get_arguments`, `get_body_arguments`, `get_query_arguments`. */
|
||||
private DataFlow::Node argumentsMethod(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode argumentsMethod(DataFlow::TypeTracker t) {
|
||||
t.startInAttr(["get_arguments", "get_body_arguments", "get_query_arguments"]) and
|
||||
result = instance()
|
||||
or
|
||||
@@ -214,10 +119,12 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to one of the methods `get_arguments`, `get_body_arguments`, `get_query_arguments`. */
|
||||
DataFlow::Node argumentsMethod() { result = argumentsMethod(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node argumentsMethod() {
|
||||
argumentsMethod(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference the `redirect` method. */
|
||||
private DataFlow::Node redirectMethod(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode redirectMethod(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("redirect") and
|
||||
result = instance()
|
||||
or
|
||||
@@ -225,10 +132,12 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference the `redirect` method. */
|
||||
DataFlow::Node redirectMethod() { result = redirectMethod(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node redirectMethod() {
|
||||
redirectMethod(DataFlow::TypeTracker::end()).flowsTo(result)
|
||||
}
|
||||
|
||||
/** Gets a reference to the `write` method. */
|
||||
private DataFlow::Node writeMethod(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode writeMethod(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("write") and
|
||||
result = instance()
|
||||
or
|
||||
@@ -236,7 +145,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `write` method. */
|
||||
DataFlow::Node writeMethod() { result = writeMethod(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node writeMethod() { writeMethod(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
@@ -279,15 +188,7 @@ private module Tornado {
|
||||
*/
|
||||
module Application {
|
||||
/** Gets a reference to the `tornado.web.Application` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = web_attr("Application")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `tornado.web.Application` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
API::Node classRef() { result = web().getMember("Application") }
|
||||
|
||||
/**
|
||||
* A source of instances of `tornado.web.Application`, extend this class to model new instances.
|
||||
@@ -298,17 +199,15 @@ private module Tornado {
|
||||
*
|
||||
* Use the predicate `Application::instance()` to get references to instances of `tornado.web.Application`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `tornado.web.Application`. */
|
||||
class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `tornado.web.Application`. */
|
||||
private DataFlow::Node instance(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
@@ -316,10 +215,10 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `tornado.web.Application`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** Gets a reference to the `add_handlers` method. */
|
||||
private DataFlow::Node add_handlers(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode add_handlers(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("add_handlers") and
|
||||
result = instance()
|
||||
or
|
||||
@@ -327,7 +226,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `add_handlers` method. */
|
||||
DataFlow::Node add_handlers() { result = add_handlers(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node add_handlers() { add_handlers(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,50 +234,10 @@ private module Tornado {
|
||||
// tornado.httputil
|
||||
// -------------------------------------------------------------------------
|
||||
/** Gets a reference to the `tornado.httputil` module. */
|
||||
DataFlow::Node httputil() { result = tornado_attr("httputil") }
|
||||
API::Node httputil() { result = tornado().getMember("httputil") }
|
||||
|
||||
/** Provides models for the `tornado.httputil` module */
|
||||
module httputil {
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `tornado.httputil` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node httputil_attr(DataFlow::TypeTracker t, string attr_name) {
|
||||
attr_name in ["HTTPServerRequest"] and
|
||||
(
|
||||
t.start() and
|
||||
result = DataFlow::importNode("tornado.httputil" + "." + attr_name)
|
||||
or
|
||||
t.startInAttr(attr_name) and
|
||||
result = httputil()
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `httputil_attr(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
httputil_attr_first_join(t2, attr_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate httputil_attr_first_join(
|
||||
DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
|
||||
DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(httputil_attr(t2, attr_name), res, summary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the attribute `attr_name` of the `tornado.httputil` module.
|
||||
* WARNING: Only holds for a few predefined attributes.
|
||||
*/
|
||||
private DataFlow::Node httputil_attr(string attr_name) {
|
||||
result = httputil_attr(DataFlow::TypeTracker::end(), attr_name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides models for the `tornado.httputil.HttpServerRequest` class
|
||||
*
|
||||
@@ -386,15 +245,7 @@ private module Tornado {
|
||||
*/
|
||||
module HttpServerRequest {
|
||||
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
|
||||
private DataFlow::Node classRef(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result = httputil_attr("HttpServerRequest")
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = classRef(t2).track(t2, t))
|
||||
}
|
||||
|
||||
/** Gets a reference to the `tornado.httputil.HttpServerRequest` class. */
|
||||
DataFlow::Node classRef() { result = classRef(DataFlow::TypeTracker::end()) }
|
||||
API::Node classRef() { result = httputil().getMember("HttpServerRequest") }
|
||||
|
||||
/**
|
||||
* A source of instances of `tornado.httputil.HttpServerRequest`, extend this class to model new instances.
|
||||
@@ -405,17 +256,15 @@ private module Tornado {
|
||||
*
|
||||
* Use the predicate `HttpServerRequest::instance()` to get references to instances of `tornado.httputil.HttpServerRequest`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
abstract class InstanceSource extends DataFlow::LocalSourceNode { }
|
||||
|
||||
/** A direct instantiation of `tornado.httputil.HttpServerRequest`. */
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
ClassInstantiation() { node.getFunction() = classRef().asCfgNode() }
|
||||
private class ClassInstantiation extends InstanceSource, DataFlow::CallCfgNode {
|
||||
ClassInstantiation() { this = classRef().getACall() }
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */
|
||||
private DataFlow::Node instance(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode instance(DataFlow::TypeTracker t) {
|
||||
t.start() and
|
||||
result instanceof InstanceSource
|
||||
or
|
||||
@@ -423,10 +272,10 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to an instance of `tornado.httputil.HttpServerRequest`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
/** Gets a reference to the `full_url` method. */
|
||||
private DataFlow::Node full_url(DataFlow::TypeTracker t) {
|
||||
private DataFlow::LocalSourceNode full_url(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("full_url") and
|
||||
result = instance()
|
||||
or
|
||||
@@ -434,7 +283,7 @@ private module Tornado {
|
||||
}
|
||||
|
||||
/** Gets a reference to the `full_url` method. */
|
||||
DataFlow::Node full_url() { result = full_url(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node full_url() { full_url(DataFlow::TypeTracker::end()).flowsTo(result) }
|
||||
|
||||
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
|
||||
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
|
||||
@@ -576,11 +425,9 @@ private module Tornado {
|
||||
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.redirect
|
||||
*/
|
||||
private class TornadoRequestHandlerRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
DataFlow::CallCfgNode {
|
||||
TornadoRequestHandlerRedirectCall() {
|
||||
node.getFunction() = tornado::web::RequestHandler::redirectMethod().asCfgNode()
|
||||
this.getFunction() = tornado::web::RequestHandler::redirectMethod()
|
||||
}
|
||||
|
||||
override DataFlow::Node getRedirectLocation() {
|
||||
@@ -600,11 +447,9 @@ private module Tornado {
|
||||
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write
|
||||
*/
|
||||
private class TornadoRequestHandlerWriteCall extends HTTP::Server::HttpResponse::Range,
|
||||
DataFlow::CfgNode {
|
||||
override CallNode node;
|
||||
|
||||
DataFlow::CallCfgNode {
|
||||
TornadoRequestHandlerWriteCall() {
|
||||
node.getFunction() = tornado::web::RequestHandler::writeMethod().asCfgNode()
|
||||
this.getFunction() = tornado::web::RequestHandler::writeMethod()
|
||||
}
|
||||
|
||||
override DataFlow::Node getBody() {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
private import python
|
||||
private import semmle.python.dataflow.new.DataFlow
|
||||
private import semmle.python.dataflow.new.TaintTracking
|
||||
private import semmle.python.ApiGraphs
|
||||
|
||||
/**
|
||||
* Provides models for the `Werkzeug` PyPI package.
|
||||
@@ -23,6 +24,9 @@ module Werkzeug {
|
||||
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.MultiDict.
|
||||
*/
|
||||
module MultiDict {
|
||||
/** DEPRECATED. Use `InstanceSourceApiNode` instead. */
|
||||
abstract deprecated class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of instances of `werkzeug.datastructures.MultiDict`, extend this class to model new instances.
|
||||
*
|
||||
@@ -32,37 +36,16 @@ module Werkzeug {
|
||||
*
|
||||
* Use the predicate `MultiDict::instance()` to get references to instances of `werkzeug.datastructures.MultiDict`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
/** Gets a reference to an instance of `werkzeug.datastructures.MultiDict`. */
|
||||
private DataFlow::Node 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 `werkzeug.datastructures.MultiDict`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
abstract class InstanceSourceApiNode extends API::Node { }
|
||||
|
||||
/**
|
||||
* Gets a reference to the `getlist` method on an instance of `werkzeug.datastructures.MultiDict`.
|
||||
*
|
||||
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist
|
||||
*/
|
||||
private DataFlow::Node getlist(DataFlow::TypeTracker t) {
|
||||
t.startInAttr("getlist") and
|
||||
result = instance()
|
||||
or
|
||||
exists(DataFlow::TypeTracker t2 | result = getlist(t2).track(t2, t))
|
||||
DataFlow::Node getlist() {
|
||||
result = any(InstanceSourceApiNode a).getMember("getlist").getAUse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reference to the `getlist` method on an instance of `werkzeug.datastructures.MultiDict`.
|
||||
*
|
||||
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.Headers.getlist
|
||||
*/
|
||||
DataFlow::Node getlist() { result = getlist(DataFlow::TypeTracker::end()) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +54,9 @@ module Werkzeug {
|
||||
* See https://werkzeug.palletsprojects.com/en/1.0.x/datastructures/#werkzeug.datastructures.FileStorage.
|
||||
*/
|
||||
module FileStorage {
|
||||
/** DEPRECATED. Use `InstanceSourceApiNode` instead. */
|
||||
abstract deprecated class InstanceSource extends DataFlow::Node { }
|
||||
|
||||
/**
|
||||
* A source of instances of `werkzeug.datastructures.FileStorage`, extend this class to model new instances.
|
||||
*
|
||||
@@ -80,18 +66,10 @@ module Werkzeug {
|
||||
*
|
||||
* Use the predicate `FileStorage::instance()` to get references to instances of `werkzeug.datastructures.FileStorage`.
|
||||
*/
|
||||
abstract class InstanceSource extends DataFlow::Node { }
|
||||
abstract class InstanceSourceApiNode extends API::Node { }
|
||||
|
||||
/** Gets a reference to an instance of `werkzeug.datastructures.FileStorage`. */
|
||||
private DataFlow::Node 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 `werkzeug.datastructures.FileStorage`. */
|
||||
DataFlow::Node instance() { result = instance(DataFlow::TypeTracker::end()) }
|
||||
DataFlow::Node instance() { result = any(InstanceSourceApiNode a).getAUse() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1853,8 +1853,10 @@ module Expressions {
|
||||
private boolean isinstanceEvaluatesTo(
|
||||
CallNode call, PointsToContext context, ControlFlowNode use, ObjectInternal val
|
||||
) {
|
||||
exists(ObjectInternal cls | isinstance_call(call, use, context, val, cls) |
|
||||
result = Types::improperSubclass(val.getClass(), cls)
|
||||
exists(ObjectInternal cls, ObjectInternal val_cls |
|
||||
isinstance_call(call, use, context, val, val_cls, cls)
|
||||
|
|
||||
result = Types::improperSubclass(val_cls, cls)
|
||||
or
|
||||
val = ObjectInternal::unknown() and result = maybe()
|
||||
or
|
||||
@@ -1866,12 +1868,13 @@ module Expressions {
|
||||
|
||||
private predicate isinstance_call(
|
||||
CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val,
|
||||
ObjectInternal cls
|
||||
ObjectInternal val_cls, ObjectInternal cls
|
||||
) {
|
||||
exists(ControlFlowNode func, ControlFlowNode arg1 |
|
||||
call2(call, func, use, arg1) and
|
||||
points_to_isinstance(func, context) and
|
||||
PointsToInternal::pointsTo(use, context, val, _) and
|
||||
val_cls = val.getClass() and
|
||||
PointsToInternal::pointsTo(arg1, context, cls, _)
|
||||
)
|
||||
}
|
||||
@@ -1993,10 +1996,7 @@ module Expressions {
|
||||
exists(ObjectInternal sup_or_tuple |
|
||||
issubclass_call(_, _, _, sub, sup_or_tuple) and sub.isClass() = true
|
||||
or
|
||||
exists(ObjectInternal val |
|
||||
isinstance_call(_, _, _, val, sup_or_tuple) and
|
||||
sub = val.getClass()
|
||||
)
|
||||
exists(ObjectInternal val | isinstance_call(_, _, _, val, sub, sup_or_tuple))
|
||||
|
|
||||
sup = sup_or_tuple
|
||||
or
|
||||
|
||||
@@ -100,10 +100,14 @@ private int total_call_cost(CallNode call) {
|
||||
if call_to_init_or_del(call) then result = 1 else result = call_cost(call) + splay_cost(call)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private int relevant_call_cost(PointsToContext ctx, CallNode call) {
|
||||
ctx.appliesTo(call) and result = total_call_cost(call)
|
||||
}
|
||||
|
||||
pragma[noinline]
|
||||
private int total_cost(CallNode call, PointsToContext ctx) {
|
||||
ctx.appliesTo(call) and
|
||||
result = total_call_cost(call) + context_cost(ctx)
|
||||
result = relevant_call_cost(ctx, call) + context_cost(ctx)
|
||||
}
|
||||
|
||||
cached
|
||||
|
||||
@@ -82,21 +82,7 @@ private DataFlow::LocalSourceNode re_flag_tracker(string flag_name, DataFlow::Ty
|
||||
result.asCfgNode() = binop
|
||||
)
|
||||
or
|
||||
// Due to bad performance when using normal setup with `re_flag_tracker(t2, attr_name).track(t2, t)`
|
||||
// we have inlined that code and forced a join
|
||||
exists(DataFlow::TypeTracker t2 |
|
||||
exists(DataFlow::StepSummary summary |
|
||||
re_flag_tracker_first_join(t2, flag_name, result, summary) and
|
||||
t = t2.append(summary)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate re_flag_tracker_first_join(
|
||||
DataFlow::TypeTracker t2, string flag_name, DataFlow::Node res, DataFlow::StepSummary summary
|
||||
) {
|
||||
DataFlow::StepSummary::step(re_flag_tracker(flag_name, t2), res, summary)
|
||||
exists(DataFlow::TypeTracker t2 | result = re_flag_tracker(flag_name, t2).track(t2, t))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
7
python/ql/test/3/library-tests/modules/entry_point/hash_bang/main.py
Executable file
7
python/ql/test/3/library-tests/modules/entry_point/hash_bang/main.py
Executable file
@@ -0,0 +1,7 @@
|
||||
#! /usr/bin/python3
|
||||
print(__file__)
|
||||
import module
|
||||
import package
|
||||
import namespace_package
|
||||
import namespace_package.namespace_package_main
|
||||
print(module.message)
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
message = "Hello world!"
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
import namespace_package.namespace_package_module
|
||||
@@ -0,0 +1 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
from . import package_main
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
from . import package_module
|
||||
@@ -0,0 +1 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
@@ -0,0 +1,14 @@
|
||||
| module | hash_bang/module.py:0:0:0:0 | Module module |
|
||||
| module | name_main/module.py:0:0:0:0 | Module module |
|
||||
| package | hash_bang/package:0:0:0:0 | Package package |
|
||||
| package | name_main/package:0:0:0:0 | Package package |
|
||||
| package | no_py_extension/package:0:0:0:0 | Package package |
|
||||
| package.__init__ | hash_bang/package/__init__.py:0:0:0:0 | Module package.__init__ |
|
||||
| package.__init__ | name_main/package/__init__.py:0:0:0:0 | Module package.__init__ |
|
||||
| package.__init__ | no_py_extension/package/__init__.py:0:0:0:0 | Module package.__init__ |
|
||||
| package.package_main | hash_bang/package/package_main.py:0:0:0:0 | Module package.package_main |
|
||||
| package.package_main | name_main/package/package_main.py:0:0:0:0 | Module package.package_main |
|
||||
| package.package_main | no_py_extension/package/package_main.py:0:0:0:0 | Module package.package_main |
|
||||
| package.package_module | hash_bang/package/package_module.py:0:0:0:0 | Module package.package_module |
|
||||
| package.package_module | name_main/package/package_module.py:0:0:0:0 | Module package.package_module |
|
||||
| package.package_module | no_py_extension/package/package_module.py:0:0:0:0 | Module package.package_module |
|
||||
@@ -0,0 +1,4 @@
|
||||
import python
|
||||
|
||||
from Module m
|
||||
select m.getName(), m
|
||||
8
python/ql/test/3/library-tests/modules/entry_point/name_main/main.py
Executable file
8
python/ql/test/3/library-tests/modules/entry_point/name_main/main.py
Executable file
@@ -0,0 +1,8 @@
|
||||
print(__file__)
|
||||
import module
|
||||
import package
|
||||
import namespace_package
|
||||
import namespace_package.namespace_package_main
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(module.message)
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
message = "Hello world!"
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
import namespace_package.namespace_package_module
|
||||
@@ -0,0 +1 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
from . import package_main
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
from . import package_module
|
||||
@@ -0,0 +1 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
@@ -0,0 +1,6 @@
|
||||
print(__file__)
|
||||
import module
|
||||
import package
|
||||
import namespace_package
|
||||
import namespace_package.namespace_package_main
|
||||
print(module.message)
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
message = "Hello world!"
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
import namespace_package.namespace_package_module
|
||||
@@ -0,0 +1 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
from . import package_main
|
||||
@@ -0,0 +1,2 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
from . import package_module
|
||||
@@ -0,0 +1 @@
|
||||
print(__file__.split("entry_point")[1])
|
||||
@@ -0,0 +1 @@
|
||||
semmle-extractor-options: --lang=3 --path bogus -R . --filter=include:**/*.secretpy
|
||||
@@ -9,7 +9,11 @@ class ApiUseTest extends InlineExpectationsTest {
|
||||
override string getARelevantTag() { result = "use" }
|
||||
|
||||
private predicate relevant_node(API::Node a, DataFlow::Node n, Location l) {
|
||||
n = a.getAUse() and l = n.getLocation()
|
||||
n = a.getAUse() and
|
||||
l = n.getLocation() and
|
||||
// Module variable nodes have no suitable location, so it's best to simply exclude them entirely
|
||||
// from the inline tests.
|
||||
not n instanceof DataFlow::ModuleVariableNode
|
||||
}
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
failures
|
||||
@@ -1,4 +1,4 @@
|
||||
import experimental.dataflow.tainttracking.TestTaintLib
|
||||
import experimental.meta.InlineTaintTest
|
||||
import semmle.python.dataflow.new.BarrierGuards
|
||||
|
||||
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {
|
||||
@@ -1,27 +0,0 @@
|
||||
| test_string_const_compare.py:16 | ok | test_eq | ts |
|
||||
| test_string_const_compare.py:18 | ok | test_eq | ts |
|
||||
| test_string_const_compare.py:20 | ok | test_eq | ts |
|
||||
| test_string_const_compare.py:27 | ok | test_eq_unsafe | ts |
|
||||
| test_string_const_compare.py:29 | ok | test_eq_unsafe | ts |
|
||||
| test_string_const_compare.py:35 | fail | test_eq_with_or | ts |
|
||||
| test_string_const_compare.py:37 | ok | test_eq_with_or | ts |
|
||||
| test_string_const_compare.py:43 | ok | test_non_eq1 | ts |
|
||||
| test_string_const_compare.py:45 | ok | test_non_eq1 | ts |
|
||||
| test_string_const_compare.py:51 | ok | test_non_eq2 | ts |
|
||||
| test_string_const_compare.py:53 | fail | test_non_eq2 | ts |
|
||||
| test_string_const_compare.py:59 | ok | test_in_list | ts |
|
||||
| test_string_const_compare.py:61 | ok | test_in_list | ts |
|
||||
| test_string_const_compare.py:67 | ok | test_in_tuple | ts |
|
||||
| test_string_const_compare.py:69 | ok | test_in_tuple | ts |
|
||||
| test_string_const_compare.py:75 | ok | test_in_set | ts |
|
||||
| test_string_const_compare.py:77 | ok | test_in_set | ts |
|
||||
| test_string_const_compare.py:83 | ok | test_in_unsafe1 | ts |
|
||||
| test_string_const_compare.py:85 | ok | test_in_unsafe1 | ts |
|
||||
| test_string_const_compare.py:91 | ok | test_in_unsafe2 | ts |
|
||||
| test_string_const_compare.py:93 | ok | test_in_unsafe2 | ts |
|
||||
| test_string_const_compare.py:99 | ok | test_not_in1 | ts |
|
||||
| test_string_const_compare.py:101 | ok | test_not_in1 | ts |
|
||||
| test_string_const_compare.py:107 | ok | test_not_in2 | ts |
|
||||
| test_string_const_compare.py:109 | fail | test_not_in2 | ts |
|
||||
| test_string_const_compare.py:119 | fail | test_eq_thorugh_func | ts |
|
||||
| test_string_const_compare.py:121 | ok | test_eq_thorugh_func | ts |
|
||||
@@ -15,32 +15,32 @@ def test_eq():
|
||||
if ts == "safe":
|
||||
ensure_not_tainted(ts)
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
# ts should still be tainted after exiting the if block
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_eq_unsafe(x="foo"):
|
||||
"""This test-case might seem strange, but it was a FP in our old points-to based analysis."""
|
||||
ts = TAINTED_STRING
|
||||
if ts == ts:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
if ts == x:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_eq_with_or():
|
||||
ts = TAINTED_STRING
|
||||
if ts == "safe" or ts == "also_safe":
|
||||
ensure_not_tainted(ts)
|
||||
ensure_not_tainted(ts) # $ SPURIOUS: tainted
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_non_eq1():
|
||||
ts = TAINTED_STRING
|
||||
if ts != "safe":
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(ts)
|
||||
|
||||
@@ -48,9 +48,9 @@ def test_non_eq1():
|
||||
def test_non_eq2():
|
||||
ts = TAINTED_STRING
|
||||
if not ts == "safe":
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(ts)
|
||||
ensure_not_tainted(ts) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
def test_in_list():
|
||||
@@ -58,7 +58,7 @@ def test_in_list():
|
||||
if ts in ["safe", "also_safe"]:
|
||||
ensure_not_tainted(ts)
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_in_tuple():
|
||||
@@ -66,7 +66,7 @@ def test_in_tuple():
|
||||
if ts in ("safe", "also_safe"):
|
||||
ensure_not_tainted(ts)
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_in_set():
|
||||
@@ -74,29 +74,29 @@ def test_in_set():
|
||||
if ts in {"safe", "also_safe"}:
|
||||
ensure_not_tainted(ts)
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_in_unsafe1(xs):
|
||||
ts = TAINTED_STRING
|
||||
if ts in xs:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_in_unsafe2(x):
|
||||
ts = TAINTED_STRING
|
||||
if ts in ["safe", x]:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
def test_not_in1():
|
||||
ts = TAINTED_STRING
|
||||
if ts not in ["safe", "also_safe"]:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(ts)
|
||||
|
||||
@@ -104,9 +104,9 @@ def test_not_in1():
|
||||
def test_not_in2():
|
||||
ts = TAINTED_STRING
|
||||
if not ts in ["safe", "also_safe"]:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(ts)
|
||||
ensure_not_tainted(ts) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
def is_safe(x):
|
||||
@@ -116,9 +116,9 @@ def is_safe(x):
|
||||
def test_eq_thorugh_func():
|
||||
ts = TAINTED_STRING
|
||||
if is_safe(ts):
|
||||
ensure_not_tainted(ts)
|
||||
ensure_not_tainted(ts) # $ SPURIOUS: tainted
|
||||
else:
|
||||
ensure_tainted(ts)
|
||||
ensure_tainted(ts) # $ tainted
|
||||
|
||||
|
||||
# Make tests runable
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
failures
|
||||
isSanitizer
|
||||
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
|
||||
| TestTaintTrackingConfiguration | test.py:50:10:50:29 | ControlFlowNode for emulated_escaping() |
|
||||
isSanitizerGuard
|
||||
| TestTaintTrackingConfiguration | test.py:35:8:35:26 | ControlFlowNode for emulated_is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:29:8:29:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:44:8:44:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:52:12:52:21 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:72:8:72:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:80:12:80:21 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:104:8:104:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:127:12:127:21 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:132:16:132:25 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:137:20:137:29 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_reference.py:30:8:30:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_reference.py:40:8:40:25 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_reference.py:55:8:55:21 | ControlFlowNode for is_safe() |
|
||||
@@ -1,4 +1,4 @@
|
||||
import experimental.dataflow.tainttracking.TestTaintLib
|
||||
import experimental.meta.InlineTaintTest
|
||||
|
||||
class IsSafeCheck extends DataFlow::BarrierGuard {
|
||||
IsSafeCheck() {
|
||||
@@ -1,61 +0,0 @@
|
||||
test_taint
|
||||
| test.py:22 | ok | test_custom_sanitizer | s |
|
||||
| test.py:36 | ok | test_custom_sanitizer_guard | s |
|
||||
| test.py:38 | ok | test_custom_sanitizer_guard | s |
|
||||
| test.py:40 | ok | test_custom_sanitizer_guard | s |
|
||||
| test.py:51 | ok | test_escape | s2 |
|
||||
| test_logical.py:30 | ok | test_basic | s |
|
||||
| test_logical.py:32 | ok | test_basic | s |
|
||||
| test_logical.py:35 | ok | test_basic | s |
|
||||
| test_logical.py:37 | fail | test_basic | s |
|
||||
| test_logical.py:45 | ok | test_or | s |
|
||||
| test_logical.py:47 | ok | test_or | s |
|
||||
| test_logical.py:51 | ok | test_or | s |
|
||||
| test_logical.py:53 | ok | test_or | s |
|
||||
| test_logical.py:57 | ok | test_or | s |
|
||||
| test_logical.py:59 | ok | test_or | s |
|
||||
| test_logical.py:67 | ok | test_and | s |
|
||||
| test_logical.py:69 | ok | test_and | s |
|
||||
| test_logical.py:73 | ok | test_and | s |
|
||||
| test_logical.py:75 | ok | test_and | s |
|
||||
| test_logical.py:79 | ok | test_and | s |
|
||||
| test_logical.py:81 | fail | test_and | s |
|
||||
| test_logical.py:89 | fail | test_tricky | s |
|
||||
| test_logical.py:93 | fail | test_tricky | s_ |
|
||||
| test_logical.py:100 | fail | test_nesting_not | s |
|
||||
| test_logical.py:102 | ok | test_nesting_not | s |
|
||||
| test_logical.py:105 | ok | test_nesting_not | s |
|
||||
| test_logical.py:107 | fail | test_nesting_not | s |
|
||||
| test_logical.py:116 | ok | test_nesting_not_with_and_true | s |
|
||||
| test_logical.py:118 | ok | test_nesting_not_with_and_true | s |
|
||||
| test_logical.py:121 | ok | test_nesting_not_with_and_true | s |
|
||||
| test_logical.py:123 | ok | test_nesting_not_with_and_true | s |
|
||||
| test_logical.py:126 | ok | test_nesting_not_with_and_true | s |
|
||||
| test_logical.py:128 | ok | test_nesting_not_with_and_true | s |
|
||||
| test_logical.py:137 | fail | test_with_return | s |
|
||||
| test_logical.py:146 | fail | test_with_exception | s |
|
||||
| test_reference.py:31 | fail | test_basic | s2 |
|
||||
| test_reference.py:31 | ok | test_basic | s |
|
||||
| test_reference.py:33 | ok | test_basic | s |
|
||||
| test_reference.py:33 | ok | test_basic | s2 |
|
||||
| test_reference.py:41 | fail | test_identical_call | s.strip() |
|
||||
| test_reference.py:43 | ok | test_identical_call | s.strip() |
|
||||
| test_reference.py:56 | fail | test_class_attribute_access | c.foo |
|
||||
| test_reference.py:58 | ok | test_class_attribute_access | c.foo |
|
||||
isSanitizer
|
||||
| TestTaintTrackingConfiguration | test.py:21:39:21:39 | ControlFlowNode for s |
|
||||
| TestTaintTrackingConfiguration | test.py:50:10:50:29 | ControlFlowNode for emulated_escaping() |
|
||||
isSanitizerGuard
|
||||
| TestTaintTrackingConfiguration | test.py:35:8:35:26 | ControlFlowNode for emulated_is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:29:8:29:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:44:8:44:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:50:12:50:21 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:66:8:66:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:72:12:72:21 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:92:8:92:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:115:12:115:21 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:120:16:120:25 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_logical.py:125:20:125:29 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_reference.py:30:8:30:17 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_reference.py:40:8:40:25 | ControlFlowNode for is_safe() |
|
||||
| TestTaintTrackingConfiguration | test_reference.py:55:8:55:21 | ControlFlowNode for is_safe() |
|
||||
@@ -35,9 +35,9 @@ def test_custom_sanitizer_guard():
|
||||
if emulated_is_safe(s):
|
||||
ensure_not_tainted(s)
|
||||
s = TAINTED_STRING
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
|
||||
def emulated_escaping(arg):
|
||||
|
||||
@@ -29,12 +29,12 @@ def test_basic():
|
||||
if is_safe(s):
|
||||
ensure_not_tainted(s)
|
||||
else:
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
if not is_safe(s):
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(s)
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
def test_or():
|
||||
@@ -42,21 +42,27 @@ def test_or():
|
||||
|
||||
# x or y
|
||||
if is_safe(s) or random_choice():
|
||||
ensure_tainted(s) # might be tainted
|
||||
# might be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_tainted(s) # must be tainted
|
||||
# must be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
# not (x or y)
|
||||
if not(is_safe(s) or random_choice()):
|
||||
ensure_tainted(s) # must be tainted
|
||||
# must be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_tainted(s) # might be tainted
|
||||
# might be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
# not (x or y) == not x and not y [de Morgan's laws]
|
||||
if not is_safe(s) and not random_choice():
|
||||
ensure_tainted(s) # must be tainted
|
||||
# must be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_tainted(s) # might be tainted
|
||||
# might be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
|
||||
def test_and():
|
||||
@@ -64,21 +70,27 @@ def test_and():
|
||||
|
||||
# x and y
|
||||
if is_safe(s) and random_choice():
|
||||
ensure_not_tainted(s) # must not be tainted
|
||||
# cannot be tainted
|
||||
ensure_not_tainted(s)
|
||||
else:
|
||||
ensure_tainted(s) # might be tainted
|
||||
# might be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
# not (x and y)
|
||||
if not(is_safe(s) and random_choice()):
|
||||
ensure_tainted(s) # might be tainted
|
||||
# might be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
# cannot be tainted
|
||||
ensure_not_tainted(s)
|
||||
|
||||
# not (x and y) == not x or not y [de Morgan's laws]
|
||||
if not is_safe(s) or not random_choice():
|
||||
ensure_tainted(s) # might be tainted
|
||||
# might be tainted
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(s)
|
||||
# cannot be tainted
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
def test_tricky():
|
||||
@@ -86,25 +98,25 @@ def test_tricky():
|
||||
|
||||
x = is_safe(s)
|
||||
if x:
|
||||
ensure_not_tainted(s) # FP
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
|
||||
s_ = s
|
||||
if is_safe(s):
|
||||
ensure_not_tainted(s_) # FP
|
||||
ensure_not_tainted(s_) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
def test_nesting_not():
|
||||
s = TAINTED_STRING
|
||||
|
||||
if not(not(is_safe(s))):
|
||||
ensure_not_tainted(s)
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
else:
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
if not(not(not(is_safe(s)))):
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(s)
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
# Adding `and True` makes the sanitizer trigger when it would otherwise not. See output in
|
||||
@@ -113,17 +125,17 @@ def test_nesting_not_with_and_true():
|
||||
s = TAINTED_STRING
|
||||
|
||||
if not(is_safe(s) and True):
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(s)
|
||||
|
||||
if not(not(is_safe(s) and True)):
|
||||
ensure_not_tainted(s)
|
||||
else:
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
|
||||
if not(not(not(is_safe(s) and True))):
|
||||
ensure_tainted(s)
|
||||
ensure_tainted(s) # $ tainted
|
||||
else:
|
||||
ensure_not_tainted(s)
|
||||
|
||||
@@ -134,7 +146,7 @@ def test_with_return():
|
||||
if not is_safe(s):
|
||||
return
|
||||
|
||||
ensure_not_tainted(s)
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
|
||||
|
||||
def test_with_exception():
|
||||
@@ -143,7 +155,7 @@ def test_with_exception():
|
||||
if not is_safe(s):
|
||||
raise Exception("unsafe")
|
||||
|
||||
ensure_not_tainted(s)
|
||||
ensure_not_tainted(s) # $ SPURIOUS: tainted
|
||||
|
||||
# Make tests runable
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ def test_basic():
|
||||
s2 = s
|
||||
|
||||
if is_safe(s):
|
||||
ensure_not_tainted(s, s2)
|
||||
ensure_not_tainted(s, s2) # $ SPURIOUS: tainted
|
||||
else:
|
||||
ensure_tainted(s, s2)
|
||||
ensure_tainted(s, s2) # $ tainted
|
||||
|
||||
|
||||
def test_identical_call():
|
||||
@@ -38,9 +38,9 @@ def test_identical_call():
|
||||
s = TAINTED_STRING
|
||||
|
||||
if is_safe(s.strip()):
|
||||
ensure_not_tainted(s.strip())
|
||||
ensure_not_tainted(s.strip()) # $ SPURIOUS: tainted
|
||||
else:
|
||||
ensure_tainted(s.strip())
|
||||
ensure_tainted(s.strip()) # $ tainted
|
||||
|
||||
|
||||
class C(object):
|
||||
@@ -53,9 +53,9 @@ def test_class_attribute_access():
|
||||
c = C(s)
|
||||
|
||||
if is_safe(c.foo):
|
||||
ensure_not_tainted(c.foo)
|
||||
ensure_not_tainted(c.foo) # $ SPURIOUS: tainted
|
||||
else:
|
||||
ensure_tainted(c.foo)
|
||||
ensure_tainted(c.foo) # $ tainted
|
||||
|
||||
|
||||
# Make tests runable
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
argumentToEnsureNotTaintedNotMarkedAsSpurious
|
||||
untaintedArgumentToEnsureTaintedNotMarkedAsMissing
|
||||
failures
|
||||
@@ -0,0 +1 @@
|
||||
import experimental.meta.InlineTaintTest
|
||||
@@ -1,34 +0,0 @@
|
||||
| test_collections.py:16 | ok | test_access | tainted_list.copy() |
|
||||
| test_collections.py:24 | ok | list_clear | tainted_list |
|
||||
| test_collections.py:27 | fail | list_clear | tainted_list |
|
||||
| test_pathlib.py:26 | fail | test_basic | tainted_path |
|
||||
| test_pathlib.py:28 | fail | test_basic | tainted_pure_path |
|
||||
| test_pathlib.py:29 | fail | test_basic | tainted_pure_posix_path |
|
||||
| test_pathlib.py:30 | fail | test_basic | tainted_pure_windows_path |
|
||||
| test_pathlib.py:32 | fail | test_basic | BinaryExpr |
|
||||
| test_pathlib.py:33 | fail | test_basic | BinaryExpr |
|
||||
| test_pathlib.py:35 | fail | test_basic | tainted_path.joinpath(..) |
|
||||
| test_pathlib.py:36 | fail | test_basic | pathlib.Path(..).joinpath(..) |
|
||||
| test_pathlib.py:37 | fail | test_basic | pathlib.Path(..).joinpath(..) |
|
||||
| test_pathlib.py:39 | fail | test_basic | str(..) |
|
||||
| test_pathlib.py:49 | fail | test_basic | tainted_posix_path |
|
||||
| test_pathlib.py:55 | fail | test_basic | tainted_windows_path |
|
||||
| test_string.py:17 | ok | str_methods | ts.casefold() |
|
||||
| test_string.py:19 | ok | str_methods | ts.format_map(..) |
|
||||
| test_string.py:20 | ok | str_methods | "{unsafe}".format_map(..) |
|
||||
| test_string.py:31 | ok | binary_decode_encode | base64.a85encode(..) |
|
||||
| test_string.py:32 | ok | binary_decode_encode | base64.a85decode(..) |
|
||||
| test_string.py:35 | ok | binary_decode_encode | base64.b85encode(..) |
|
||||
| test_string.py:36 | ok | binary_decode_encode | base64.b85decode(..) |
|
||||
| test_string.py:39 | ok | binary_decode_encode | base64.encodebytes(..) |
|
||||
| test_string.py:40 | ok | binary_decode_encode | base64.decodebytes(..) |
|
||||
| test_string.py:48 | ok | f_strings | Fstring |
|
||||
| test_unpacking.py:18 | ok | extended_unpacking | first |
|
||||
| test_unpacking.py:18 | ok | extended_unpacking | last |
|
||||
| test_unpacking.py:18 | ok | extended_unpacking | rest |
|
||||
| test_unpacking.py:23 | ok | also_allowed | a |
|
||||
| test_unpacking.py:31 | ok | also_allowed | b |
|
||||
| test_unpacking.py:31 | ok | also_allowed | c |
|
||||
| test_unpacking.py:39 | ok | nested | x |
|
||||
| test_unpacking.py:39 | ok | nested | xs |
|
||||
| test_unpacking.py:39 | ok | nested | ys |
|
||||
@@ -1 +0,0 @@
|
||||
import experimental.dataflow.tainttracking.TestTaintLib
|
||||
@@ -13,7 +13,7 @@ def test_access():
|
||||
tainted_list = TAINTED_LIST
|
||||
|
||||
ensure_tainted(
|
||||
tainted_list.copy(),
|
||||
tainted_list.copy(), # $ tainted
|
||||
)
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@ def list_clear():
|
||||
tainted_string = TAINTED_STRING
|
||||
tainted_list = [tainted_string]
|
||||
|
||||
ensure_tainted(tainted_list)
|
||||
ensure_tainted(tainted_list) # $ tainted
|
||||
|
||||
tainted_list.clear()
|
||||
ensure_not_tainted(tainted_list)
|
||||
ensure_not_tainted(tainted_list) # $ SPURIOUS: tainted
|
||||
|
||||
# Make tests runable
|
||||
|
||||
|
||||
@@ -23,20 +23,20 @@ def test_basic():
|
||||
tainted_pure_windows_path = pathlib.PureWindowsPath(ts)
|
||||
|
||||
ensure_tainted(
|
||||
tainted_path,
|
||||
tainted_path, # $ tainted
|
||||
|
||||
tainted_pure_path,
|
||||
tainted_pure_posix_path,
|
||||
tainted_pure_windows_path,
|
||||
tainted_pure_path, # $ tainted
|
||||
tainted_pure_posix_path, # $ tainted
|
||||
tainted_pure_windows_path, # $ tainted
|
||||
|
||||
pathlib.Path("foo") / ts,
|
||||
ts / pathlib.Path("foo"),
|
||||
pathlib.Path("foo") / ts, # $ tainted
|
||||
ts / pathlib.Path("foo"), # $ tainted
|
||||
|
||||
tainted_path.joinpath("foo", "bar"),
|
||||
pathlib.Path("foo").joinpath(tainted_path, "bar"),
|
||||
pathlib.Path("foo").joinpath("bar", tainted_path),
|
||||
tainted_path.joinpath("foo", "bar"), # $ tainted
|
||||
pathlib.Path("foo").joinpath(tainted_path, "bar"), # $ tainted
|
||||
pathlib.Path("foo").joinpath("bar", tainted_path), # $ tainted
|
||||
|
||||
str(tainted_path),
|
||||
str(tainted_path), # $ tainted
|
||||
|
||||
# TODO: Tainted methods and attributes
|
||||
# https://docs.python.org/3.8/library/pathlib.html#methods-and-properties
|
||||
@@ -46,13 +46,13 @@ def test_basic():
|
||||
tainted_posix_path = pathlib.PosixPath(ts)
|
||||
|
||||
ensure_tainted(
|
||||
tainted_posix_path,
|
||||
tainted_posix_path, # $ tainted
|
||||
)
|
||||
|
||||
if os.name == "nt":
|
||||
tainted_windows_path = pathlib.WindowsPath(ts)
|
||||
ensure_tainted(
|
||||
tainted_windows_path,
|
||||
tainted_windows_path, # $ tainted
|
||||
)
|
||||
|
||||
# Make tests runable
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user