Merge branch 'github:main' into jorgectf/python/ldapimproperauth

This commit is contained in:
Jorge
2021-05-07 20:46:09 +02:00
909 changed files with 30301 additions and 8220 deletions

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
message = "Hello world!"

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
import namespace_package.namespace_package_module

View File

@@ -0,0 +1 @@
print(__file__.split("entry_point")[1])

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
from . import package_main

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
from . import package_module

View File

@@ -0,0 +1 @@
print(__file__.split("entry_point")[1])

View File

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

View File

@@ -0,0 +1,4 @@
import python
from Module m
select m.getName(), m

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

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
message = "Hello world!"

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
import namespace_package.namespace_package_module

View File

@@ -0,0 +1 @@
print(__file__.split("entry_point")[1])

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
from . import package_main

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
from . import package_module

View File

@@ -0,0 +1 @@
print(__file__.split("entry_point")[1])

View File

@@ -0,0 +1,6 @@
print(__file__)
import module
import package
import namespace_package
import namespace_package.namespace_package_main
print(module.message)

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
message = "Hello world!"

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
import namespace_package.namespace_package_module

View File

@@ -0,0 +1 @@
print(__file__.split("entry_point")[1])

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
from . import package_main

View File

@@ -0,0 +1,2 @@
print(__file__.split("entry_point")[1])
from . import package_module

View File

@@ -0,0 +1 @@
print(__file__.split("entry_point")[1])

View File

@@ -0,0 +1 @@
semmle-extractor-options: --lang=3 --path bogus -R . --filter=include:**/*.secretpy

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import experimental.dataflow.tainttracking.TestTaintLib
import experimental.meta.InlineTaintTest
import semmle.python.dataflow.new.BarrierGuards
class CustomSanitizerOverrides extends TestTaintTrackingConfiguration {

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
import experimental.dataflow.tainttracking.TestTaintLib
import experimental.meta.InlineTaintTest
class IsSafeCheck extends DataFlow::BarrierGuard {
IsSafeCheck() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
import experimental.dataflow.tainttracking.TestTaintLib

View File

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

View File

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