-
-
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
deleted file mode 100644
index 2d78074db7a..00000000000
--- a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * @id test-id
- * @name Test
- * @description Test description
- * @tags test
- */
-
-import csharp
-import semmle.code.csharp.frameworks.ServiceStack::ServiceStackSQL
-
-from TaintTrackingConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
-where c.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "Query might include code from $@.", source,
- ("this " + getSourceType(source.getNode()))
-
diff --git a/csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs b/csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs
deleted file mode 100644
index 5bb53450a9e..00000000000
--- a/csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Web;
-using ServiceStack;
-using ServiceStack.Web;
-using SqlServer.ServiceModel;
-
-namespace SqlServer.ServiceInterface
-{
- public class MyServices : Service
- {
- // public object Any(Hello request)
- // {
- // return new HelloResponse { Result = "Hello, {0}!".Fmt(request.Name) };
- // }
-
- //5. Using a Request or Response Filter
- [AddHeader(ContentType = "text/plain")]
- [AddHeader(ContentDisposition = "attachment; filename=hello.txt")]
- public string Get(Hello request)
- {
- return $"Hello, {request.Name}!";
- }
-
- //6. Returning responses in a decorated HttpResult
- public object Any(Hello request)
- {
- return new HttpResult($"Hello, {request.Name}!") {
- ContentType = MimeTypes.PlainText,
- Headers = {
- [HttpHeaders.ContentDisposition] = "attachment; filename=\"hello.txt\""
- }
- };
- }
- }
-}
\ No newline at end of file
From 563dc62c33c9ef9e412c9202b2a1654bf7353c25 Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Fri, 18 Dec 2020 08:23:27 -0800
Subject: [PATCH 019/459] Improve qldoc for ServiceStack.qll
---
.../code/csharp/frameworks/ServiceStack.qll | 32 +++++++++++++------
1 file changed, 22 insertions(+), 10 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 91169fd318e..fd3c7217b43 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -1,3 +1,10 @@
+/**
+ * General modelling of ServiceStack framework including separate modules for:
+ * - flow sources
+ * - SQLi sinks
+ * - XSS sinks
+ */
+
import csharp
/** Provides definitions related to the namespace `ServiceStack`. */
@@ -52,6 +59,13 @@ module Sources {
)
}
+ /**
+ * Remote flow sources for ServiceStack
+ *
+ * Assumes all nested fields/properties on request DTOs are tainted, which is
+ * an overapproximation and may lead to FPs depending on how Service Stack app
+ * is configured.
+ */
class ServiceStackSource extends RemoteFlowSource {
ServiceStackSource() {
// Parameters are sources. In practice only interesting when they are string/primitive typed.
@@ -70,11 +84,12 @@ module Sources {
}
}
-/** SQL sinks for the ServiceStack framework */
+/** SQLi support for the ServiceStack framework */
module SQL {
private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
+ /** SQLi sinks for ServiceStack */
class ServiceStackSink extends Sink {
ServiceStackSink() {
exists(MethodCall mc, Method m, int p |
@@ -116,24 +131,21 @@ module SQL {
}
}
-/** XSS sinks for the ServiceStack framework */
+/** XSS support for ServiceStack framework */
module XSS {
private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.XSS::XSS
- class XssSinks extends Sink {
- XssSinks() { this.asExpr() instanceof XssExpr }
- }
-
- class XssExpr extends Expr {
- XssExpr() {
+ /** XSS sinks for ServiceStack */
+ class XssSink extends Sink {
+ XssSink() {
exists(ServiceClass service, ReturnStmt r |
- this = r.getExpr() and
+ this.asExpr() = r.getExpr() and
r.getEnclosingCallable() = service.getARequestMethod()
) or
exists(ObjectCreation oc |
oc.getType().hasQualifiedName("ServiceStack.HttpResult") and
- this = oc.getArgument(0)
+ this.asExpr() = oc.getArgument(0)
)
}
}
From 059d6b0e0fd523c79c9d89a481c7e89262678a5f Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Fri, 18 Dec 2020 08:34:06 -0800
Subject: [PATCH 020/459] Fix warning in ServiceStack.qll
---
.../code/csharp/frameworks/ServiceStack.qll | 47 ++++++++-----------
1 file changed, 20 insertions(+), 27 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index fd3c7217b43..07a771f7183 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -7,41 +7,36 @@
import csharp
-/** Provides definitions related to the namespace `ServiceStack`. */
-module ServiceStack {
+/** A class representing a Service */
+class ServiceClass extends Class {
+ ServiceClass() { this.getBaseClass+().getQualifiedName()="ServiceStack.Service" }
- /** A class representing a Service */
- class ServiceClass extends Class {
- ServiceClass() { this.getBaseClass+().getQualifiedName()="ServiceStack.Service" }
-
- /** Get a method that handles incoming requests */
- Method getARequestMethod() {
- result = this.getAMethod(["Post", "Get", "Put", "Delete", "Any", "Option", "Head"])
- }
+ /** Get a method that handles incoming requests */
+ Method getARequestMethod() {
+ result = this.getAMethod(["Post", "Get", "Put", "Delete", "Any", "Option", "Head"])
}
+}
- /** Top-level Request DTO types */
- class RequestDTO extends Class {
- RequestDTO() {
- this.getABaseInterface().getQualifiedName() = ["ServiceStack.IReturn", "ServieStack.IReturnVoid"]
- }
+/** Top-level Request DTO types */
+class RequestDTO extends Class {
+ RequestDTO() {
+ this.getABaseInterface().getQualifiedName() = ["ServiceStack.IReturn", "ServieStack.IReturnVoid"]
}
+}
- /** Top-level Response DTO types */
- class ResponseDTO extends Class {
- ResponseDTO() {
- exists(RequestDTO req, ConstructedGeneric respInterface |
- req.getABaseInterface() = respInterface and
- respInterface.getUndecoratedName() = "IReturn" and
- respInterface.getATypeArgument() = this
- )
- }
+/** Top-level Response DTO types */
+class ResponseDTO extends Class {
+ ResponseDTO() {
+ exists(RequestDTO req, ConstructedGeneric respInterface |
+ req.getABaseInterface() = respInterface and
+ respInterface.getUndecoratedName() = "IReturn" and
+ respInterface.getATypeArgument() = this
+ )
}
}
/** Flow sources for the ServiceStack framework */
module Sources {
- private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.flowsources.Remote
private import semmle.code.csharp.commons.Collections
@@ -86,7 +81,6 @@ module Sources {
/** SQLi support for the ServiceStack framework */
module SQL {
- private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
/** SQLi sinks for ServiceStack */
@@ -133,7 +127,6 @@ module SQL {
/** XSS support for ServiceStack framework */
module XSS {
- private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.XSS::XSS
/** XSS sinks for ServiceStack */
From 7de9214c992f068ccfcdc25bceac48896c14862e Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 18 Mar 2021 17:41:34 +0100
Subject: [PATCH 021/459] Upload LDAP Insecure authentication query and tests
---
.../src/Security/CWE-090/LDAPInsecureAuth.ql | 191 ++++++++++++++++++
.../src/Security/CWE-090/tests/2_private.py | 66 ++++++
.../ql/src/Security/CWE-090/tests/2_remote.py | 66 ++++++
.../src/Security/CWE-090/tests/3_private.py | 105 ++++++++++
.../ql/src/Security/CWE-090/tests/3_remote.py | 146 +++++++++++++
5 files changed, 574 insertions(+)
create mode 100644 python/ql/src/Security/CWE-090/LDAPInsecureAuth.ql
create mode 100644 python/ql/src/Security/CWE-090/tests/2_private.py
create mode 100644 python/ql/src/Security/CWE-090/tests/2_remote.py
create mode 100644 python/ql/src/Security/CWE-090/tests/3_private.py
create mode 100644 python/ql/src/Security/CWE-090/tests/3_remote.py
diff --git a/python/ql/src/Security/CWE-090/LDAPInsecureAuth.ql b/python/ql/src/Security/CWE-090/LDAPInsecureAuth.ql
new file mode 100644
index 00000000000..b156cc2b036
--- /dev/null
+++ b/python/ql/src/Security/CWE-090/LDAPInsecureAuth.ql
@@ -0,0 +1,191 @@
+/**
+ * @name Python Insecure LDAP Authentication
+ * @description Python LDAP Insecure LDAP Authentication
+ * @kind path-problem
+ * @problem.severity error
+ * @id python/insecure-ldap-auth
+ * @tags experimental
+ * security
+ * external/cwe/cwe-090
+ */
+
+import python
+import semmle.python.dataflow.new.RemoteFlowSources
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.dataflow.new.internal.TaintTrackingPublic
+import DataFlow::PathGraph
+
+class FalseArg extends ControlFlowNode {
+ FalseArg() { this.getNode().(Expr).(BooleanLiteral) instanceof False }
+}
+
+// From luchua-bc's Insecure LDAP authentication in Java (to reduce false positives)
+string getFullHostRegex() { result = "(?i)ldap://[\\[a-zA-Z0-9].*" }
+
+string getSchemaRegex() { result = "(?i)ldap(://)?" }
+
+string getPrivateHostRegex() {
+ result =
+ "(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?"
+}
+
+// "ldap://somethingon.theinternet.com"
+class LDAPFullHost extends StrConst {
+ LDAPFullHost() {
+ exists(string s |
+ s = this.getText() and
+ s.regexpMatch(getFullHostRegex()) and
+ not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex()) // No need to check for ldaps, would be SSL by default.
+ )
+ }
+}
+
+class LDAPSchema extends StrConst {
+ LDAPSchema() { this.getText().regexpMatch(getSchemaRegex()) }
+}
+
+class LDAPPrivateHost extends StrConst {
+ LDAPPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
+}
+
+predicate concatAndCompareAgainstFullHostRegex(Expr schema, Expr host) {
+ schema instanceof LDAPSchema and
+ not host instanceof LDAPPrivateHost and
+ exists(string full_host |
+ full_host = schema.(StrConst).getText() + host.(StrConst).getText() and
+ full_host.regexpMatch(getFullHostRegex())
+ )
+}
+
+// "ldap://" + "somethingon.theinternet.com"
+class LDAPBothStrings extends BinaryExpr {
+ LDAPBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
+}
+
+// schema + host
+class LDAPBothVar extends BinaryExpr {
+ LDAPBothVar() {
+ exists(SsaVariable schemaVar, SsaVariable hostVar |
+ this.getLeft() = schemaVar.getVariable().getALoad() and // getAUse is incompatible with Expr
+ this.getRight() = hostVar.getVariable().getALoad() and
+ concatAndCompareAgainstFullHostRegex(schemaVar
+ .getDefinition()
+ .getImmediateDominator()
+ .getNode(), hostVar.getDefinition().getImmediateDominator().getNode())
+ )
+ }
+}
+
+// schema + "somethingon.theinternet.com"
+class LDAPVarString extends BinaryExpr {
+ LDAPVarString() {
+ exists(SsaVariable schemaVar |
+ this.getLeft() = schemaVar.getVariable().getALoad() and
+ concatAndCompareAgainstFullHostRegex(schemaVar
+ .getDefinition()
+ .getImmediateDominator()
+ .getNode(), this.getRight())
+ )
+ }
+}
+
+// "ldap://" + host
+class LDAPStringVar extends BinaryExpr {
+ LDAPStringVar() {
+ exists(SsaVariable hostVar |
+ this.getRight() = hostVar.getVariable().getALoad() and
+ concatAndCompareAgainstFullHostRegex(this.getLeft(),
+ hostVar.getDefinition().getImmediateDominator().getNode())
+ )
+ }
+}
+
+class LDAPInsecureAuthSource extends DataFlow::Node {
+ LDAPInsecureAuthSource() {
+ this instanceof RemoteFlowSource or
+ this.asExpr() instanceof LDAPBothStrings or
+ this.asExpr() instanceof LDAPBothVar or
+ this.asExpr() instanceof LDAPVarString or
+ this.asExpr() instanceof LDAPStringVar
+ }
+}
+
+class SafeLDAPOptions extends ControlFlowNode {
+ SafeLDAPOptions() {
+ this = Value::named("ldap.OPT_X_TLS_ALLOW").getAReference() or
+ this = Value::named("ldap.OPT_X_TLS_TRY").getAReference() or
+ this = Value::named("ldap.OPT_X_TLS_DEMAND").getAReference() or
+ this = Value::named("ldap.OPT_X_TLS_HARD").getAReference()
+ }
+}
+
+// LDAP3
+class LDAPInsecureAuthSink extends DataFlow::Node {
+ LDAPInsecureAuthSink() {
+ exists(SsaVariable connVar, CallNode connCall, SsaVariable srvVar, CallNode srvCall |
+ // set connCall as a Call to ldap3.Connection
+ connCall = Value::named("ldap3.Connection").getACall() and
+ // get variable whose definition is a call to ldap3.Connection to correlate ldap3.Server and Connection.start_tls()
+ connVar.getDefinition().getImmediateDominator() = connCall and
+ // get connCall's first argument variable definition
+ srvVar.getAUse() = connCall.getArg(0) and
+ /*
+ * // restrict srvVar definition to a ldap3.Server Call
+ * srvCall = Value::named("ldap3.Server").getACall() and
+ * srvVar.getDefinition().getImmediateDominator() = srvCall
+ * // redundant? ldap3.Connection's first argument *must* be ldap3.Server
+ */
+
+ // set srvCall as srvVar definition's call
+ srvVar.getDefinition().getImmediateDominator() = srvCall and
+ // set ldap3.Server's 1st argument as sink
+ this.asExpr() = srvCall.getArg(0).getNode() and
+ (
+ // check ldap3.Server call's 3rd argument (positional) is null and there's no use_ssl
+ count(srvCall.getAnArg()) < 3 and
+ count(srvCall.getArgByName("use_ssl")) = 0
+ or
+ // check ldap3.Server call's 3rd argument is False
+ srvCall.getAnArg() instanceof FalseArg
+ or
+ // check ldap3.Server argByName "use_ssl" is False
+ srvCall.getArgByName("use_ssl") instanceof FalseArg
+ ) and
+ /*
+ * Avoid flow through any function (Server()) whose variable declaring it (srv) is the first
+ * argument in any function (Connection()) whose variable declaring it also calls .start_tls
+ */
+
+ /*
+ * host = schema + "somethingon.theinternet.com"
+ * srv = Server(host, port = 1337)
+ * conn = Connection(srv, "dn", "password")
+ * conn.start_tls() !
+ */
+
+ not connVar
+ .getAUse()
+ .getImmediateDominator()
+ .(CallNode)
+ .getNode()
+ .getFunc()
+ .(Attribute)
+ .getName()
+ .matches("start_tls")
+ )
+ }
+}
+
+class LDAPInsecureAuthConfig extends TaintTracking::Configuration {
+ LDAPInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof LDAPInsecureAuthSource }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof LDAPInsecureAuthSink }
+}
+
+from LDAPInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ from $@ is authenticated insecurely.", source.getNode(),
+ "The host", sink.getNode(), "this LDAP query"
diff --git a/python/ql/src/Security/CWE-090/tests/2_private.py b/python/ql/src/Security/CWE-090/tests/2_private.py
new file mode 100644
index 00000000000..4e152cd93c2
--- /dev/null
+++ b/python/ql/src/Security/CWE-090/tests/2_private.py
@@ -0,0 +1,66 @@
+import ldap
+from flask import request, Flask
+
+app = Flask(__name__)
+
+schema = "ldap://"
+remote_host = "127.0.0.1"
+full_host = "ldap://127.0.0.1"
+
+
+# GOOD
+
+# SSL through ldap variable option
+# host from variable + variable
+
+@app.route("/one")
+def one():
+ ldap_connection_1 = ldap.initialize(schema + remote_host)
+ ldap_connection_1.set_option(ldap.OPT_X_TLS_DEMAND, True)
+ ldap_connection_1.simple_bind_s('', '')
+ user = ldap_connection_1.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+# SSL through ldap start_tls_s
+# hardcoded host
+
+@app.route("/two")
+def two():
+ ldap_connection_2 = ldap.initialize("ldap://127.0.0.1")
+ ldap_connection_2.start_tls_s()
+ ldap_connection_2.simple_bind_s('', '')
+ user = ldap_connection_2.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+# BAD (not a sink because it's private)
+
+@app.route("/one_bad")
+def one_bad():
+ ldap_connection_3 = ldap.initialize(schema + remote_host)
+ ldap_connection_3.set_option(ldap.OPT_X_TLS_DEMAND, False)
+ ldap_connection_3.simple_bind_s('', '')
+ user = ldap_connection_3.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+@app.route("/one_bad_2")
+def one_bad_2():
+ ldap_connection_4 = ldap.initialize(schema + remote_host)
+ ldap_connection_4.set_option(ldap.OPT_X_TLS_NEVER)
+ ldap_connection_4.simple_bind_s('', '')
+ user = ldap_connection_4.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+# if __name__ == "__main__":
+# app.run(debug=True)
diff --git a/python/ql/src/Security/CWE-090/tests/2_remote.py b/python/ql/src/Security/CWE-090/tests/2_remote.py
new file mode 100644
index 00000000000..6f82f1dbfd1
--- /dev/null
+++ b/python/ql/src/Security/CWE-090/tests/2_remote.py
@@ -0,0 +1,66 @@
+import ldap
+from flask import request, Flask
+
+app = Flask(__name__)
+
+schema = "ldap://"
+remote_host = "somethingon.theinternet.com"
+full_host = "ldap://somethingon.theinternet.com"
+
+
+# GOOD
+
+# SSL through ldap variable option
+# host from variable + variable
+
+@app.route("/one")
+def one():
+ ldap_connection_5 = ldap.initialize(schema + remote_host)
+ ldap_connection_5.set_option(ldap.OPT_X_TLS_DEMAND, True)
+ ldap_connection_5.simple_bind_s('', '')
+ user = ldap_connection_5.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+# SSL through ldap start_tls_s
+# hardcoded host
+
+@app.route("/two")
+def two():
+ ldap_connection_6 = ldap.initialize("ldap://somethingon.theinternet.com")
+ ldap_connection_6.start_tls_s()
+ ldap_connection_6.simple_bind_s('', '')
+ user = ldap_connection_6.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+# BAD
+
+@app.route("/one_bad")
+def one_bad():
+ ldap_connection_7 = ldap.initialize(schema + remote_host)
+ ldap_connection_7.set_option(ldap.OPT_X_TLS_DEMAND, False)
+ ldap_connection_7.simple_bind_s('', '')
+ user = ldap_connection_7.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+@app.route("/one_bad_2")
+def one_bad_2():
+ ldap_connection_8 = ldap.initialize(schema + remote_host)
+ ldap_connection_8.set_option(ldap.OPT_X_TLS_NEVER)
+ ldap_connection_8.simple_bind_s('', '')
+ user = ldap_connection_8.search_s(
+ "dn", ldap.SCOPE_SUBTREE, "search_filter")
+
+ return user
+
+
+# if __name__ == "__main__":
+# app.run(debug=True)
diff --git a/python/ql/src/Security/CWE-090/tests/3_private.py b/python/ql/src/Security/CWE-090/tests/3_private.py
new file mode 100644
index 00000000000..7c4ede3f0e6
--- /dev/null
+++ b/python/ql/src/Security/CWE-090/tests/3_private.py
@@ -0,0 +1,105 @@
+from ldap3 import Server, Connection, ALL
+from flask import request, Flask
+
+app = Flask(__name__)
+
+schema = "ldap://"
+partial_host = "127.0.0.1"
+full_host = "ldap://127.0.0.1"
+
+
+# hardcoded host
+
+@app.route("/one")
+def one():
+ srv = Server("ldap://127.0.0.1", port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# host from variable
+
+@app.route("/two")
+def two():
+ srv = Server(full_host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# schema from string + variable
+
+@app.route("/three")
+def three():
+ host = "ldap://" + partial_host
+
+ srv = Server(host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# schema from variable + variable
+
+@app.route("/four")
+def four():
+ host = schema + partial_host
+
+ srv = Server(host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# schema from string + string
+
+@app.route("/five")
+def five():
+ host = "ldap://" + "127.0.0.1"
+
+ srv = Server(host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# schema from variable + hardcoded host
+
+@app.route("/six")
+def six():
+ host = schema + "127.0.0.1"
+
+ srv = Server(host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (positional argument)
+# host from string + variable
+
+@app.route("/four")
+def four():
+ host = "ldap://" + partial_host
+
+ srv = Server(host, port=1337, True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (argument by name)
+# host from variable + variable
+
+@app.route("/five")
+def five():
+ host = schema + partial_host
+
+ srv = Server(host, port=1337, use_ssl=True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+
+
+# if __name__ == "__main__":
+# app.run(debug=True)
diff --git a/python/ql/src/Security/CWE-090/tests/3_remote.py b/python/ql/src/Security/CWE-090/tests/3_remote.py
new file mode 100644
index 00000000000..366fce2b186
--- /dev/null
+++ b/python/ql/src/Security/CWE-090/tests/3_remote.py
@@ -0,0 +1,146 @@
+from ldap3 import Server, Connection, ALL
+from flask import request, Flask
+
+app = Flask(__name__)
+
+schema = "ldap://"
+remote_host = "somethingon.theinternet.com"
+full_host = "ldap://somethingon.theinternet.com"
+
+
+# use_ssl = True (positional argument)
+# hardcoded host
+
+@app.route("/one")
+def one():
+ srv = Server("ldap://somethingon.theinternet.com", port=1337, True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (argument by name)
+# host from variable
+
+@app.route("/two")
+def two():
+ srv = Server(full_host, port=1337, use_ssl=True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (argument by name)
+# host from RFS
+
+@app.route("/three")
+def three():
+ srv = Server(request.args['host'], port=1337, use_ssl=True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (positional argument)
+# host from string + variable
+
+@app.route("/four")
+def four():
+ host = "ldap://" + remote_host
+
+ srv = Server(host, port=1337, True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (argument by name)
+# host from variable + variable
+
+@app.route("/five")
+def five():
+ host = schema + remote_host
+
+ srv = Server(host, port=1337, use_ssl=True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (argument by name)
+# host from string + RFS
+
+@app.route("/six")
+def six():
+ host = "ldap://" + request.args['host']
+
+ srv = Server(host, port=1337, use_ssl=True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# use_ssl = True (positional argument)
+# host from variable + RFS
+
+@app.route("/seven")
+def seven():
+ host = schema + request.args['host']
+
+ srv = Server(host, port=1337, True)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# SSL through special method
+# host from variable + hardcoded host
+
+@app.route("/eight")
+def eight():
+ host = schema + "somethingon.theinternet.com"
+ srv = Server(host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.start_tls() # !
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# No SSL (to test sink)
+# host from variable + hardcoded host
+
+@app.route("/nine")
+def nine():
+ host = schema + "somethingon.theinternet.com"
+ srv = Server(host, port=1337, False)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# No SSL (to test sink)
+# host from variable + variable
+
+@app.route("/ten")
+def ten():
+ host = schema + remote_host
+ srv = Server(host, port=1337, use_ssl=False)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# No SSL (to test sink)
+# host from variable + RFS
+
+@app.route("/eleven")
+def eleven():
+ host = schema + request.args['host']
+ srv = Server(host, port=1337)
+ conn = Connection(srv, "dn", "password")
+ conn.search("dn", "search_filter")
+ return conn.response
+
+
+# if __name__ == "__main__":
+# app.run(debug=True)
From 3ce0a9c8c08ed8091335267143fd2f25703fb425 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 18 Mar 2021 20:20:04 +0100
Subject: [PATCH 022/459] Move to experimental folder
---
.../src/{ => experimental}/Security/CWE-090/LDAPInsecureAuth.ql | 0
.../ql/src/{ => experimental}/Security/CWE-090/tests/2_private.py | 0
.../ql/src/{ => experimental}/Security/CWE-090/tests/2_remote.py | 0
.../ql/src/{ => experimental}/Security/CWE-090/tests/3_private.py | 0
.../ql/src/{ => experimental}/Security/CWE-090/tests/3_remote.py | 0
5 files changed, 0 insertions(+), 0 deletions(-)
rename python/ql/src/{ => experimental}/Security/CWE-090/LDAPInsecureAuth.ql (100%)
rename python/ql/src/{ => experimental}/Security/CWE-090/tests/2_private.py (100%)
rename python/ql/src/{ => experimental}/Security/CWE-090/tests/2_remote.py (100%)
rename python/ql/src/{ => experimental}/Security/CWE-090/tests/3_private.py (100%)
rename python/ql/src/{ => experimental}/Security/CWE-090/tests/3_remote.py (100%)
diff --git a/python/ql/src/Security/CWE-090/LDAPInsecureAuth.ql b/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
similarity index 100%
rename from python/ql/src/Security/CWE-090/LDAPInsecureAuth.ql
rename to python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
diff --git a/python/ql/src/Security/CWE-090/tests/2_private.py b/python/ql/src/experimental/Security/CWE-090/tests/2_private.py
similarity index 100%
rename from python/ql/src/Security/CWE-090/tests/2_private.py
rename to python/ql/src/experimental/Security/CWE-090/tests/2_private.py
diff --git a/python/ql/src/Security/CWE-090/tests/2_remote.py b/python/ql/src/experimental/Security/CWE-090/tests/2_remote.py
similarity index 100%
rename from python/ql/src/Security/CWE-090/tests/2_remote.py
rename to python/ql/src/experimental/Security/CWE-090/tests/2_remote.py
diff --git a/python/ql/src/Security/CWE-090/tests/3_private.py b/python/ql/src/experimental/Security/CWE-090/tests/3_private.py
similarity index 100%
rename from python/ql/src/Security/CWE-090/tests/3_private.py
rename to python/ql/src/experimental/Security/CWE-090/tests/3_private.py
diff --git a/python/ql/src/Security/CWE-090/tests/3_remote.py b/python/ql/src/experimental/Security/CWE-090/tests/3_remote.py
similarity index 100%
rename from python/ql/src/Security/CWE-090/tests/3_remote.py
rename to python/ql/src/experimental/Security/CWE-090/tests/3_remote.py
From 957b3e1e85fd3c478b81ffbef7c4e6bed5e7d235 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 18 Mar 2021 20:39:53 +0100
Subject: [PATCH 023/459] Precision warn
---
python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql | 1 +
1 file changed, 1 insertion(+)
diff --git a/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql b/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
index b156cc2b036..ba31bb8a287 100644
--- a/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
+++ b/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
@@ -9,6 +9,7 @@
* external/cwe/cwe-090
*/
+// Determine precision above
import python
import semmle.python.dataflow.new.RemoteFlowSources
import semmle.python.dataflow.new.DataFlow
From 858c0e67a11818dd1d90db44910f51eef9c59a2d Mon Sep 17 00:00:00 2001
From: mr-sherman <77112096+mr-sherman@users.noreply.github.com>
Date: Mon, 22 Mar 2021 19:27:49 -0400
Subject: [PATCH 024/459] added support for remote flow sinks in the form of
parameters to the function ServiceStack.IRestClient.Get()
---
.../code/csharp/frameworks/ServiceStack.qll | 16 +++++++++++++++-
.../security/dataflow/flowsinks/Remote.qll | 1 +
2 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 07a771f7183..2d29bee9e68 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -78,7 +78,21 @@ module Sources {
}
}
}
-
+/** Flow Sinks for the ServiceStack framework */
+module Sinks {
+ private import semmle.code.csharp.security.dataflow.flowsinks.Remote
+
+ /** RemoteFlow sinks for service stack */
+ class ServiceStackRemoteRequestParameter extends RemoteFlowSink {
+ ServiceStackRemoteRequestParameter() {
+ exists(MethodCall mc |
+ mc.getTarget().hasQualifiedName("ServiceStack.IRestClient.Get") and
+ mc.getArgument(0) = this.asExpr()
+ )
+ }
+ }
+ }
+
/** SQLi support for the ServiceStack framework */
module SQL {
private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
index 10885d52a16..ed5fceefb91 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
@@ -8,6 +8,7 @@ private import ExternalLocationSink
private import Html
private import semmle.code.csharp.security.dataflow.XSS
private import semmle.code.csharp.frameworks.system.web.UI
+import semmle.code.csharp.frameworks.ServiceStack::Sinks
/** A data flow sink of remote user output. */
abstract class RemoteFlowSink extends DataFlow::Node { }
From 3e889c398e47097addd46bd8c693b87a6ee69009 Mon Sep 17 00:00:00 2001
From: mr-sherman <77112096+mr-sherman@users.noreply.github.com>
Date: Tue, 23 Mar 2021 10:09:30 -0400
Subject: [PATCH 025/459] updated document formatting
---
.../code/csharp/frameworks/ServiceStack.qll | 245 +++++++++---------
1 file changed, 129 insertions(+), 116 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 2d29bee9e68..cda710b839e 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -9,151 +9,164 @@ import csharp
/** A class representing a Service */
class ServiceClass extends Class {
- ServiceClass() { this.getBaseClass+().getQualifiedName()="ServiceStack.Service" }
+ ServiceClass() { this.getBaseClass+().getQualifiedName() = "ServiceStack.Service" }
- /** Get a method that handles incoming requests */
- Method getARequestMethod() {
- result = this.getAMethod(["Post", "Get", "Put", "Delete", "Any", "Option", "Head"])
- }
+ /** Get a method that handles incoming requests */
+ Method getARequestMethod() {
+ result = this.getAMethod(["Post", "Get", "Put", "Delete", "Any", "Option", "Head"])
+ }
}
/** Top-level Request DTO types */
class RequestDTO extends Class {
- RequestDTO() {
- this.getABaseInterface().getQualifiedName() = ["ServiceStack.IReturn", "ServieStack.IReturnVoid"]
- }
+ RequestDTO() {
+ this.getABaseInterface().getQualifiedName() =
+ ["ServiceStack.IReturn", "ServieStack.IReturnVoid"]
+ }
}
/** Top-level Response DTO types */
class ResponseDTO extends Class {
- ResponseDTO() {
- exists(RequestDTO req, ConstructedGeneric respInterface |
- req.getABaseInterface() = respInterface and
- respInterface.getUndecoratedName() = "IReturn" and
- respInterface.getATypeArgument() = this
- )
- }
+ ResponseDTO() {
+ exists(RequestDTO req, ConstructedGeneric respInterface |
+ req.getABaseInterface() = respInterface and
+ respInterface.getUndecoratedName() = "IReturn" and
+ respInterface.getATypeArgument() = this
+ )
+ }
}
/** Flow sources for the ServiceStack framework */
module Sources {
- private import semmle.code.csharp.security.dataflow.flowsources.Remote
- private import semmle.code.csharp.commons.Collections
+ private import semmle.code.csharp.security.dataflow.flowsources.Remote
+ private import semmle.code.csharp.commons.Collections
- /** Types involved in a RequestDTO. Recurse through props and collection types */
- private predicate involvedInRequest(RefType c) {
- c instanceof RequestDTO or
- exists(RefType parent, RefType propType | involvedInRequest(parent) |
- (propType = parent.getAProperty().getType() or propType = parent.getAField().getType()) and
- if propType instanceof CollectionType then (
- c = propType.(ConstructedGeneric).getATypeArgument() or
- c = propType.(ArrayType).getElementType()
- ) else (
- c = propType
- )
- )
+ /** Types involved in a RequestDTO. Recurse through props and collection types */
+ private predicate involvedInRequest(RefType c) {
+ c instanceof RequestDTO
+ or
+ exists(RefType parent, RefType propType | involvedInRequest(parent) |
+ (propType = parent.getAProperty().getType() or propType = parent.getAField().getType()) and
+ if propType instanceof CollectionType
+ then
+ c = propType.(ConstructedGeneric).getATypeArgument() or
+ c = propType.(ArrayType).getElementType()
+ else c = propType
+ )
+ }
+
+ /**
+ * Remote flow sources for ServiceStack
+ *
+ * Assumes all nested fields/properties on request DTOs are tainted, which is
+ * an overapproximation and may lead to FPs depending on how Service Stack app
+ * is configured.
+ */
+ class ServiceStackSource extends RemoteFlowSource {
+ ServiceStackSource() {
+ // Parameters are sources. In practice only interesting when they are string/primitive typed.
+ exists(ServiceClass service |
+ service.getARequestMethod().getAParameter() = this.asParameter()
+ )
+ or
+ // Field/property accesses on RequestDTOs and request involved types
+ // involved types aren't necessarily only from requests so may lead to FPs...
+ exists(RefType reqType | involvedInRequest(reqType) |
+ reqType.getAProperty().getAnAccess() = this.asExpr() or
+ reqType.getAField().getAnAccess() = this.asExpr()
+ )
}
- /**
- * Remote flow sources for ServiceStack
- *
- * Assumes all nested fields/properties on request DTOs are tainted, which is
- * an overapproximation and may lead to FPs depending on how Service Stack app
- * is configured.
- */
- class ServiceStackSource extends RemoteFlowSource {
- ServiceStackSource() {
- // Parameters are sources. In practice only interesting when they are string/primitive typed.
- exists(ServiceClass service |
- service.getARequestMethod().getAParameter() = this.asParameter()) or
- // Field/property accesses on RequestDTOs and request involved types
- // involved types aren't necessarily only from requests so may lead to FPs...
- exists(RefType reqType | involvedInRequest(reqType) |
- reqType.getAProperty().getAnAccess() = this.asExpr() or
- reqType.getAField().getAnAccess() = this.asExpr())
- }
-
- override string getSourceType() {
- result = "ServiceStack request DTO field"
- }
- }
+ override string getSourceType() { result = "ServiceStack request DTO field" }
+ }
}
+
/** Flow Sinks for the ServiceStack framework */
module Sinks {
- private import semmle.code.csharp.security.dataflow.flowsinks.Remote
-
- /** RemoteFlow sinks for service stack */
- class ServiceStackRemoteRequestParameter extends RemoteFlowSink {
- ServiceStackRemoteRequestParameter() {
- exists(MethodCall mc |
- mc.getTarget().hasQualifiedName("ServiceStack.IRestClient.Get") and
- mc.getArgument(0) = this.asExpr()
- )
- }
+ private import semmle.code.csharp.security.dataflow.flowsinks.Remote
+
+ /** RemoteFlow sinks for service stack */
+ class ServiceStackRemoteRequestParameter extends RemoteFlowSink {
+ ServiceStackRemoteRequestParameter() {
+ exists(MethodCall mc |
+ mc.getTarget().hasQualifiedName("ServiceStack.IRestClient.Get") and
+ mc.getArgument(0) = this.asExpr()
+ )
}
}
-
+}
+
/** SQLi support for the ServiceStack framework */
module SQL {
- private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
-
- /** SQLi sinks for ServiceStack */
- class ServiceStackSink extends Sink {
- ServiceStackSink() {
- exists(MethodCall mc, Method m, int p |
- (mc.getTarget() = m.getAnOverrider*() or mc.getTarget() = m.getAnImplementor*()) and
- sqlSinkParam(m, p) and
- mc.getArgument(p) = this.asExpr())
- }
- }
+ private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
- private predicate sqlSinkParam(Method m, int p) {
- exists(RefType cls | cls = m.getDeclaringType() |
- (
- // if using the typed query builder api, only need to worry about Unsafe variants
- cls.getQualifiedName() = ["ServiceStack.OrmLite.SqlExpression", "ServiceStack.OrmLite.IUntypedSqlExpression"] and
- m.getName().matches("Unsafe%") and
- p = 0
- ) or (
- // Read api - all string typed 1st params are potential sql sinks. They should be templates, not directly user controlled.
- cls.getQualifiedName() = ["ServiceStack.OrmLite.OrmLiteReadApi", "ServiceStack.OrmLite.OrmLiteReadExpressionsApi", "ServiceStack.OrmLite.OrmLiteReadApiAsync", "ServiceStack.OrmLite.OrmLiteReadExpressionsApiAsync"] and
- m.getParameter(p).getType() instanceof StringType and
- p = 1
- ) or (
- // Write API - only 2 methods that take string
- cls.getQualifiedName() = ["ServiceStack.OrmLite.OrmLiteWriteApi", "ServiceStack.OrmLite.OrmLiteWriteApiAsync"] and
- m.getName() = ["ExecuteSql", "ExecuteSqlAsync"] and
- p = 1
- ) or (
- // NoSQL sinks in redis client. TODO should these be separate query?
- cls.getQualifiedName() = "ServiceStack.Redis.IRedisClient" and
- (m.getName() = ["Custom", "LoadLuaScript"] or (m.getName().matches("%Lua%") and not m.getName().matches("%Sha%"))) and
- p = 0
- )
- // TODO
- // ServiceStack.OrmLite.OrmLiteUtils.SqlColumn - what about other similar classes?
- // couldn't find CustomSelect
- // need to handle "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable"
-
- )
+ /** SQLi sinks for ServiceStack */
+ class ServiceStackSink extends Sink {
+ ServiceStackSink() {
+ exists(MethodCall mc, Method m, int p |
+ (mc.getTarget() = m.getAnOverrider*() or mc.getTarget() = m.getAnImplementor*()) and
+ sqlSinkParam(m, p) and
+ mc.getArgument(p) = this.asExpr()
+ )
}
+ }
+
+ private predicate sqlSinkParam(Method m, int p) {
+ exists(RefType cls | cls = m.getDeclaringType() |
+ // if using the typed query builder api, only need to worry about Unsafe variants
+ cls.getQualifiedName() =
+ ["ServiceStack.OrmLite.SqlExpression", "ServiceStack.OrmLite.IUntypedSqlExpression"] and
+ m.getName().matches("Unsafe%") and
+ p = 0
+ or
+ // Read api - all string typed 1st params are potential sql sinks. They should be templates, not directly user controlled.
+ cls.getQualifiedName() =
+ [
+ "ServiceStack.OrmLite.OrmLiteReadApi", "ServiceStack.OrmLite.OrmLiteReadExpressionsApi",
+ "ServiceStack.OrmLite.OrmLiteReadApiAsync",
+ "ServiceStack.OrmLite.OrmLiteReadExpressionsApiAsync"
+ ] and
+ m.getParameter(p).getType() instanceof StringType and
+ p = 1
+ or
+ // Write API - only 2 methods that take string
+ cls.getQualifiedName() =
+ ["ServiceStack.OrmLite.OrmLiteWriteApi", "ServiceStack.OrmLite.OrmLiteWriteApiAsync"] and
+ m.getName() = ["ExecuteSql", "ExecuteSqlAsync"] and
+ p = 1
+ or
+ // NoSQL sinks in redis client. TODO should these be separate query?
+ cls.getQualifiedName() = "ServiceStack.Redis.IRedisClient" and
+ (
+ m.getName() = ["Custom", "LoadLuaScript"]
+ or
+ m.getName().matches("%Lua%") and not m.getName().matches("%Sha%")
+ ) and
+ p = 0
+ // TODO
+ // ServiceStack.OrmLite.OrmLiteUtils.SqlColumn - what about other similar classes?
+ // couldn't find CustomSelect
+ // need to handle "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable"
+ )
+ }
}
/** XSS support for ServiceStack framework */
module XSS {
- private import semmle.code.csharp.security.dataflow.XSS::XSS
+ private import semmle.code.csharp.security.dataflow.XSS::XSS
- /** XSS sinks for ServiceStack */
- class XssSink extends Sink {
- XssSink() {
- exists(ServiceClass service, ReturnStmt r |
- this.asExpr() = r.getExpr() and
- r.getEnclosingCallable() = service.getARequestMethod()
- ) or
- exists(ObjectCreation oc |
- oc.getType().hasQualifiedName("ServiceStack.HttpResult") and
- this.asExpr() = oc.getArgument(0)
- )
- }
+ /** XSS sinks for ServiceStack */
+ class XssSink extends Sink {
+ XssSink() {
+ exists(ServiceClass service, ReturnStmt r |
+ this.asExpr() = r.getExpr() and
+ r.getEnclosingCallable() = service.getARequestMethod()
+ )
+ or
+ exists(ObjectCreation oc |
+ oc.getType().hasQualifiedName("ServiceStack.HttpResult") and
+ this.asExpr() = oc.getArgument(0)
+ )
}
+ }
}
From 13997caa32e018b4daef2ce16267292a79e21aeb Mon Sep 17 00:00:00 2001
From: mr-sherman <77112096+mr-sherman@users.noreply.github.com>
Date: Fri, 26 Mar 2021 16:29:14 -0400
Subject: [PATCH 026/459] feedback from code review
---
.../semmle/code/csharp/frameworks/ServiceStack.qll | 13 +++++++++----
.../dataflow/flowsinks/ExternalLocationSink.qll | 2 +-
.../csharp/security/dataflow/flowsinks/Remote.qll | 2 +-
3 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index cda710b839e..9520b093a7e 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -83,14 +83,19 @@ module Sources {
/** Flow Sinks for the ServiceStack framework */
module Sinks {
- private import semmle.code.csharp.security.dataflow.flowsinks.Remote
+ private import semmle.code.csharp.security.dataflow.flowsinks.ExternalLocationSink
/** RemoteFlow sinks for service stack */
- class ServiceStackRemoteRequestParameter extends RemoteFlowSink {
+ class ServiceStackRemoteRequestParameter extends ExternalLocationSink {
ServiceStackRemoteRequestParameter() {
exists(MethodCall mc |
- mc.getTarget().hasQualifiedName("ServiceStack.IRestClient.Get") and
- mc.getArgument(0) = this.asExpr()
+ mc.getTarget().getQualifiedName() in [
+ "ServiceStack.IRestClient.Get", "ServiceStack.IRestClient.Put",
+ "ServiceStack.IRestClient.Post", "ServiceStack.IRestClient.Delete",
+ "ServiceStack.IRestClient.Post", "ServiceStack.IRestClient.Put",
+ "ServiceStack.IRestClient.Patch", "ServiceStack.IRestClient.Send"
+ ] and
+ this.asExpr() = mc.getAnArgument()
)
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll
index 25a50f3733c..63d1dfd04ce 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/ExternalLocationSink.qll
@@ -6,7 +6,7 @@ import csharp
private import Remote
private import semmle.code.csharp.commons.Loggers
private import semmle.code.csharp.frameworks.system.Web
-
+private import semmle.code.csharp.frameworks.ServiceStack::Sinks
/**
* An external location sink.
*
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
index ed5fceefb91..52685a50661 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
@@ -8,7 +8,7 @@ private import ExternalLocationSink
private import Html
private import semmle.code.csharp.security.dataflow.XSS
private import semmle.code.csharp.frameworks.system.web.UI
-import semmle.code.csharp.frameworks.ServiceStack::Sinks
+private import semmle.code.csharp.frameworks.ServiceStack::Sinks
/** A data flow sink of remote user output. */
abstract class RemoteFlowSink extends DataFlow::Node { }
From bf2d7b3a16173d6588a47d457e3ba43e16783952 Mon Sep 17 00:00:00 2001
From: mr-sherman <77112096+mr-sherman@users.noreply.github.com>
Date: Mon, 29 Mar 2021 14:37:51 -0400
Subject: [PATCH 027/459] Added IRestClientAsync methods to external location
sink. Removed import from Remote.qll, as it is un-necessary now.
---
.../ql/src/semmle/code/csharp/frameworks/ServiceStack.qll | 8 +++++---
.../code/csharp/security/dataflow/flowsinks/Remote.qll | 1 -
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 9520b093a7e..198fea63b0e 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -91,9 +91,11 @@ module Sinks {
exists(MethodCall mc |
mc.getTarget().getQualifiedName() in [
"ServiceStack.IRestClient.Get", "ServiceStack.IRestClient.Put",
- "ServiceStack.IRestClient.Post", "ServiceStack.IRestClient.Delete",
- "ServiceStack.IRestClient.Post", "ServiceStack.IRestClient.Put",
- "ServiceStack.IRestClient.Patch", "ServiceStack.IRestClient.Send"
+ "ServiceStack.IRestClient.Post", "ServiceStack.IRestClient.Delete",
+ "ServiceStack.IRestClient.Patch", "ServiceStack.IRestClient.Send",
+ "ServiceStack.IRestClientAsync.GetAsync","ServiceStack.IRestClientAsync.DeleteAsync",
+ "ServiceStack.IRestClientAsync.PutAsync","ServiceStack.IRestClientAsync.PostAsync",
+ "ServiceStack.IRestClientAsync.PatchAsync","ServiceStack.IRestClientAsync.CustomMethodAsync"
] and
this.asExpr() = mc.getAnArgument()
)
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
index 52685a50661..10885d52a16 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsinks/Remote.qll
@@ -8,7 +8,6 @@ private import ExternalLocationSink
private import Html
private import semmle.code.csharp.security.dataflow.XSS
private import semmle.code.csharp.frameworks.system.web.UI
-private import semmle.code.csharp.frameworks.ServiceStack::Sinks
/** A data flow sink of remote user output. */
abstract class RemoteFlowSink extends DataFlow::Node { }
From 092fbd60d9b433dc005519c76945d5df8549244b Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 22 Jun 2021 15:31:17 +0200
Subject: [PATCH 028/459] C++: Create a new SQL interface.
---
cpp/ql/src/semmle/code/cpp/security/Sql.qll | 140 ++++++++++++++++++++
1 file changed, 140 insertions(+)
create mode 100644 cpp/ql/src/semmle/code/cpp/security/Sql.qll
diff --git a/cpp/ql/src/semmle/code/cpp/security/Sql.qll b/cpp/ql/src/semmle/code/cpp/security/Sql.qll
new file mode 100644
index 00000000000..cd94dd0995d
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/security/Sql.qll
@@ -0,0 +1,140 @@
+/**
+ * This file provides classes for working with various SQL libraries and frameworks.
+ */
+
+private import cpp
+private import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
+
+/**
+ * An abstract class that represents SQL parameters and escaping functions.
+ *
+ * To add support for a new SQL framework, extend this class with
+ * a subclass whose characteristic predicate is a unique singleton string.
+ * For example, write
+ *
+ * ```ql
+ * class MySqlFunctionality extends SqlFunctionality {
+ * MySqlFunctionality() { this = "MySqlFunctionality" }
+ * // Override `getAnSqlParameter`.
+ * // Optionally override `getAnEscapedParameter`.
+ * }
+ * ```
+ */
+abstract class SqlFunctionality extends string {
+ bindingset[this]
+ SqlFunctionality() { any() }
+
+ /**
+ * Holds if `input` to the function `func` represents data that is passed to an SQL server.
+ */
+ abstract predicate getAnSqlParameter(Function func, FunctionInput input);
+
+ /**
+ * Holds if the `output` from `func` escapes the SQL input `input` such that is it safe to pass to
+ * an SQL server.
+ */
+ predicate getAnEscapedParameter(Function func, FunctionInput input, FunctionOutput output) {
+ none()
+ }
+}
+
+private class MySqlFunctionality extends SqlFunctionality {
+ MySqlFunctionality() { this = "MySqlFunctionality" }
+
+ override predicate getAnSqlParameter(Function func, FunctionInput input) {
+ func.hasName(["mysql_query", "mysql_real_query"]) and
+ input.isParameterDeref(1)
+ }
+}
+
+private class SqLite3Functionality extends SqlFunctionality {
+ SqLite3Functionality() { this = "SqLite3Functionality" }
+
+ override predicate getAnSqlParameter(Function func, FunctionInput input) {
+ func.hasName("sqlite3_exec") and
+ input.isParameterDeref(1)
+ }
+}
+
+private module PostgreSql {
+ private predicate pqxxTransactionSqlArgument(string function, int arg) {
+ function = "exec" and arg = 0
+ or
+ function = "exec0" and arg = 0
+ or
+ function = "exec1" and arg = 0
+ or
+ function = "exec_n" and arg = 1
+ or
+ function = "exec_params" and arg = 0
+ or
+ function = "exec_params0" and arg = 0
+ or
+ function = "exec_params1" and arg = 0
+ or
+ function = "exec_params_n" and arg = 1
+ or
+ function = "query_value" and arg = 0
+ or
+ function = "stream" and arg = 0
+ }
+
+ private predicate pqxxConnectionSqlArgument(string function, int arg) {
+ function = "prepare" and arg = 1
+ }
+
+ private predicate pqxxTransationClassNames(string className, string namespace) {
+ namespace = "pqxx" and
+ className in [
+ "dbtransaction", "nontransaction", "basic_robusttransaction", "robusttransaction",
+ "subtransaction", "transaction", "basic_transaction", "transaction_base", "work"
+ ]
+ }
+
+ private predicate pqxxConnectionClassNames(string className, string namespace) {
+ namespace = "pqxx" and
+ className in ["connection_base", "basic_connection", "connection"]
+ }
+
+ private predicate pqxxEscapeArgument(string function, int arg) {
+ arg = 0 and
+ function in ["esc", "esc_raw", "quote", "quote_raw", "quote_name", "quote_table", "esc_like"]
+ }
+
+ class PostgreSqlFunctionality extends SqlFunctionality {
+ PostgreSqlFunctionality() { this = "PostgreSqlFunctionality" }
+
+ override predicate getAnSqlParameter(Function func, FunctionInput input) {
+ exists(int argIndex, UserType t |
+ func.getDeclaringType() = t and
+ // transaction exec and connection prepare variations
+ (
+ pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) and
+ pqxxTransactionSqlArgument(func.getName(), argIndex)
+ or
+ pqxxConnectionClassNames(t.getName(), t.getNamespace().getName()) and
+ pqxxConnectionSqlArgument(func.getName(), argIndex)
+ ) and
+ input.isParameterDeref(argIndex)
+ )
+ }
+
+ override predicate getAnEscapedParameter(
+ Function func, FunctionInput input, FunctionOutput output
+ ) {
+ exists(int argIndex, UserType t |
+ func.getDeclaringType() = t and
+ // transaction and connection escape functions
+ (
+ pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) or
+ pqxxConnectionClassNames(t.getName(), t.getNamespace().getName())
+ ) and
+ pqxxEscapeArgument(func.getName(), argIndex) and
+ input.isParameterDeref(argIndex) and
+ output.isReturnValueDeref()
+ )
+ }
+ }
+}
+
+private import PostgreSql
From 222cd41aa33dae33f9c5c97591ef9197656e076d Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 22 Jun 2021 15:33:10 +0200
Subject: [PATCH 029/459] C++: Use the new SQL interface in 'Security.qll' and
'SqlTainted.ql'.
---
cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql | 11 ++++++++++-
cpp/ql/src/semmle/code/cpp/security/Security.qll | 13 ++++++-------
2 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql b/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
index a3f935170d7..b75371c705c 100644
--- a/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
+++ b/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
@@ -16,6 +16,7 @@ import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.TaintTracking
+import semmle.code.cpp.security.Sql
import TaintedWithPath
class SQLLikeFunction extends FunctionWithWrappers {
@@ -30,7 +31,15 @@ class Configuration extends TaintTrackingConfiguration {
}
override predicate isBarrier(Expr e) {
- super.isBarrier(e) or e.getUnspecifiedType() instanceof IntegralType
+ super.isBarrier(e)
+ or
+ e.getUnspecifiedType() instanceof IntegralType
+ or
+ exists(SqlFunctionality sql, int arg, Function func, FunctionInput input |
+ e = func.getACallToThisFunction().getArgument(arg) and
+ input.isParameterDeref(arg) and
+ sql.getAnEscapedParameter(func, input, _)
+ )
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/security/Security.qll b/cpp/ql/src/semmle/code/cpp/security/Security.qll
index d39c13a25a0..ac1b5e9f280 100644
--- a/cpp/ql/src/semmle/code/cpp/security/Security.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/Security.qll
@@ -7,6 +7,7 @@ import semmle.code.cpp.exprs.Expr
import semmle.code.cpp.commons.Environment
import semmle.code.cpp.security.SecurityOptions
import semmle.code.cpp.models.interfaces.FlowSource
+private import Sql
/**
* Extend this class to customize the security queries for
@@ -34,13 +35,11 @@ class SecurityOptions extends string {
* An argument to a function that is passed to a SQL server.
*/
predicate sqlArgument(string function, int arg) {
- // MySQL C API
- function = "mysql_query" and arg = 1
- or
- function = "mysql_real_query" and arg = 1
- or
- // SQLite3 C API
- function = "sqlite3_exec" and arg = 1
+ exists(Function func, FunctionInput input, SqlFunctionality sql |
+ func.hasName(function) and
+ input.isParameterDeref(arg) and
+ sql.getAnSqlParameter(func, input)
+ )
}
/**
From 440793b5ff2226a51b78e993db35dd63ab2ad9f2 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 22 Jun 2021 15:45:58 +0200
Subject: [PATCH 030/459] C++: Move the example from the experimental CWE-089
query into a test.
---
.../CWE-089/SqlTainted/SqlTainted.expected | 18 +++++++
.../Security/CWE/CWE-089/SqlTainted/test.cpp | 48 +++++++++++++++++++
2 files changed, 66 insertions(+)
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.cpp
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
index e267dd48bba..9c16adc2a11 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
@@ -5,6 +5,14 @@ edges
| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 |
| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 indirection |
| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 indirection |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | (const char *)... |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | (const char *)... |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array indirection |
+| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array indirection |
nodes
| test.c:15:20:15:23 | argv | semmle.label | argv |
| test.c:15:20:15:23 | argv | semmle.label | argv |
@@ -13,5 +21,15 @@ nodes
| test.c:21:18:21:23 | query1 | semmle.label | query1 |
| test.c:21:18:21:23 | query1 indirection | semmle.label | query1 indirection |
| test.c:21:18:21:23 | query1 indirection | semmle.label | query1 indirection |
+| test.cpp:44:27:44:30 | argv | semmle.label | argv |
+| test.cpp:44:27:44:30 | argv | semmle.label | argv |
+| test.cpp:44:27:44:33 | (const char *)... | semmle.label | (const char *)... |
+| test.cpp:44:27:44:33 | (const char *)... | semmle.label | (const char *)... |
+| test.cpp:44:27:44:33 | access to array | semmle.label | access to array |
+| test.cpp:44:27:44:33 | access to array | semmle.label | access to array |
+| test.cpp:44:27:44:33 | access to array | semmle.label | access to array |
+| test.cpp:44:27:44:33 | access to array indirection | semmle.label | access to array indirection |
+| test.cpp:44:27:44:33 | access to array indirection | semmle.label | access to array indirection |
#select
| test.c:21:18:21:23 | query1 | test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 | This argument to a SQL query function is derived from $@ and then passed to mysql_query(sqlArg) | test.c:15:20:15:23 | argv | user input (argv) |
+| test.cpp:44:27:44:33 | access to array | test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array | This argument to a SQL query function is derived from $@ and then passed to pqxx::work::exec1((unnamed parameter 0)) | test.cpp:44:27:44:30 | argv | user input (argv) |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.cpp
new file mode 100644
index 00000000000..8bdf7dded23
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/test.cpp
@@ -0,0 +1,48 @@
+int sprintf(char* str, const char* format, ...);
+
+namespace std
+{
+ template struct char_traits;
+
+ template class allocator {
+ public:
+ allocator() throw();
+ };
+
+ template, class Allocator = allocator >
+ class basic_string {
+ public:
+ explicit basic_string(const Allocator& a = Allocator());
+ basic_string(const charT* s, const Allocator& a = Allocator());
+
+ const charT* c_str() const;
+ };
+
+ typedef basic_string string;
+}
+
+namespace pqxx {
+ struct connection {};
+
+ struct row {};
+ struct result {};
+
+ struct work {
+ work(connection&);
+
+ row exec1(const char*);
+ result exec(const std::string&);
+ std::string quote(const char*);
+ };
+}
+
+int main(int argc, char** argv) {
+ pqxx::connection c;
+ pqxx::work w(c);
+
+ pqxx::row r = w.exec1(argv[1]); // BAD
+
+ pqxx::result r2 = w.exec(w.quote(argv[1])); // GOOD
+
+ return 0;
+}
\ No newline at end of file
From 2e2673aff649efbf04145899e035c08b6946e745 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 22 Jun 2021 15:46:26 +0200
Subject: [PATCH 031/459] C++: Delete the experimental SqlPqxxTainted query.
---
.../Security/CWE/CWE-089/SqlPqxxTainted.cpp | 28 -----
.../Security/CWE/CWE-089/SqlPqxxTainted.qhelp | 31 -----
.../Security/CWE/CWE-089/SqlPqxxTainted.ql | 113 ------------------
3 files changed, 172 deletions(-)
delete mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp
delete mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp
delete mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp
deleted file mode 100644
index 3b85835fff9..00000000000
--- a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-#include
-#include
-#include
-
-int main(int argc, char ** argv) {
-
- if (argc != 2) {
- throw std::runtime_error("Give me a string!");
- }
-
- pqxx::connection c;
- pqxx::work w(c);
-
- // BAD
- char *userName = argv[1];
- char query1[1000] = {0};
- sprintf(query1, "SELECT UID FROM USERS where name = \"%s\"", userName);
- pqxx::row r = w.exec1(query1);
- w.commit();
- std::cout << r[0].as() << std::endl;
-
- // GOOD
- pqxx::result r2 = w.exec("SELECT " + w.quote(argv[1]));
- w.commit();
- std::cout << r2[0][0].c_str() << std::endl;
-
- return 0;
-}
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp
deleted file mode 100644
index 1c01b3e4f3a..00000000000
--- a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.qhelp
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
The code passes user input as part of a SQL query without escaping special elements.
-It generates a SQL query to Postgres using sprintf,
-with the user-supplied data directly passed as an argument
-to sprintf. This leaves the code vulnerable to attack by SQL Injection.
-
-
-
-
-
Use a library routine to escape characters in the user-supplied
-string before converting it to SQL. Use esc and quote pqxx library functions.
-
-
-
-
-
-
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql b/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql
deleted file mode 100644
index 8de55953b15..00000000000
--- a/cpp/ql/src/experimental/Security/CWE/CWE-089/SqlPqxxTainted.ql
+++ /dev/null
@@ -1,113 +0,0 @@
-/**
- * @name Uncontrolled data in SQL query to Postgres
- * @description Including user-supplied data in a SQL query to Postgres
- * without neutralizing special elements can make code
- * vulnerable to SQL Injection.
- * @kind path-problem
- * @problem.severity error
- * @precision high
- * @id cpp/sql-injection-via-pqxx
- * @tags security
- * external/cwe/cwe-089
- */
-
-import cpp
-import semmle.code.cpp.security.Security
-import semmle.code.cpp.dataflow.TaintTracking
-import DataFlow::PathGraph
-
-predicate pqxxTransationClassNames(string className, string namespace) {
- namespace = "pqxx" and
- className in [
- "dbtransaction", "nontransaction", "basic_robusttransaction", "robusttransaction",
- "subtransaction", "transaction", "basic_transaction", "transaction_base", "work"
- ]
-}
-
-predicate pqxxConnectionClassNames(string className, string namespace) {
- namespace = "pqxx" and
- className in ["connection_base", "basic_connection", "connection"]
-}
-
-predicate pqxxTransactionSqlArgument(string function, int arg) {
- function = "exec" and arg = 0
- or
- function = "exec0" and arg = 0
- or
- function = "exec1" and arg = 0
- or
- function = "exec_n" and arg = 1
- or
- function = "exec_params" and arg = 0
- or
- function = "exec_params0" and arg = 0
- or
- function = "exec_params1" and arg = 0
- or
- function = "exec_params_n" and arg = 1
- or
- function = "query_value" and arg = 0
- or
- function = "stream" and arg = 0
-}
-
-predicate pqxxConnectionSqlArgument(string function, int arg) { function = "prepare" and arg = 1 }
-
-Expr getPqxxSqlArgument() {
- exists(FunctionCall fc, Expr e, int argIndex, UserType t |
- // examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'.
- e = fc.getQualifier() and
- // to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior
- // and return pointer to a connection/transation object
- e.getType().refersTo(t) and
- // transaction exec and connection prepare variations
- (
- pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) and
- pqxxTransactionSqlArgument(fc.getTarget().getName(), argIndex)
- or
- pqxxConnectionClassNames(t.getName(), t.getNamespace().getName()) and
- pqxxConnectionSqlArgument(fc.getTarget().getName(), argIndex)
- ) and
- result = fc.getArgument(argIndex)
- )
-}
-
-predicate pqxxEscapeArgument(string function, int arg) {
- arg = 0 and
- function in ["esc", "esc_raw", "quote", "quote_raw", "quote_name", "quote_table", "esc_like"]
-}
-
-predicate isEscapedPqxxArgument(Expr argExpr) {
- exists(FunctionCall fc, Expr e, int argIndex, UserType t |
- // examples: 'work' for 'work.exec(...)'; '->' for 'tx->exec()'.
- e = fc.getQualifier() and
- // to find ConnectionHandle/TransationHandle and similar classes which override '->' operator behavior
- // and return pointer to a connection/transation object
- e.getType().refersTo(t) and
- // transaction and connection escape functions
- (
- pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) or
- pqxxConnectionClassNames(t.getName(), t.getNamespace().getName())
- ) and
- pqxxEscapeArgument(fc.getTarget().getName(), argIndex) and
- // is escaped arg == argExpr
- argExpr = fc.getArgument(argIndex)
- )
-}
-
-class Configuration extends TaintTracking::Configuration {
- Configuration() { this = "SqlPqxxTainted" }
-
- override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) }
-
- override predicate isSink(DataFlow::Node sink) { sink.asExpr() = getPqxxSqlArgument() }
-
- override predicate isSanitizer(DataFlow::Node node) { isEscapedPqxxArgument(node.asExpr()) }
-}
-
-from DataFlow::PathNode source, DataFlow::PathNode sink, Configuration config, string taintCause
-where
- config.hasFlowPath(source, sink) and
- isUserInput(source.getNode().asExpr(), taintCause)
-select sink, source, sink, "This argument to a SQL query function is derived from $@", source,
- "user input (" + taintCause + ")"
From 90fe5c5aca878225ad9275a12eaa96f12e1db49b Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 22 Jun 2021 15:59:43 +0200
Subject: [PATCH 032/459] C++: Add change-note.
---
cpp/change-notes/2021-06-22-sql-tainted.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 cpp/change-notes/2021-06-22-sql-tainted.md
diff --git a/cpp/change-notes/2021-06-22-sql-tainted.md b/cpp/change-notes/2021-06-22-sql-tainted.md
new file mode 100644
index 00000000000..004f96ce25b
--- /dev/null
+++ b/cpp/change-notes/2021-06-22-sql-tainted.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* The 'Uncontrolled data in SQL query' (cpp/sql-injection) query now supports the `libpqxx` library.
\ No newline at end of file
From 90633b9ce1317c876786f625bd271afb227e8a61 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Wed, 23 Jun 2021 11:31:28 +0200
Subject: [PATCH 033/459] C++: Make the new SQL abstract classes extend
'Function' instead. This is more in line with how we model
RemoteFlowFunction.
---
cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql | 7 +-
cpp/ql/src/semmle/code/cpp/models/Models.qll | 3 +
.../code/cpp/models/implementations/MySql.qll | 8 +
.../cpp/models/implementations/PostgreSql.qll | 93 ++++++++++++
.../cpp/models/implementations/SqLite3.qll | 8 +
.../semmle/code/cpp/models/interfaces/Sql.qll | 30 ++++
.../src/semmle/code/cpp/security/Security.qll | 8 +-
cpp/ql/src/semmle/code/cpp/security/Sql.qll | 140 ------------------
.../CWE-089/SqlTainted/SqlTainted.expected | 36 ++---
9 files changed, 167 insertions(+), 166 deletions(-)
create mode 100644 cpp/ql/src/semmle/code/cpp/models/implementations/MySql.qll
create mode 100644 cpp/ql/src/semmle/code/cpp/models/implementations/PostgreSql.qll
create mode 100644 cpp/ql/src/semmle/code/cpp/models/implementations/SqLite3.qll
create mode 100644 cpp/ql/src/semmle/code/cpp/models/interfaces/Sql.qll
delete mode 100644 cpp/ql/src/semmle/code/cpp/security/Sql.qll
diff --git a/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql b/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
index b75371c705c..c108d57db23 100644
--- a/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
+++ b/cpp/ql/src/Security/CWE/CWE-089/SqlTainted.ql
@@ -16,7 +16,6 @@ import cpp
import semmle.code.cpp.security.Security
import semmle.code.cpp.security.FunctionWithWrappers
import semmle.code.cpp.security.TaintTracking
-import semmle.code.cpp.security.Sql
import TaintedWithPath
class SQLLikeFunction extends FunctionWithWrappers {
@@ -35,10 +34,10 @@ class Configuration extends TaintTrackingConfiguration {
or
e.getUnspecifiedType() instanceof IntegralType
or
- exists(SqlFunctionality sql, int arg, Function func, FunctionInput input |
- e = func.getACallToThisFunction().getArgument(arg) and
+ exists(SqlBarrier sqlFunc, int arg, FunctionInput input |
+ e = sqlFunc.getACallToThisFunction().getArgument(arg) and
input.isParameterDeref(arg) and
- sql.getAnEscapedParameter(func, input, _)
+ sqlFunc.getAnEscapedParameter(input, _)
)
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/Models.qll b/cpp/ql/src/semmle/code/cpp/models/Models.qll
index d5c7f50dde1..f6e40d140bd 100644
--- a/cpp/ql/src/semmle/code/cpp/models/Models.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/Models.qll
@@ -33,3 +33,6 @@ private import implementations.Recv
private import implementations.Accept
private import implementations.Poll
private import implementations.Select
+private import implementations.MySql
+private import implementations.SqLite3
+private import implementations.PostgreSql
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/MySql.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/MySql.qll
new file mode 100644
index 00000000000..58d0ebf8a70
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/MySql.qll
@@ -0,0 +1,8 @@
+private import semmle.code.cpp.models.interfaces.Sql
+private import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
+
+private class MySqlSink extends SqlSink {
+ MySqlSink() { this.hasName(["mysql_query", "mysql_real_query"]) }
+
+ override predicate getAnSqlParameter(FunctionInput input) { input.isParameterDeref(1) }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/PostgreSql.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/PostgreSql.qll
new file mode 100644
index 00000000000..b7c11ffc0e2
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/PostgreSql.qll
@@ -0,0 +1,93 @@
+private import semmle.code.cpp.models.interfaces.Sql
+private import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
+
+private predicate pqxxTransactionSqlArgument(string function, int arg) {
+ function = "exec" and arg = 0
+ or
+ function = "exec0" and arg = 0
+ or
+ function = "exec1" and arg = 0
+ or
+ function = "exec_n" and arg = 1
+ or
+ function = "exec_params" and arg = 0
+ or
+ function = "exec_params0" and arg = 0
+ or
+ function = "exec_params1" and arg = 0
+ or
+ function = "exec_params_n" and arg = 1
+ or
+ function = "query_value" and arg = 0
+ or
+ function = "stream" and arg = 0
+}
+
+private predicate pqxxConnectionSqlArgument(string function, int arg) {
+ function = "prepare" and arg = 1
+}
+
+private predicate pqxxTransationClassNames(string className, string namespace) {
+ namespace = "pqxx" and
+ className in [
+ "dbtransaction", "nontransaction", "basic_robusttransaction", "robusttransaction",
+ "subtransaction", "transaction", "basic_transaction", "transaction_base", "work"
+ ]
+}
+
+private predicate pqxxConnectionClassNames(string className, string namespace) {
+ namespace = "pqxx" and
+ className in ["connection_base", "basic_connection", "connection"]
+}
+
+private predicate pqxxEscapeArgument(string function, int arg) {
+ arg = 0 and
+ function in ["esc", "esc_raw", "quote", "quote_raw", "quote_name", "quote_table", "esc_like"]
+}
+
+private class PostgreSqlSink extends SqlSink {
+ PostgreSqlSink() {
+ exists(Class c |
+ this.getDeclaringType() = c and
+ // transaction exec and connection prepare variations
+ (
+ pqxxTransationClassNames(c.getName(), c.getNamespace().getName()) and
+ pqxxTransactionSqlArgument(this.getName(), _)
+ or
+ pqxxConnectionSqlArgument(this.getName(), _) and
+ pqxxConnectionClassNames(c.getName(), c.getNamespace().getName())
+ )
+ )
+ }
+
+ override predicate getAnSqlParameter(FunctionInput input) {
+ exists(int argIndex |
+ pqxxTransactionSqlArgument(this.getName(), argIndex)
+ or
+ pqxxConnectionSqlArgument(this.getName(), argIndex)
+ |
+ input.isParameterDeref(argIndex)
+ )
+ }
+}
+
+private class PostgreSqlBarrier extends SqlBarrier {
+ PostgreSqlBarrier() {
+ exists(Class c |
+ this.getDeclaringType() = c and
+ // transaction and connection escape functions
+ (
+ pqxxTransationClassNames(c.getName(), c.getNamespace().getName()) or
+ pqxxConnectionClassNames(c.getName(), c.getNamespace().getName())
+ ) and
+ pqxxEscapeArgument(this.getName(), _)
+ )
+ }
+
+ override predicate getAnEscapedParameter(FunctionInput input, FunctionOutput output) {
+ exists(int argIndex |
+ input.isParameterDeref(argIndex) and
+ output.isReturnValueDeref()
+ )
+ }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/SqLite3.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/SqLite3.qll
new file mode 100644
index 00000000000..51e8ace9f43
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/SqLite3.qll
@@ -0,0 +1,8 @@
+private import semmle.code.cpp.models.interfaces.Sql
+private import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
+
+private class SqLite3Sink extends SqlSink {
+ SqLite3Sink() { this.hasName("sqlite3_exec") }
+
+ override predicate getAnSqlParameter(FunctionInput input) { input.isParameterDeref(1) }
+}
diff --git a/cpp/ql/src/semmle/code/cpp/models/interfaces/Sql.qll b/cpp/ql/src/semmle/code/cpp/models/interfaces/Sql.qll
new file mode 100644
index 00000000000..8eae36f5972
--- /dev/null
+++ b/cpp/ql/src/semmle/code/cpp/models/interfaces/Sql.qll
@@ -0,0 +1,30 @@
+/**
+ * Provides abstract classes for modeling functions that execute and escape SQL query strings.
+ * To use this QL library, create a QL class extending `SqlSink` or `SqlBarrier` with a
+ * characteristic predicate that selects the function or set of functions you are modeling.
+ * Within that class, override the predicates provided by the class to match the way a
+ * parameter flows into the function and, in the case of `SqlBarrier`, out of the function.
+ */
+
+private import cpp
+
+/**
+ * An abstract class that represents a function that executes an SQL query.
+ */
+abstract class SqlSink extends Function {
+ /**
+ * Holds if `input` to this function represents data that is passed to an SQL server.
+ */
+ abstract predicate getAnSqlParameter(FunctionInput input);
+}
+
+/**
+ * An abstract class that represents a function that escapes an SQL query string.
+ */
+abstract class SqlBarrier extends Function {
+ /**
+ * Holds if the `output` escapes the SQL input `input` such that is it safe to pass to
+ * an `SqlSink`.
+ */
+ abstract predicate getAnEscapedParameter(FunctionInput input, FunctionOutput output);
+}
diff --git a/cpp/ql/src/semmle/code/cpp/security/Security.qll b/cpp/ql/src/semmle/code/cpp/security/Security.qll
index ac1b5e9f280..9927d3397cf 100644
--- a/cpp/ql/src/semmle/code/cpp/security/Security.qll
+++ b/cpp/ql/src/semmle/code/cpp/security/Security.qll
@@ -7,7 +7,7 @@ import semmle.code.cpp.exprs.Expr
import semmle.code.cpp.commons.Environment
import semmle.code.cpp.security.SecurityOptions
import semmle.code.cpp.models.interfaces.FlowSource
-private import Sql
+import semmle.code.cpp.models.interfaces.Sql
/**
* Extend this class to customize the security queries for
@@ -35,10 +35,10 @@ class SecurityOptions extends string {
* An argument to a function that is passed to a SQL server.
*/
predicate sqlArgument(string function, int arg) {
- exists(Function func, FunctionInput input, SqlFunctionality sql |
- func.hasName(function) and
+ exists(FunctionInput input, SqlSink sqlSink |
+ sqlSink.hasName(function) and
input.isParameterDeref(arg) and
- sql.getAnSqlParameter(func, input)
+ sqlSink.getAnSqlParameter(input)
)
}
diff --git a/cpp/ql/src/semmle/code/cpp/security/Sql.qll b/cpp/ql/src/semmle/code/cpp/security/Sql.qll
deleted file mode 100644
index cd94dd0995d..00000000000
--- a/cpp/ql/src/semmle/code/cpp/security/Sql.qll
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * This file provides classes for working with various SQL libraries and frameworks.
- */
-
-private import cpp
-private import semmle.code.cpp.models.interfaces.FunctionInputsAndOutputs
-
-/**
- * An abstract class that represents SQL parameters and escaping functions.
- *
- * To add support for a new SQL framework, extend this class with
- * a subclass whose characteristic predicate is a unique singleton string.
- * For example, write
- *
- * ```ql
- * class MySqlFunctionality extends SqlFunctionality {
- * MySqlFunctionality() { this = "MySqlFunctionality" }
- * // Override `getAnSqlParameter`.
- * // Optionally override `getAnEscapedParameter`.
- * }
- * ```
- */
-abstract class SqlFunctionality extends string {
- bindingset[this]
- SqlFunctionality() { any() }
-
- /**
- * Holds if `input` to the function `func` represents data that is passed to an SQL server.
- */
- abstract predicate getAnSqlParameter(Function func, FunctionInput input);
-
- /**
- * Holds if the `output` from `func` escapes the SQL input `input` such that is it safe to pass to
- * an SQL server.
- */
- predicate getAnEscapedParameter(Function func, FunctionInput input, FunctionOutput output) {
- none()
- }
-}
-
-private class MySqlFunctionality extends SqlFunctionality {
- MySqlFunctionality() { this = "MySqlFunctionality" }
-
- override predicate getAnSqlParameter(Function func, FunctionInput input) {
- func.hasName(["mysql_query", "mysql_real_query"]) and
- input.isParameterDeref(1)
- }
-}
-
-private class SqLite3Functionality extends SqlFunctionality {
- SqLite3Functionality() { this = "SqLite3Functionality" }
-
- override predicate getAnSqlParameter(Function func, FunctionInput input) {
- func.hasName("sqlite3_exec") and
- input.isParameterDeref(1)
- }
-}
-
-private module PostgreSql {
- private predicate pqxxTransactionSqlArgument(string function, int arg) {
- function = "exec" and arg = 0
- or
- function = "exec0" and arg = 0
- or
- function = "exec1" and arg = 0
- or
- function = "exec_n" and arg = 1
- or
- function = "exec_params" and arg = 0
- or
- function = "exec_params0" and arg = 0
- or
- function = "exec_params1" and arg = 0
- or
- function = "exec_params_n" and arg = 1
- or
- function = "query_value" and arg = 0
- or
- function = "stream" and arg = 0
- }
-
- private predicate pqxxConnectionSqlArgument(string function, int arg) {
- function = "prepare" and arg = 1
- }
-
- private predicate pqxxTransationClassNames(string className, string namespace) {
- namespace = "pqxx" and
- className in [
- "dbtransaction", "nontransaction", "basic_robusttransaction", "robusttransaction",
- "subtransaction", "transaction", "basic_transaction", "transaction_base", "work"
- ]
- }
-
- private predicate pqxxConnectionClassNames(string className, string namespace) {
- namespace = "pqxx" and
- className in ["connection_base", "basic_connection", "connection"]
- }
-
- private predicate pqxxEscapeArgument(string function, int arg) {
- arg = 0 and
- function in ["esc", "esc_raw", "quote", "quote_raw", "quote_name", "quote_table", "esc_like"]
- }
-
- class PostgreSqlFunctionality extends SqlFunctionality {
- PostgreSqlFunctionality() { this = "PostgreSqlFunctionality" }
-
- override predicate getAnSqlParameter(Function func, FunctionInput input) {
- exists(int argIndex, UserType t |
- func.getDeclaringType() = t and
- // transaction exec and connection prepare variations
- (
- pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) and
- pqxxTransactionSqlArgument(func.getName(), argIndex)
- or
- pqxxConnectionClassNames(t.getName(), t.getNamespace().getName()) and
- pqxxConnectionSqlArgument(func.getName(), argIndex)
- ) and
- input.isParameterDeref(argIndex)
- )
- }
-
- override predicate getAnEscapedParameter(
- Function func, FunctionInput input, FunctionOutput output
- ) {
- exists(int argIndex, UserType t |
- func.getDeclaringType() = t and
- // transaction and connection escape functions
- (
- pqxxTransationClassNames(t.getName(), t.getNamespace().getName()) or
- pqxxConnectionClassNames(t.getName(), t.getNamespace().getName())
- ) and
- pqxxEscapeArgument(func.getName(), argIndex) and
- input.isParameterDeref(argIndex) and
- output.isReturnValueDeref()
- )
- }
- }
-}
-
-private import PostgreSql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
index 9c16adc2a11..744598ed70b 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-089/SqlTainted/SqlTainted.expected
@@ -5,14 +5,14 @@ edges
| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 |
| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 indirection |
| test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 indirection |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | (const char *)... |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | (const char *)... |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array indirection |
-| test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array indirection |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | (const char *)... |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | (const char *)... |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array indirection |
+| test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array indirection |
nodes
| test.c:15:20:15:23 | argv | semmle.label | argv |
| test.c:15:20:15:23 | argv | semmle.label | argv |
@@ -21,15 +21,15 @@ nodes
| test.c:21:18:21:23 | query1 | semmle.label | query1 |
| test.c:21:18:21:23 | query1 indirection | semmle.label | query1 indirection |
| test.c:21:18:21:23 | query1 indirection | semmle.label | query1 indirection |
-| test.cpp:44:27:44:30 | argv | semmle.label | argv |
-| test.cpp:44:27:44:30 | argv | semmle.label | argv |
-| test.cpp:44:27:44:33 | (const char *)... | semmle.label | (const char *)... |
-| test.cpp:44:27:44:33 | (const char *)... | semmle.label | (const char *)... |
-| test.cpp:44:27:44:33 | access to array | semmle.label | access to array |
-| test.cpp:44:27:44:33 | access to array | semmle.label | access to array |
-| test.cpp:44:27:44:33 | access to array | semmle.label | access to array |
-| test.cpp:44:27:44:33 | access to array indirection | semmle.label | access to array indirection |
-| test.cpp:44:27:44:33 | access to array indirection | semmle.label | access to array indirection |
+| test.cpp:43:27:43:30 | argv | semmle.label | argv |
+| test.cpp:43:27:43:30 | argv | semmle.label | argv |
+| test.cpp:43:27:43:33 | (const char *)... | semmle.label | (const char *)... |
+| test.cpp:43:27:43:33 | (const char *)... | semmle.label | (const char *)... |
+| test.cpp:43:27:43:33 | access to array | semmle.label | access to array |
+| test.cpp:43:27:43:33 | access to array | semmle.label | access to array |
+| test.cpp:43:27:43:33 | access to array | semmle.label | access to array |
+| test.cpp:43:27:43:33 | access to array indirection | semmle.label | access to array indirection |
+| test.cpp:43:27:43:33 | access to array indirection | semmle.label | access to array indirection |
#select
| test.c:21:18:21:23 | query1 | test.c:15:20:15:23 | argv | test.c:21:18:21:23 | query1 | This argument to a SQL query function is derived from $@ and then passed to mysql_query(sqlArg) | test.c:15:20:15:23 | argv | user input (argv) |
-| test.cpp:44:27:44:33 | access to array | test.cpp:44:27:44:30 | argv | test.cpp:44:27:44:33 | access to array | This argument to a SQL query function is derived from $@ and then passed to pqxx::work::exec1((unnamed parameter 0)) | test.cpp:44:27:44:30 | argv | user input (argv) |
+| test.cpp:43:27:43:33 | access to array | test.cpp:43:27:43:30 | argv | test.cpp:43:27:43:33 | access to array | This argument to a SQL query function is derived from $@ and then passed to pqxx::work::exec1((unnamed parameter 0)) | test.cpp:43:27:43:30 | argv | user input (argv) |
From df8a6b984ac54c6fe553fe987de60c85e73b1bc4 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 13 Jul 2021 17:37:33 +0000
Subject: [PATCH 034/459] Python: Add `import *` tests
Moves the current test out of `test.py`, as otherwise any unknown global
(like, say, `sink`) would _also_ be considered to be something
potentially defined in `unknown`.
---
.../experimental/dataflow/ApiGraphs/test.py | 6 -----
.../dataflow/ApiGraphs/test_import_star.py | 23 +++++++++++++++++++
.../dataflow/ApiGraphs/test_import_star2.py | 15 ++++++++++++
3 files changed, 38 insertions(+), 6 deletions(-)
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test_import_star2.py
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test.py b/python/ql/test/experimental/dataflow/ApiGraphs/test.py
index f4988e41a0f..084ada0e801 100644
--- a/python/ql/test/experimental/dataflow/ApiGraphs/test.py
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test.py
@@ -74,12 +74,6 @@ def f():
change_foo()
sink(foo) #$ use=moduleImport("danger").getMember("SOURCE")
-# Star imports
-
-from unknown import * #$ use=moduleImport("unknown")
-
-hello() #$ MISSING: use=moduleImport("unknown").getMember("hello").getReturn()
-
# Subclasses
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
new file mode 100644
index 00000000000..5dbf03cb294
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
@@ -0,0 +1,23 @@
+# Star imports
+
+from unknown import * #$ use=moduleImport("unknown")
+
+# Currently missing, as we do not consider `hello` to be a `LocalSourceNode`, since it has flow
+# going into it from its corresponding `GlobalSsaVariable`.
+hello() #$ MISSING: use=moduleImport("unknown").getMember("hello").getReturn()
+
+non_module_member
+
+outer_bar = 5
+outer_bar
+
+def foo():
+ world() #$ use=moduleImport("unknown").getMember("world").getReturn()
+ bar = 5
+ bar
+ non_module_member
+ print(bar) #$ use=moduleImport("builtins").getMember("print").getReturn()
+
+def quux():
+ global non_module_member
+ non_module_member = 5
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star2.py b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star2.py
new file mode 100644
index 00000000000..a73f1f43548
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star2.py
@@ -0,0 +1,15 @@
+# Star imports in local scope
+
+hello2()
+
+def foo():
+ from unknown2 import * #$ use=moduleImport("unknown2")
+ world2() #$ use=moduleImport("unknown2").getMember("world2").getReturn()
+ bar2 = 5
+ bar2
+ non_module_member2
+ print(bar2) #$ use=moduleImport("builtins").getMember("print").getReturn()
+
+def quux2():
+ global non_module_member2
+ non_module_member2 = 5
From 8b6b4dde6924fd6660ec792611c2825cfd79ad4b Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 13 Jul 2021 18:20:25 +0000
Subject: [PATCH 035/459] Python: Refactor built-ins logic
This will make it possible to reuse for names defined in `import *`.
---
python/ql/src/semmle/python/ApiGraphs.qll | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 2969d317576..4f45bedd8da 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -424,13 +424,8 @@ module API {
* a value in the module `m`.
*/
private predicate possible_builtin_defined_in_module(string name, Module m) {
- exists(NameNode n |
- not exists(LocalVariable v | n.defines(v)) and
- n.isStore() and
- name = n.getId() and
- name = getBuiltInName() and
- m = n.getEnclosingModule()
- )
+ global_name_defined_in_module(name, m) and
+ name = getBuiltInName()
}
/**
@@ -445,6 +440,16 @@ module API {
m = n.getEnclosingModule()
}
+ /** Holds if a global variable called `name` is assigned a value in the module `m`. */
+ private predicate global_name_defined_in_module(string name, Module m) {
+ exists(NameNode n |
+ not exists(LocalVariable v | n.defines(v)) and
+ n.isStore() and
+ name = n.getId() and
+ m = n.getEnclosingModule()
+ )
+ }
+
/**
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
* `lbl` in the API graph.
From c3789811c86527cbcad4064055ab542ddafc2a2b Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 13 Jul 2021 18:22:51 +0000
Subject: [PATCH 036/459] Python: Support `import *` in API graphs
---
python/ql/src/semmle/python/ApiGraphs.qll | 44 +++++++++++++++++++++++
1 file changed, 44 insertions(+)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 4f45bedd8da..e0b326958a1 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -440,6 +440,23 @@ module API {
m = n.getEnclosingModule()
}
+ /**
+ * Holds if `n` is an access of a variable called `name` (which is _not_ the name of a
+ * built-in, and which is _not_ a global defined in the enclosing module) inside the scope `s`.
+ */
+ private predicate name_possibly_defined_in_import_star(NameNode n, string name, Scope s) {
+ n.isLoad() and
+ name = n.getId() and
+ // Not already defined in an enclosing scope.
+ not exists(LocalVariable v |
+ v.getId() = name and v.getScope().getEnclosingScope*() = n.getScope()
+ ) and
+ not name = getBuiltInName() and
+ s = n.getScope().getEnclosingScope*() and
+ exists(potential_import_star_base(s)) and
+ not global_name_defined_in_module(name, n.getEnclosingModule())
+ }
+
/** Holds if a global variable called `name` is assigned a value in the module `m`. */
private predicate global_name_defined_in_module(string name, Module m) {
exists(NameNode n |
@@ -450,6 +467,24 @@ module API {
)
}
+ /**
+ * Gets the API graph node for all modules imported with `from ... import *` inside the scope `s`.
+ *
+ * For example, given
+ *
+ * `from foo.bar import *`
+ *
+ * this would be the API graph node with the path
+ *
+ * `moduleImport("foo").getMember("bar")`
+ */
+ private TApiNode potential_import_star_base(Scope s) {
+ exists(DataFlow::Node ref |
+ ref.asCfgNode() = any(ImportStarNode n | n.getScope() = s).getModule() and
+ use(result, ref)
+ )
+ }
+
/**
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
* `lbl` in the API graph.
@@ -492,6 +527,15 @@ module API {
// Built-ins, treated as members of the module `builtins`
base = MkModuleImport("builtins") and
lbl = Label::member(any(string name | ref = likely_builtin(name)))
+ or
+ // Unknown variables that may belong to a module imported with `import *`
+ exists(Scope s |
+ base = potential_import_star_base(s) and
+ lbl =
+ Label::member(any(string name |
+ name_possibly_defined_in_import_star(ref.asCfgNode(), name, s)
+ ))
+ )
}
/**
From ec9063b4a52a8dde26256232c3c1b686ba63456d Mon Sep 17 00:00:00 2001
From: Taus
Date: Wed, 14 Jul 2021 13:52:32 +0000
Subject: [PATCH 037/459] Python: Add test case for github/codeql#6227
---
.../generators/UnguardedNextInGenerator.expected | 1 +
python/ql/test/query-tests/Exceptions/generators/test.py | 9 +++++++++
2 files changed, 10 insertions(+)
diff --git a/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected
index 289b8fb5a0d..f9772afb8b7 100644
--- a/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected
+++ b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected
@@ -1,2 +1,3 @@
| test.py:5:15:5:22 | ControlFlowNode for next() | Call to next() in a generator |
| test.py:10:20:10:27 | ControlFlowNode for next() | Call to next() in a generator |
+| test.py:62:19:62:28 | ControlFlowNode for next() | Call to next() in a generator |
diff --git a/python/ql/test/query-tests/Exceptions/generators/test.py b/python/ql/test/query-tests/Exceptions/generators/test.py
index 7b7ef8c7fe2..e8b3f0b2b34 100644
--- a/python/ql/test/query-tests/Exceptions/generators/test.py
+++ b/python/ql/test/query-tests/Exceptions/generators/test.py
@@ -53,3 +53,12 @@ def ok5(seq):
def ok6(seq):
yield next(iter([]), default='foo')
+
+# Handling for multiple exception types, one of which is `StopIteration`
+# Reported as a false positive in github/codeql#6227
+def ok7(seq, ctx):
+ try:
+ with ctx:
+ yield next(iter)
+ except (StopIteration, MemoryError):
+ return
From 5a9fca48e8f65c09cf92e297fff4dd9c50a10efe Mon Sep 17 00:00:00 2001
From: Taus
Date: Wed, 14 Jul 2021 13:53:11 +0000
Subject: [PATCH 038/459] Python: Fix `ExceptStmt::getType`
We were not supporting `except` statements handling multiple exception
types (specified as a tuple) correctly, instead just returning the
tuple itself as the "type" (which makes little sense).
To fix this, we explicitly extract the elements of this node, in the
case where it _is_ a tuple.
This is a change that can potentially affect many queries (as `getType`
is used in quite a few places), so some care should be taken to
ensure that this does not adversely affect performance.
---
python/ql/src/semmle/python/Stmts.qll | 6 ++++++
.../Exceptions/generators/UnguardedNextInGenerator.expected | 1 -
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/Stmts.qll b/python/ql/src/semmle/python/Stmts.qll
index 0aa34c2a3fe..56612ef7283 100644
--- a/python/ql/src/semmle/python/Stmts.qll
+++ b/python/ql/src/semmle/python/Stmts.qll
@@ -153,6 +153,12 @@ class ExceptStmt extends ExceptStmt_ {
override Stmt getASubStatement() { result = this.getAStmt() }
override Stmt getLastStatement() { result = this.getBody().getLastItem().getLastStatement() }
+
+ override Expr getType() {
+ result = super.getType() and not result instanceof Tuple
+ or
+ result = super.getType().(Tuple).getAnElt()
+ }
}
/** An assert statement, such as `assert a == b, "A is not equal to b"` */
diff --git a/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected
index f9772afb8b7..289b8fb5a0d 100644
--- a/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected
+++ b/python/ql/test/query-tests/Exceptions/generators/UnguardedNextInGenerator.expected
@@ -1,3 +1,2 @@
| test.py:5:15:5:22 | ControlFlowNode for next() | Call to next() in a generator |
| test.py:10:20:10:27 | ControlFlowNode for next() | Call to next() in a generator |
-| test.py:62:19:62:28 | ControlFlowNode for next() | Call to next() in a generator |
From 04940a11052d04e2b452e3bac86879aa5eaaa6e7 Mon Sep 17 00:00:00 2001
From: mr-sherman <77112096+mr-sherman@users.noreply.github.com>
Date: Wed, 14 Jul 2021 15:54:28 -0400
Subject: [PATCH 039/459] Create 2021-07-14-service-stack-support.md
---
csharp/change-notes/2021-07-14-service-stack-support.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 csharp/change-notes/2021-07-14-service-stack-support.md
diff --git a/csharp/change-notes/2021-07-14-service-stack-support.md b/csharp/change-notes/2021-07-14-service-stack-support.md
new file mode 100644
index 00000000000..bbcf868ffc9
--- /dev/null
+++ b/csharp/change-notes/2021-07-14-service-stack-support.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* added support for service stack framework with support for SQL injection, XSS and external API calls
From 3fd0ec74f08e675adebdebc7ae49ecd46bd18261 Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 16 Jul 2021 11:30:32 +0000
Subject: [PATCH 040/459] Python: Deprecate `importNode`
Unsurprisingly, the only thing affected by this was the `import-helper`
tests. These have lost all of the results relating to `ImportMember`s,
but apart from that the underlying behaviour should be the same.
I also limited the test to only `CfgNode`s, as a bunch of `EssaNode`s
suddenly appeared when I switched to API graphs.
Finally, I used `API::moduleImport` with a dotted name in the type
tracking tests. This goes against the API graphs interface, but I think
it's more correct for this use case, as these type trackers are doing
the "module attribute lookup" bit manually.
---
.../2021-07-16-deprecate-importnode.md | 4 ++++
.../dataflow/new/internal/DataFlowUtil.qll | 6 +++++-
.../import-helper/ImportHelper.expected | 19 ++++++++++++-------
.../dataflow/import-helper/ImportHelper.ql | 5 ++++-
.../dataflow/typetracking/moduleattr.ql | 3 ++-
.../dataflow/typetracking/tracked.ql | 7 ++++---
6 files changed, 31 insertions(+), 13 deletions(-)
create mode 100644 python/change-notes/2021-07-16-deprecate-importnode.md
diff --git a/python/change-notes/2021-07-16-deprecate-importnode.md b/python/change-notes/2021-07-16-deprecate-importnode.md
new file mode 100644
index 00000000000..4acd1243943
--- /dev/null
+++ b/python/change-notes/2021-07-16-deprecate-importnode.md
@@ -0,0 +1,4 @@
+lgtm,codescanning
+* The `importNode` predicate from the data-flow library has been deprecated. In its place, we
+ recommend using the API graphs library, accessible via `import semmle.python.ApiGraphs`.
+
\ No newline at end of file
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll
index 174564db96b..6741092ff57 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll
@@ -18,6 +18,10 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFr
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
/**
+ * DEPRECATED. Use the API graphs library instead.
+ *
+ * For a drop-in replacement, use `API::moduleImport(name).getAUse()`.
+ *
* Gets a `Node` that refers to the module referenced by `name`.
* Note that for the statement `import pkg.mod`, the new variable introduced is `pkg` that is a
* reference to the module `pkg`.
@@ -37,7 +41,7 @@ predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
* `mypkg/foo.py` but the variable `foo` containing `42` -- however, `import mypkg.foo` will always cause `mypkg.foo`
* to refer to the module.
*/
-Node importNode(string name) {
+deprecated Node importNode(string name) {
exists(Variable var, Import imp, Alias alias |
alias = imp.getAName() and
alias.getAsname() = var.getAStore() and
diff --git a/python/ql/test/experimental/dataflow/import-helper/ImportHelper.expected b/python/ql/test/experimental/dataflow/import-helper/ImportHelper.expected
index 11608b04ab8..4d65bc8f960 100644
--- a/python/ql/test/experimental/dataflow/import-helper/ImportHelper.expected
+++ b/python/ql/test/experimental/dataflow/import-helper/ImportHelper.expected
@@ -1,23 +1,28 @@
| test1.py:1:8:1:12 | ControlFlowNode for ImportExpr | mypkg |
+| test1.py:2:7:2:11 | ControlFlowNode for mypkg | mypkg |
+| test1.py:4:11:4:15 | ControlFlowNode for mypkg | mypkg |
| test2.py:1:6:1:10 | ControlFlowNode for ImportExpr | mypkg |
| test2.py:1:6:1:10 | ControlFlowNode for ImportExpr | mypkg |
-| test2.py:1:19:1:21 | ControlFlowNode for ImportMember | mypkg.foo |
-| test2.py:1:24:1:26 | ControlFlowNode for ImportMember | mypkg.bar |
| test3.py:1:8:1:16 | ControlFlowNode for ImportExpr | mypkg |
| test3.py:2:8:2:16 | ControlFlowNode for ImportExpr | mypkg |
+| test3.py:3:7:3:11 | ControlFlowNode for mypkg | mypkg |
+| test3.py:4:7:4:11 | ControlFlowNode for mypkg | mypkg |
| test4.py:1:8:1:16 | ControlFlowNode for ImportExpr | mypkg.foo |
| test4.py:2:8:2:16 | ControlFlowNode for ImportExpr | mypkg.bar |
+| test4.py:3:7:3:10 | ControlFlowNode for _foo | mypkg.foo |
+| test4.py:4:7:4:10 | ControlFlowNode for _bar | mypkg.bar |
| test5.py:1:8:1:12 | ControlFlowNode for ImportExpr | mypkg |
+| test5.py:3:7:3:11 | ControlFlowNode for mypkg | mypkg |
+| test5.py:5:11:5:15 | ControlFlowNode for mypkg | mypkg |
| test5.py:9:6:9:10 | ControlFlowNode for ImportExpr | mypkg |
-| test5.py:9:19:9:29 | ControlFlowNode for ImportMember | mypkg.bar |
+| test5.py:10:7:10:11 | ControlFlowNode for mypkg | mypkg |
| test6.py:1:8:1:12 | ControlFlowNode for ImportExpr | mypkg |
+| test6.py:3:7:3:11 | ControlFlowNode for mypkg | mypkg |
| test6.py:5:8:5:16 | ControlFlowNode for ImportExpr | mypkg |
+| test6.py:6:7:6:11 | ControlFlowNode for mypkg | mypkg |
| test7.py:1:6:1:10 | ControlFlowNode for ImportExpr | mypkg |
-| test7.py:1:19:1:21 | ControlFlowNode for ImportMember | mypkg.foo |
| test7.py:5:8:5:16 | ControlFlowNode for ImportExpr | mypkg |
+| test7.py:7:7:7:11 | ControlFlowNode for mypkg | mypkg |
| test7.py:9:6:9:10 | ControlFlowNode for ImportExpr | mypkg |
-| test7.py:9:19:9:21 | ControlFlowNode for ImportMember | mypkg.foo |
| test_deep.py:1:6:1:21 | ControlFlowNode for ImportExpr | start.middle.end |
| test_deep.py:1:6:1:21 | ControlFlowNode for ImportExpr | start.middle.end |
-| test_deep.py:1:30:1:32 | ControlFlowNode for ImportMember | start.middle.end.foo |
-| test_deep.py:1:35:1:37 | ControlFlowNode for ImportMember | start.middle.end.bar |
diff --git a/python/ql/test/experimental/dataflow/import-helper/ImportHelper.ql b/python/ql/test/experimental/dataflow/import-helper/ImportHelper.ql
index 0af7d88d87d..082c35c3070 100644
--- a/python/ql/test/experimental/dataflow/import-helper/ImportHelper.ql
+++ b/python/ql/test/experimental/dataflow/import-helper/ImportHelper.ql
@@ -1,4 +1,7 @@
import python
import semmle.python.dataflow.new.DataFlow
+import semmle.python.ApiGraphs
-query predicate importNode(DataFlow::Node res, string name) { res = DataFlow::importNode(name) }
+query predicate importNode(DataFlow::CfgNode res, string name) {
+ res = API::moduleImport(name).getAUse()
+}
diff --git a/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql b/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql
index 49a2d49c3e2..e578670885b 100644
--- a/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql
+++ b/python/ql/test/experimental/dataflow/typetracking/moduleattr.ql
@@ -1,10 +1,11 @@
import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TypeTracker
+import semmle.python.ApiGraphs
private DataFlow::TypeTrackingNode module_tracker(TypeTracker t) {
t.start() and
- result = DataFlow::importNode("module")
+ result = API::moduleImport("module").getAUse()
or
exists(TypeTracker t2 | result = module_tracker(t2).track(t2, t))
}
diff --git a/python/ql/test/experimental/dataflow/typetracking/tracked.ql b/python/ql/test/experimental/dataflow/typetracking/tracked.ql
index 617ba63314e..5748d660e43 100644
--- a/python/ql/test/experimental/dataflow/typetracking/tracked.ql
+++ b/python/ql/test/experimental/dataflow/typetracking/tracked.ql
@@ -2,6 +2,7 @@ import python
import semmle.python.dataflow.new.DataFlow
import semmle.python.dataflow.new.TypeTracker
import TestUtilities.InlineExpectationsTest
+import semmle.python.ApiGraphs
// -----------------------------------------------------------------------------
// tracked
@@ -119,7 +120,7 @@ class TrackedSelfTest extends InlineExpectationsTest {
/** Gets a reference to `foo` (fictive module). */
private DataFlow::TypeTrackingNode foo(DataFlow::TypeTracker t) {
t.start() and
- result = DataFlow::importNode("foo")
+ result = API::moduleImport("foo").getAUse()
or
exists(DataFlow::TypeTracker t2 | result = foo(t2).track(t2, t))
}
@@ -130,7 +131,7 @@ DataFlow::Node foo() { foo(DataFlow::TypeTracker::end()).flowsTo(result) }
/** Gets a reference to `foo.bar` (fictive module). */
private DataFlow::TypeTrackingNode foo_bar(DataFlow::TypeTracker t) {
t.start() and
- result = DataFlow::importNode("foo.bar")
+ result = API::moduleImport("foo.bar").getAUse()
or
t.startInAttr("bar") and
result = foo()
@@ -144,7 +145,7 @@ DataFlow::Node foo_bar() { foo_bar(DataFlow::TypeTracker::end()).flowsTo(result)
/** Gets a reference to `foo.bar.baz` (fictive attribute on `foo.bar` module). */
private DataFlow::TypeTrackingNode foo_bar_baz(DataFlow::TypeTracker t) {
t.start() and
- result = DataFlow::importNode("foo.bar.baz")
+ result = API::moduleImport("foo.bar.baz").getAUse()
or
t.startInAttr("baz") and
result = foo_bar()
From 4f3f93f267c6224369bdc9d5f91224ea457687db Mon Sep 17 00:00:00 2001
From: Taus
Date: Fri, 16 Jul 2021 12:22:24 +0000
Subject: [PATCH 041/459] Python: Autoformat
---
.../semmle/python/dataflow/new/internal/DataFlowUtil.qll | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll
index 6741092ff57..9bdb7ede42c 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowUtil.qll
@@ -18,10 +18,10 @@ predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFr
predicate localFlow(Node source, Node sink) { localFlowStep*(source, sink) }
/**
- * DEPRECATED. Use the API graphs library instead.
- *
+ * DEPRECATED. Use the API graphs library (`semmle.python.ApiGraphs`) instead.
+ *
* For a drop-in replacement, use `API::moduleImport(name).getAUse()`.
- *
+ *
* Gets a `Node` that refers to the module referenced by `name`.
* Note that for the statement `import pkg.mod`, the new variable introduced is `pkg` that is a
* reference to the module `pkg`.
From c6c925d67aba30f81c9f8f5538059396864fb38c Mon Sep 17 00:00:00 2001
From: Porcuiney Hairs
Date: Tue, 20 Jul 2021 03:31:30 +0530
Subject: [PATCH 042/459] Python : Improve Xpath Injection Query
---
.../Security/CWE-643-new/Xpath.ql | 36 ++++++
.../Security/CWE-643-new/XpathInjection.qll | 35 ++++++
.../XpathInjectionCustomizations.qll | 105 ++++++++++++++++++
3 files changed, 176 insertions(+)
create mode 100644 python/ql/src/experimental/Security/CWE-643-new/Xpath.ql
create mode 100644 python/ql/src/experimental/Security/CWE-643-new/XpathInjection.qll
create mode 100644 python/ql/src/experimental/Security/CWE-643-new/XpathInjectionCustomizations.qll
diff --git a/python/ql/src/experimental/Security/CWE-643-new/Xpath.ql b/python/ql/src/experimental/Security/CWE-643-new/Xpath.ql
new file mode 100644
index 00000000000..7299eaab1e1
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-643-new/Xpath.ql
@@ -0,0 +1,36 @@
+/**
+ * @name XPath query built from user-controlled sources
+ * @description Building a XPath query from user-controlled sources is vulnerable to insertion of
+ * malicious Xpath code by the user.
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id py/xpath-injection-new
+ * @tags security
+ * external/cwe/cwe-643
+ */
+
+private import python
+private import semmle.python.Concepts
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.dataflow.new.BarrierGuards
+import XpathInjection::XpathInjection
+
+class XpathInjectionConfiguration extends TaintTracking::Configuration {
+ XpathInjectionConfiguration() { this = "PathNotNormalizedConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+ // override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
+ // exists(AdditionalFlowStep af | af.isAdditionalTaintStep(node1, node2))
+ // }
+}
+
+from XpathInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink, source, sink, "This Xpath query depends on $@.", source,
+ "a user-provided value"
diff --git a/python/ql/src/experimental/Security/CWE-643-new/XpathInjection.qll b/python/ql/src/experimental/Security/CWE-643-new/XpathInjection.qll
new file mode 100644
index 00000000000..e0a0815666a
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-643-new/XpathInjection.qll
@@ -0,0 +1,35 @@
+/**
+ * Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `XpathInjection::Configuration` is needed, otherwise
+ * `XpathInjectionCustomizations` should be imported instead.
+ */
+
+private import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
+ */
+module XpathInjection {
+ import XpathInjectionCustomizations::XpathInjection
+
+ /**
+ * A taint-tracking configuration for detecting "Xpath Injection" vulnerabilities.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "Xpath Injection" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+ }
+}
diff --git a/python/ql/src/experimental/Security/CWE-643-new/XpathInjectionCustomizations.qll b/python/ql/src/experimental/Security/CWE-643-new/XpathInjectionCustomizations.qll
new file mode 100644
index 00000000000..58b6f8dad84
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-643-new/XpathInjectionCustomizations.qll
@@ -0,0 +1,105 @@
+/**
+ * Provides class and predicates to track external data that
+ * may represent malicious xpath query objects.
+ *
+ * This module is intended to be imported into a taint-tracking query.
+ */
+
+private import python
+private import semmle.python.Concepts
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.Concepts
+private import semmle.python.ApiGraphs
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.dataflow.new.BarrierGuards
+
+/** Models Xpath Injection related classes and functions */
+module XpathInjection {
+ /**
+ * A data flow source for "XPath injection" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for "XPath injection" vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for "XPath injection" vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "XPath injection" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /** Returns an API node referring to `lxml.etree` */
+ API::Node etree() { result = API::moduleImport("lxml").getMember("etree") }
+
+ /** Returns an API node referring to `lxml.etree` */
+ API::Node etreeFromString() { result = etree().getMember("fromstring") }
+
+ /** Returns an API node referring to `lxml.etree.parse` */
+ API::Node etreeParse() { result = etree().getMember("parse") }
+
+ /** Returns an API node referring to `lxml.etree.parse` */
+ API::Node libxml2parseFile() { result = API::moduleImport("libxml2").getMember("parseFile") }
+
+ /**
+ * A Sink representing an argument to `etree.XPath` or `etree.ETXpath` call.
+ *
+ * from lxml import etree
+ * root = etree.XML("")
+ * find_text = etree.XPath("`sink`")
+ * find_text = etree.ETXpath("`sink`")
+ */
+ private class EtreeXpathArgument extends Sink {
+ EtreeXpathArgument() { this = etree().getMember(["XPath", "ETXpath"]).getACall().getArg(0) }
+ }
+
+ /**
+ * A Sink representing an argument to the `etree.XPath` call.
+ *
+ * from lxml import etree
+ * root = etree.fromstring(file(XML_DB).read(), XMLParser())
+ * find_text = root.xpath("`sink`")
+ */
+ private class EtreeFromstringXpathArgument extends Sink {
+ EtreeFromstringXpathArgument() {
+ this = etreeFromString().getReturn().getMember("xpath").getACall().getArg(0)
+ }
+ }
+
+ /**
+ * A Sink representing an argument to the `xpath` call to a parsed xml document.
+ *
+ * from lxml import etree
+ * from io import StringIO
+ * f = StringIO('')
+ * tree = etree.parse(f)
+ * r = tree.xpath('`sink`')
+ */
+ private class ParseXpathArgument extends Sink {
+ ParseXpathArgument() { this = etreeParse().getReturn().getMember("xpath").getACall().getArg(0) }
+ }
+
+ /**
+ * A Sink representing an argument to the `xpathEval` call to a parsed libxml2 document.
+ *
+ * import libxml2
+ * tree = libxml2.parseFile("file.xml")
+ * r = tree.xpathEval('`sink`')
+ */
+ private class ParseFileXpathEvalArgument extends Sink {
+ ParseFileXpathEvalArgument() {
+ this = libxml2parseFile().getReturn().getMember("xpathEval").getACall().getArg(0)
+ }
+ }
+}
From f91e82678158858af60ea15b8064c6b5882dec77 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 20 Jul 2021 11:43:52 +0000
Subject: [PATCH 043/459] Python: Add test case
---
.../Variables/undefined/UninitializedLocal.expected | 1 +
.../query-tests/Variables/undefined/UninitializedLocal.py | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected
index f2782e7e888..5cea4a323dc 100644
--- a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected
+++ b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected
@@ -11,4 +11,5 @@
| UninitializedLocal.py:163:7:163:7 | x | Local variable 'x' may be used before it is initialized. |
| UninitializedLocal.py:176:16:176:16 | x | Local variable 'x' may be used before it is initialized. |
| UninitializedLocal.py:178:16:178:16 | y | Local variable 'y' may be used before it is initialized. |
+| UninitializedLocal.py:294:14:294:22 | annotated | Local variable 'annotated' may be used before it is initialized. |
| odasa3987.py:11:8:11:10 | var | Local variable 'var' may be used before it is initialized. |
diff --git a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py
index f8a8d76ad15..26e109af5a2 100644
--- a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py
+++ b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.py
@@ -288,3 +288,8 @@ def avoid_redundant_split(a):
var = False
if var:
foo.bar() #foo is defined here.
+
+def type_annotation_fp():
+ annotated : annotation = [1,2,3]
+ for x in annotated:
+ print(x)
From 8b3fa789da62cba67039a78b7e5fb355eedfcbad Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 20 Jul 2021 11:57:26 +0000
Subject: [PATCH 044/459] Python: Add `AnnAssign` `DefinitionNode`
This was a source of false positives for the
`py/uninitialized-local-variable` query, as exemplified by the test
case.
---
python/ql/src/semmle/python/Flow.qll | 5 +++++
.../Variables/undefined/UninitializedLocal.expected | 1 -
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/Flow.qll b/python/ql/src/semmle/python/Flow.qll
index 83a89b053a7..beda3cef1c4 100755
--- a/python/ql/src/semmle/python/Flow.qll
+++ b/python/ql/src/semmle/python/Flow.qll
@@ -653,6 +653,8 @@ class DefinitionNode extends ControlFlowNode {
DefinitionNode() {
exists(Assign a | a.getATarget().getAFlowNode() = this)
or
+ exists(AnnAssign a | a.getTarget().getAFlowNode() = this and exists(a.getValue()))
+ or
exists(Alias a | a.getAsname().getAFlowNode() = this)
or
augstore(_, this)
@@ -795,6 +797,9 @@ private AstNode assigned_value(Expr lhs) {
/* lhs = result */
exists(Assign a | a.getATarget() = lhs and result = a.getValue())
or
+ /* lhs : annotation = result */
+ exists(AnnAssign a | a.getTarget() = lhs and result = a.getValue())
+ or
/* import result as lhs */
exists(Alias a | a.getAsname() = lhs and result = a.getValue())
or
diff --git a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected
index 5cea4a323dc..f2782e7e888 100644
--- a/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected
+++ b/python/ql/test/query-tests/Variables/undefined/UninitializedLocal.expected
@@ -11,5 +11,4 @@
| UninitializedLocal.py:163:7:163:7 | x | Local variable 'x' may be used before it is initialized. |
| UninitializedLocal.py:176:16:176:16 | x | Local variable 'x' may be used before it is initialized. |
| UninitializedLocal.py:178:16:178:16 | y | Local variable 'y' may be used before it is initialized. |
-| UninitializedLocal.py:294:14:294:22 | annotated | Local variable 'annotated' may be used before it is initialized. |
| odasa3987.py:11:8:11:10 | var | Local variable 'var' may be used before it is initialized. |
From 233ae5a54b836970a0d8fdc2ef56438664491a85 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 20 Jul 2021 12:13:44 +0000
Subject: [PATCH 045/459] Python: Fix FP in `py/unused-local-variable`
This is only a temporary fix, as indicated by the TODO comment.
The real underlying issue is the fact that `isUnused` is defined in
terms of the underlying SSA variables (as these are only created
for variables that are actually used), and the fact that annotated
assignments are always considered to redefine their targets, which may
not actually be the case.
Thus, the correct fix would be to change the extractor to _disregard_
mere type annotations for the purposes of figuring out whether an
SSA variable should be created or not.
However, in the short term the present fix is likely sufficient.
---
python/ql/src/Variables/UnusedLocalVariable.ql | 12 ++++++++++++
.../Variables/unused/UnusedLocalVariable.expected | 1 -
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/Variables/UnusedLocalVariable.ql b/python/ql/src/Variables/UnusedLocalVariable.ql
index de83345f62d..e52c7fccaff 100644
--- a/python/ql/src/Variables/UnusedLocalVariable.ql
+++ b/python/ql/src/Variables/UnusedLocalVariable.ql
@@ -19,6 +19,7 @@ predicate unused_local(Name unused, LocalVariable v) {
def.getVariable() = v and
def.isUnused() and
not exists(def.getARedef()) and
+ not exists(annotation_without_assignment(v)) and
def.isRelevant() and
not v = any(Nonlocal n).getAVariable() and
not exists(def.getNode().getParentNode().(FunctionDef).getDefinedFunction().getADecorator()) and
@@ -26,6 +27,17 @@ predicate unused_local(Name unused, LocalVariable v) {
)
}
+/**
+ * Gets any annotation of the local variable `v` that does not also reassign its value.
+ *
+ * TODO: This predicate should not be needed. Rather, annotated "assignments" that do not actually
+ * assign a value should not result in the creation of an SSA variable (which then goes unused).
+ */
+private AnnAssign annotation_without_assignment(LocalVariable v) {
+ result.getTarget() = v.getAStore() and
+ not exists(result.getValue())
+}
+
from Name unused, LocalVariable v
where
unused_local(unused, v) and
diff --git a/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected
index 339a74432bc..a902cad04cb 100644
--- a/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected
+++ b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected
@@ -1,4 +1,3 @@
-| type_annotation_fp.py:5:5:5:7 | foo | The value assigned to local variable 'foo' is never used. |
| variables_test.py:29:5:29:5 | x | The value assigned to local variable 'x' is never used. |
| variables_test.py:89:5:89:5 | a | The value assigned to local variable 'a' is never used. |
| variables_test.py:89:7:89:7 | b | The value assigned to local variable 'b' is never used. |
From bbcbcefedcbff4cfd7a16cbfa904b42462f1ee88 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 20 Jul 2021 12:54:06 +0000
Subject: [PATCH 046/459] Python: Add false negative test case.
---
.../test/query-tests/Variables/unused/type_annotation_fp.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/python/ql/test/query-tests/Variables/unused/type_annotation_fp.py b/python/ql/test/query-tests/Variables/unused/type_annotation_fp.py
index 72293f232e5..4f10f6e333e 100644
--- a/python/ql/test/query-tests/Variables/unused/type_annotation_fp.py
+++ b/python/ql/test/query-tests/Variables/unused/type_annotation_fp.py
@@ -9,3 +9,8 @@ def type_annotation(x):
else:
foo : float
do_other_stuff_with(foo)
+
+def type_annotation_fn():
+ # False negative: the value of `bar` is never used, but this is masked by the presence of the type annotation.
+ bar = 5
+ bar : int
From e53b86fbbca889ca8fe6371b40906c185421b83a Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 20 Jul 2021 15:19:45 +0200
Subject: [PATCH 047/459] Python: Apply suggestions from code review
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/semmle/python/ApiGraphs.qll | 2 +-
.../ql/test/experimental/dataflow/ApiGraphs/test_import_star.py | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index e0b326958a1..42b699af6aa 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -449,7 +449,7 @@ module API {
name = n.getId() and
// Not already defined in an enclosing scope.
not exists(LocalVariable v |
- v.getId() = name and v.getScope().getEnclosingScope*() = n.getScope()
+ v.getId() = name and v.getScope() = n.getScope().getEnclosingScope*()
) and
not name = getBuiltInName() and
s = n.getScope().getEnclosingScope*() and
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
index 5dbf03cb294..a1d044372ae 100644
--- a/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
@@ -6,6 +6,8 @@ from unknown import * #$ use=moduleImport("unknown")
# going into it from its corresponding `GlobalSsaVariable`.
hello() #$ MISSING: use=moduleImport("unknown").getMember("hello").getReturn()
+# We don't want our analysis to think that either `non_module_member` or `outer_bar` can
+# come from `from unknown import *`
non_module_member
outer_bar = 5
From 6591a86aadc36fa0fe05f9a9f10933354ed35734 Mon Sep 17 00:00:00 2001
From: Taus
Date: Tue, 20 Jul 2021 13:22:23 +0000
Subject: [PATCH 048/459] Python: Add test cases
I debated whether to add a
`MISSING: use=moduleImport("builtins").getMember("print").getReturn()`
annotation to the last line.
Ultimately, I decided to add it, as we likely _do_ want this information
to propagate into inner functions (even if the value of `var2` may
change before `func4` is called).
---
.../dataflow/ApiGraphs/test_import_star.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
index a1d044372ae..63c2e6900fd 100644
--- a/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test_import_star.py
@@ -23,3 +23,13 @@ def foo():
def quux():
global non_module_member
non_module_member = 5
+
+def func1():
+ var() #$ use=moduleImport("unknown").getMember("var").getReturn()
+ def func2():
+ var = "FOO"
+
+def func3():
+ var2 = print #$ use=moduleImport("builtins").getMember("print")
+ def func4():
+ var2() #$ MISSING: use=moduleImport("builtins").getMember("print").getReturn()
From a34d6d390ecb8387b3c68bf79cdf9ac3eb7b4a25 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 22 Jul 2021 18:34:57 +0200
Subject: [PATCH 049/459] Port to ApiGraphs and finish the query
---
.../Security/CWE-090/LDAPInsecureAuth.ql | 192 ------------------
.../Security/CWE-522/LDAPInsecureAuth.ql | 20 ++
.../experimental/semmle/python/Concepts.qll | 23 +++
.../semmle/python/frameworks/LDAP.qll | 54 +++++
.../python/security/LDAPInsecureAuth.qll | 108 ++++++++++
.../CWE-522/LDAPInsecureAuth.expected | 39 ++++
.../Security/CWE-522/LDAPInsecureAuth.qlref | 1 +
.../Security/CWE-522/ldap2_private.py} | 0
.../Security/CWE-522/ldap2_remote.py} | 0
.../Security/CWE-522/ldap3_private.py} | 2 +-
.../Security/CWE-522/ldap3_remote.py} | 0
11 files changed, 246 insertions(+), 193 deletions(-)
delete mode 100644 python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
create mode 100644 python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
create mode 100644 python/ql/src/experimental/semmle/python/security/LDAPInsecureAuth.qll
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
create mode 100644 python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.qlref
rename python/ql/{src/experimental/Security/CWE-090/tests/2_private.py => test/experimental/query-tests/Security/CWE-522/ldap2_private.py} (100%)
rename python/ql/{src/experimental/Security/CWE-090/tests/2_remote.py => test/experimental/query-tests/Security/CWE-522/ldap2_remote.py} (100%)
rename python/ql/{src/experimental/Security/CWE-090/tests/3_private.py => test/experimental/query-tests/Security/CWE-522/ldap3_private.py} (98%)
rename python/ql/{src/experimental/Security/CWE-090/tests/3_remote.py => test/experimental/query-tests/Security/CWE-522/ldap3_remote.py} (100%)
diff --git a/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql b/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
deleted file mode 100644
index ba31bb8a287..00000000000
--- a/python/ql/src/experimental/Security/CWE-090/LDAPInsecureAuth.ql
+++ /dev/null
@@ -1,192 +0,0 @@
-/**
- * @name Python Insecure LDAP Authentication
- * @description Python LDAP Insecure LDAP Authentication
- * @kind path-problem
- * @problem.severity error
- * @id python/insecure-ldap-auth
- * @tags experimental
- * security
- * external/cwe/cwe-090
- */
-
-// Determine precision above
-import python
-import semmle.python.dataflow.new.RemoteFlowSources
-import semmle.python.dataflow.new.DataFlow
-import semmle.python.dataflow.new.TaintTracking
-import semmle.python.dataflow.new.internal.TaintTrackingPublic
-import DataFlow::PathGraph
-
-class FalseArg extends ControlFlowNode {
- FalseArg() { this.getNode().(Expr).(BooleanLiteral) instanceof False }
-}
-
-// From luchua-bc's Insecure LDAP authentication in Java (to reduce false positives)
-string getFullHostRegex() { result = "(?i)ldap://[\\[a-zA-Z0-9].*" }
-
-string getSchemaRegex() { result = "(?i)ldap(://)?" }
-
-string getPrivateHostRegex() {
- result =
- "(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?"
-}
-
-// "ldap://somethingon.theinternet.com"
-class LDAPFullHost extends StrConst {
- LDAPFullHost() {
- exists(string s |
- s = this.getText() and
- s.regexpMatch(getFullHostRegex()) and
- not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex()) // No need to check for ldaps, would be SSL by default.
- )
- }
-}
-
-class LDAPSchema extends StrConst {
- LDAPSchema() { this.getText().regexpMatch(getSchemaRegex()) }
-}
-
-class LDAPPrivateHost extends StrConst {
- LDAPPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
-}
-
-predicate concatAndCompareAgainstFullHostRegex(Expr schema, Expr host) {
- schema instanceof LDAPSchema and
- not host instanceof LDAPPrivateHost and
- exists(string full_host |
- full_host = schema.(StrConst).getText() + host.(StrConst).getText() and
- full_host.regexpMatch(getFullHostRegex())
- )
-}
-
-// "ldap://" + "somethingon.theinternet.com"
-class LDAPBothStrings extends BinaryExpr {
- LDAPBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
-}
-
-// schema + host
-class LDAPBothVar extends BinaryExpr {
- LDAPBothVar() {
- exists(SsaVariable schemaVar, SsaVariable hostVar |
- this.getLeft() = schemaVar.getVariable().getALoad() and // getAUse is incompatible with Expr
- this.getRight() = hostVar.getVariable().getALoad() and
- concatAndCompareAgainstFullHostRegex(schemaVar
- .getDefinition()
- .getImmediateDominator()
- .getNode(), hostVar.getDefinition().getImmediateDominator().getNode())
- )
- }
-}
-
-// schema + "somethingon.theinternet.com"
-class LDAPVarString extends BinaryExpr {
- LDAPVarString() {
- exists(SsaVariable schemaVar |
- this.getLeft() = schemaVar.getVariable().getALoad() and
- concatAndCompareAgainstFullHostRegex(schemaVar
- .getDefinition()
- .getImmediateDominator()
- .getNode(), this.getRight())
- )
- }
-}
-
-// "ldap://" + host
-class LDAPStringVar extends BinaryExpr {
- LDAPStringVar() {
- exists(SsaVariable hostVar |
- this.getRight() = hostVar.getVariable().getALoad() and
- concatAndCompareAgainstFullHostRegex(this.getLeft(),
- hostVar.getDefinition().getImmediateDominator().getNode())
- )
- }
-}
-
-class LDAPInsecureAuthSource extends DataFlow::Node {
- LDAPInsecureAuthSource() {
- this instanceof RemoteFlowSource or
- this.asExpr() instanceof LDAPBothStrings or
- this.asExpr() instanceof LDAPBothVar or
- this.asExpr() instanceof LDAPVarString or
- this.asExpr() instanceof LDAPStringVar
- }
-}
-
-class SafeLDAPOptions extends ControlFlowNode {
- SafeLDAPOptions() {
- this = Value::named("ldap.OPT_X_TLS_ALLOW").getAReference() or
- this = Value::named("ldap.OPT_X_TLS_TRY").getAReference() or
- this = Value::named("ldap.OPT_X_TLS_DEMAND").getAReference() or
- this = Value::named("ldap.OPT_X_TLS_HARD").getAReference()
- }
-}
-
-// LDAP3
-class LDAPInsecureAuthSink extends DataFlow::Node {
- LDAPInsecureAuthSink() {
- exists(SsaVariable connVar, CallNode connCall, SsaVariable srvVar, CallNode srvCall |
- // set connCall as a Call to ldap3.Connection
- connCall = Value::named("ldap3.Connection").getACall() and
- // get variable whose definition is a call to ldap3.Connection to correlate ldap3.Server and Connection.start_tls()
- connVar.getDefinition().getImmediateDominator() = connCall and
- // get connCall's first argument variable definition
- srvVar.getAUse() = connCall.getArg(0) and
- /*
- * // restrict srvVar definition to a ldap3.Server Call
- * srvCall = Value::named("ldap3.Server").getACall() and
- * srvVar.getDefinition().getImmediateDominator() = srvCall
- * // redundant? ldap3.Connection's first argument *must* be ldap3.Server
- */
-
- // set srvCall as srvVar definition's call
- srvVar.getDefinition().getImmediateDominator() = srvCall and
- // set ldap3.Server's 1st argument as sink
- this.asExpr() = srvCall.getArg(0).getNode() and
- (
- // check ldap3.Server call's 3rd argument (positional) is null and there's no use_ssl
- count(srvCall.getAnArg()) < 3 and
- count(srvCall.getArgByName("use_ssl")) = 0
- or
- // check ldap3.Server call's 3rd argument is False
- srvCall.getAnArg() instanceof FalseArg
- or
- // check ldap3.Server argByName "use_ssl" is False
- srvCall.getArgByName("use_ssl") instanceof FalseArg
- ) and
- /*
- * Avoid flow through any function (Server()) whose variable declaring it (srv) is the first
- * argument in any function (Connection()) whose variable declaring it also calls .start_tls
- */
-
- /*
- * host = schema + "somethingon.theinternet.com"
- * srv = Server(host, port = 1337)
- * conn = Connection(srv, "dn", "password")
- * conn.start_tls() !
- */
-
- not connVar
- .getAUse()
- .getImmediateDominator()
- .(CallNode)
- .getNode()
- .getFunc()
- .(Attribute)
- .getName()
- .matches("start_tls")
- )
- }
-}
-
-class LDAPInsecureAuthConfig extends TaintTracking::Configuration {
- LDAPInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
-
- override predicate isSource(DataFlow::Node source) { source instanceof LDAPInsecureAuthSource }
-
- override predicate isSink(DataFlow::Node sink) { sink instanceof LDAPInsecureAuthSink }
-}
-
-from LDAPInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
-where config.hasFlowPath(source, sink)
-select sink.getNode(), source, sink, "$@ from $@ is authenticated insecurely.", source.getNode(),
- "The host", sink.getNode(), "this LDAP query"
diff --git a/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql b/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
new file mode 100644
index 00000000000..9899eae932f
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Python Insecure LDAP Authentication
+ * @description Python LDAP Insecure LDAP Authentication
+ * @kind path-problem
+ * @problem.severity error
+ * @id python/insecure-ldap-auth
+ * @tags experimental
+ * security
+ * external/cwe/cwe-522
+ */
+
+// determine precision above
+import python
+import DataFlow::PathGraph
+import experimental.semmle.python.security.LDAPInsecureAuth
+
+from LDAPInsecureAuthConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ is authenticated insecurely.", sink.getNode(),
+ "This LDAP host"
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index 42af8abd64a..b848cb15381 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -156,10 +156,20 @@ module LDAPBind {
* extend `LDAPBind` instead.
*/
abstract class Range extends DataFlow::Node {
+ /**
+ * Gets the argument containing the binding host.
+ */
+ abstract DataFlow::Node getHost();
+
/**
* Gets the argument containing the binding expression.
*/
abstract DataFlow::Node getPassword();
+
+ /**
+ * Checks if the binding process use SSL.
+ */
+ abstract predicate useSSL();
}
}
@@ -174,5 +184,18 @@ class LDAPBind extends DataFlow::Node {
LDAPBind() { this = range }
+ /**
+ * Gets the argument containing the binding host.
+ */
+ DataFlow::Node getHost() { result = range.getHost() }
+
+ /**
+ * Gets the argument containing the binding expression.
+ */
DataFlow::Node getPassword() { result = range.getPassword() }
+
+ /**
+ * Checks if the binding process use SSL.
+ */
+ predicate useSSL() { range.useSSL() }
}
diff --git a/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll b/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
index 9d115959f19..ed2ba3a5817 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
@@ -99,6 +99,42 @@ private module LDAP {
override DataFlow::Node getPassword() {
result in [this.getArg(1), this.getArgByName("cred")]
}
+
+ override DataFlow::Node getHost() {
+ exists(DataFlow::CallCfgNode initialize |
+ this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() = initialize and
+ initialize = ldapInitialize().getACall() and
+ result = initialize.getArg(0)
+ )
+ }
+
+ override predicate useSSL() {
+ // use initialize to correlate `this` and so avoid FP in several instances
+ exists(DataFlow::CallCfgNode initialize |
+ this.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() = initialize and
+ initialize = ldapInitialize().getACall() and
+ (
+ // ldap_connection.start_tls_s()
+ exists(DataFlow::AttrRead startTLS |
+ startTLS.getObject().getALocalSource() = initialize and
+ startTLS.getAttributeName().matches("%start_tls%")
+ )
+ or
+ // ldap_connection.set_option(ldap.OPT_X_TLS_%s, True)
+ // ldap_connection.set_option(ldap.OPT_X_TLS_%s)
+ exists(DataFlow::CallCfgNode setOption |
+ setOption.getFunction().(DataFlow::AttrRead).getObject().getALocalSource() =
+ initialize and
+ setOption.getFunction().(DataFlow::AttrRead).getAttributeName() = "set_option" and
+ setOption.getArg(0) =
+ ldap().getMember("OPT_X_TLS_" + ["ALLOW", "TRY", "DEMAND", "HARD"]).getAUse() and
+ not DataFlow::exprNode(any(False falseExpr))
+ .(DataFlow::LocalSourceNode)
+ .flowsTo(setOption.getArg(1))
+ )
+ )
+ )
+ }
}
/**
@@ -166,6 +202,24 @@ private module LDAP {
override DataFlow::Node getPassword() {
result in [this.getArg(2), this.getArgByName("password")]
}
+
+ override DataFlow::Node getHost() {
+ exists(DataFlow::CallCfgNode serverCall |
+ serverCall = ldap3Server().getACall() and
+ this.getArg(0).getALocalSource() = serverCall and
+ result = serverCall.getArg(0)
+ )
+ }
+
+ override predicate useSSL() {
+ exists(DataFlow::CallCfgNode serverCall |
+ serverCall = ldap3Server().getACall() and
+ this.getArg(0).getALocalSource() = serverCall and
+ DataFlow::exprNode(any(True trueExpr))
+ .(DataFlow::LocalSourceNode)
+ .flowsTo([serverCall.getArg(2), serverCall.getArgByName("use_ssl")])
+ )
+ }
}
/**
diff --git a/python/ql/src/experimental/semmle/python/security/LDAPInsecureAuth.qll b/python/ql/src/experimental/semmle/python/security/LDAPInsecureAuth.qll
new file mode 100644
index 00000000000..53b3745cc62
--- /dev/null
+++ b/python/ql/src/experimental/semmle/python/security/LDAPInsecureAuth.qll
@@ -0,0 +1,108 @@
+/**
+ * Provides a taint-tracking configuration for detecting LDAP injection vulnerabilities
+ */
+
+import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.dataflow.new.RemoteFlowSources
+import experimental.semmle.python.Concepts
+
+string getFullHostRegex() { result = "(?i)ldap://[\\[a-zA-Z0-9].*" }
+
+string getSchemaRegex() { result = "(?i)ldap(://)?" }
+
+string getPrivateHostRegex() {
+ result =
+ "(?i)localhost(?:[:/?#].*)?|127\\.0\\.0\\.1(?:[:/?#].*)?|10(?:\\.[0-9]+){3}(?:[:/?#].*)?|172\\.16(?:\\.[0-9]+){2}(?:[:/?#].*)?|192.168(?:\\.[0-9]+){2}(?:[:/?#].*)?|\\[?0:0:0:0:0:0:0:1\\]?(?:[:/?#].*)?|\\[?::1\\]?(?:[:/?#].*)?"
+}
+
+// "ldap://somethingon.theinternet.com"
+class LDAPFullHost extends StrConst {
+ LDAPFullHost() {
+ exists(string s |
+ s = this.getText() and
+ s.regexpMatch(getFullHostRegex()) and
+ not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex()) // No need to check for ldaps, it would be SSL by default.
+ )
+ }
+}
+
+class LDAPSchema extends StrConst {
+ LDAPSchema() { this.getText().regexpMatch(getSchemaRegex()) }
+}
+
+class LDAPPrivateHost extends StrConst {
+ LDAPPrivateHost() { this.getText().regexpMatch(getPrivateHostRegex()) }
+}
+
+predicate concatAndCompareAgainstFullHostRegex(Expr schema, Expr host) {
+ schema instanceof LDAPSchema and
+ not host instanceof LDAPPrivateHost and
+ exists(string full_host |
+ full_host = schema.(StrConst).getText() + host.(StrConst).getText() and
+ full_host.regexpMatch(getFullHostRegex())
+ )
+}
+
+// "ldap://" + "somethingon.theinternet.com"
+class LDAPBothStrings extends BinaryExpr {
+ LDAPBothStrings() { concatAndCompareAgainstFullHostRegex(this.getLeft(), this.getRight()) }
+}
+
+// schema + host
+class LDAPBothVar extends BinaryExpr {
+ LDAPBothVar() {
+ exists(SsaVariable schemaVar, SsaVariable hostVar |
+ this.getLeft() = schemaVar.getVariable().getALoad() and // getAUse is incompatible with Expr
+ this.getRight() = hostVar.getVariable().getALoad() and
+ concatAndCompareAgainstFullHostRegex(schemaVar
+ .getDefinition()
+ .getImmediateDominator()
+ .getNode(), hostVar.getDefinition().getImmediateDominator().getNode())
+ )
+ }
+}
+
+// schema + "somethingon.theinternet.com"
+class LDAPVarString extends BinaryExpr {
+ LDAPVarString() {
+ exists(SsaVariable schemaVar |
+ this.getLeft() = schemaVar.getVariable().getALoad() and
+ concatAndCompareAgainstFullHostRegex(schemaVar
+ .getDefinition()
+ .getImmediateDominator()
+ .getNode(), this.getRight())
+ )
+ }
+}
+
+// "ldap://" + host
+class LDAPStringVar extends BinaryExpr {
+ LDAPStringVar() {
+ exists(SsaVariable hostVar |
+ this.getRight() = hostVar.getVariable().getALoad() and
+ concatAndCompareAgainstFullHostRegex(this.getLeft(),
+ hostVar.getDefinition().getImmediateDominator().getNode())
+ )
+ }
+}
+
+/**
+ * A taint-tracking configuration for detecting LDAP injections.
+ */
+class LDAPInsecureAuthConfig extends TaintTracking::Configuration {
+ LDAPInsecureAuthConfig() { this = "LDAPInsecureAuthConfig" }
+
+ override predicate isSource(DataFlow::Node source) {
+ source instanceof RemoteFlowSource or
+ source.asExpr() instanceof LDAPBothStrings or
+ source.asExpr() instanceof LDAPBothVar or
+ source.asExpr() instanceof LDAPVarString or
+ source.asExpr() instanceof LDAPStringVar
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(LDAPBind ldapBind | not ldapBind.useSSL() and sink = ldapBind.getHost())
+ }
+}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected b/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
new file mode 100644
index 00000000000..fdcb414b981
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
@@ -0,0 +1,39 @@
+edges
+| ldap3_remote.py:49:12:49:34 | ControlFlowNode for BinaryExpr | ldap3_remote.py:51:18:51:21 | ControlFlowNode for host |
+| ldap3_remote.py:88:21:88:27 | ControlFlowNode for request | ldap3_remote.py:88:21:88:32 | ControlFlowNode for Attribute |
+| ldap3_remote.py:88:21:88:32 | ControlFlowNode for Attribute | ldap3_remote.py:88:21:88:40 | ControlFlowNode for Subscript |
+| ldap3_remote.py:88:21:88:40 | ControlFlowNode for Subscript | ldap3_remote.py:90:18:90:21 | ControlFlowNode for host |
+| ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host |
+| ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host |
+| ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host |
+| ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute |
+| ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute | ldap3_remote.py:138:21:138:40 | ControlFlowNode for Subscript |
+| ldap3_remote.py:138:21:138:40 | ControlFlowNode for Subscript | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host |
+nodes
+| ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| ldap3_remote.py:49:12:49:34 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
+| ldap3_remote.py:88:21:88:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| ldap3_remote.py:88:21:88:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| ldap3_remote.py:88:21:88:40 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
+| ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
+| ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
+| ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
+| ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
+| ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
+| ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| ldap3_remote.py:138:21:138:40 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
+#select
+| ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | $@ is authenticated insecurely. | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | This LDAP host |
+| ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | $@ is authenticated insecurely. | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | This LDAP host |
+| ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | ldap3_remote.py:49:12:49:34 | ControlFlowNode for BinaryExpr | ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | This LDAP host |
+| ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | ldap3_remote.py:88:21:88:27 | ControlFlowNode for request | ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | This LDAP host |
+| ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | This LDAP host |
+| ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | This LDAP host |
+| ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | This LDAP host |
+| ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | This LDAP host |
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.qlref b/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.qlref
new file mode 100644
index 00000000000..8bb2c1e9b52
--- /dev/null
+++ b/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-522/LDAPInsecureAuth.ql
\ No newline at end of file
diff --git a/python/ql/src/experimental/Security/CWE-090/tests/2_private.py b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap2_private.py
similarity index 100%
rename from python/ql/src/experimental/Security/CWE-090/tests/2_private.py
rename to python/ql/test/experimental/query-tests/Security/CWE-522/ldap2_private.py
diff --git a/python/ql/src/experimental/Security/CWE-090/tests/2_remote.py b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap2_remote.py
similarity index 100%
rename from python/ql/src/experimental/Security/CWE-090/tests/2_remote.py
rename to python/ql/test/experimental/query-tests/Security/CWE-522/ldap2_remote.py
diff --git a/python/ql/src/experimental/Security/CWE-090/tests/3_private.py b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_private.py
similarity index 98%
rename from python/ql/src/experimental/Security/CWE-090/tests/3_private.py
rename to python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_private.py
index 7c4ede3f0e6..796ecbd01b0 100644
--- a/python/ql/src/experimental/Security/CWE-090/tests/3_private.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_private.py
@@ -83,7 +83,7 @@ def six():
def four():
host = "ldap://" + partial_host
- srv = Server(host, port=1337, True)
+ srv = Server(host, 1337, True)
conn = Connection(srv, "dn", "password")
conn.search("dn", "search_filter")
return conn.response
diff --git a/python/ql/src/experimental/Security/CWE-090/tests/3_remote.py b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
similarity index 100%
rename from python/ql/src/experimental/Security/CWE-090/tests/3_remote.py
rename to python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
From b03e75e3d1920135dbd396ee53acfe340a1e13bd Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 22 Jul 2021 18:42:41 +0200
Subject: [PATCH 050/459] Extend `ldap3`'s `start_tls` and fix tests
---
.../ql/src/experimental/semmle/python/frameworks/LDAP.qll | 5 +++++
.../query-tests/Security/CWE-522/ldap3_remote.py | 6 +++---
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll b/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
index ed2ba3a5817..b36897deea7 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/LDAP.qll
@@ -219,6 +219,11 @@ private module LDAP {
.(DataFlow::LocalSourceNode)
.flowsTo([serverCall.getArg(2), serverCall.getArgByName("use_ssl")])
)
+ or
+ exists(DataFlow::AttrRead startTLS |
+ startTLS.getAttributeName().matches("%start_tls%") and
+ startTLS.getObject().getALocalSource() = this
+ )
}
}
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
index 366fce2b186..09dadcfb0e0 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
@@ -48,7 +48,7 @@ def three():
def four():
host = "ldap://" + remote_host
- srv = Server(host, port=1337, True)
+ srv = Server(host, 1337, True)
conn = Connection(srv, "dn", "password")
conn.search("dn", "search_filter")
return conn.response
@@ -87,7 +87,7 @@ def six():
def seven():
host = schema + request.args['host']
- srv = Server(host, port=1337, True)
+ srv = Server(host, 1337, True)
conn = Connection(srv, "dn", "password")
conn.search("dn", "search_filter")
return conn.response
@@ -112,7 +112,7 @@ def eight():
@app.route("/nine")
def nine():
host = schema + "somethingon.theinternet.com"
- srv = Server(host, port=1337, False)
+ srv = Server(host, 1337, False)
conn = Connection(srv, "dn", "password")
conn.search("dn", "search_filter")
return conn.response
From e07516585a5faa60697951e23a3258da911e0501 Mon Sep 17 00:00:00 2001
From: Jordy Zomer
Date: Tue, 3 Aug 2021 19:08:47 +0200
Subject: [PATCH 051/459] cpp: Add query to detect unsigned integer to signed
integer conversions used in pointer arithmetics
---
.../CWE-787/UnsignedToSignedPointerArith.ql | 28 +++++++++++++++++++
1 file changed, 28 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
new file mode 100644
index 00000000000..3e50994c30c
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
@@ -0,0 +1,28 @@
+/**
+ * @author Jordy Zomer
+ * @name unsiged to signed used in pointer arithmetic
+ * @description finds unsigned to signed conversions used in pointer arithmetic, potentially causing an out-of-bound access
+ * @id cpp/out-of-bounds
+ * @kind problem
+ * @problem.severity warning
+ * @tags reliability
+ * security
+ * external/cwe/cwe-787
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.security.Overflow
+
+from FunctionCall call, Function f, Parameter p, DataFlow::Node sink, PointerArithmeticOperation pao, Operation a, Operation b
+where
+f = call.getTarget() and
+p = f.getAParameter() and
+p.getType().getUnderlyingType().(IntegralType).isSigned() and
+call.getArgument(p.getIndex()).getType().getUnderlyingType().(IntegralType).isUnsigned() and
+pao.getAnOperand() = sink.asExpr() and
+not guardedLesser(a, sink.asExpr()) and
+not guardedGreater(b, call.getArgument(p.getIndex())) and
+not call.getArgument(p.getIndex()).isConstant() and
+DataFlow::localFlow(DataFlow::parameterNode(p), sink)
+select call, "This call: $@ passes an unsigned int to a function that requires a signed int: $@. And then used in pointer arithmetic: $@", call, call.toString(), f, f.toString(), sink, sink.toString()
From 19bb8e8c17316f9afc219c964edc59c2b81e36f8 Mon Sep 17 00:00:00 2001
From: Jordy Zomer
Date: Tue, 3 Aug 2021 21:54:04 +0200
Subject: [PATCH 052/459] Make requested changes
---
.../CWE/CWE-787/UnsignedToSignedPointerArith.ql | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
index 3e50994c30c..dffb262445d 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
@@ -2,7 +2,7 @@
* @author Jordy Zomer
* @name unsiged to signed used in pointer arithmetic
* @description finds unsigned to signed conversions used in pointer arithmetic, potentially causing an out-of-bound access
- * @id cpp/out-of-bounds
+ * @id cpp/sign-conversion-pointer-arithmetic
* @kind problem
* @problem.severity warning
* @tags reliability
@@ -14,15 +14,15 @@ import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.security.Overflow
-from FunctionCall call, Function f, Parameter p, DataFlow::Node sink, PointerArithmeticOperation pao, Operation a, Operation b
+from FunctionCall call, Function f, Parameter p, DataFlow::Node sink, PointerArithmeticOperation pao
where
f = call.getTarget() and
p = f.getAParameter() and
-p.getType().getUnderlyingType().(IntegralType).isSigned() and
-call.getArgument(p.getIndex()).getType().getUnderlyingType().(IntegralType).isUnsigned() and
+p.getUnspecifiedType().(IntegralType).isSigned() and
+call.getArgument(p.getIndex()).getUnspecifiedType().(IntegralType).isUnsigned() and
pao.getAnOperand() = sink.asExpr() and
-not guardedLesser(a, sink.asExpr()) and
-not guardedGreater(b, call.getArgument(p.getIndex())) and
+not exists(Operation a | guardedLesser(a, sink.asExpr())) and
+not exists(Operation b | guardedGreater(b, call.getArgument(p.getIndex()))) and
not call.getArgument(p.getIndex()).isConstant() and
DataFlow::localFlow(DataFlow::parameterNode(p), sink)
select call, "This call: $@ passes an unsigned int to a function that requires a signed int: $@. And then used in pointer arithmetic: $@", call, call.toString(), f, f.toString(), sink, sink.toString()
From 489ac04f86aa56372bcd6564bdebaa70763e331c Mon Sep 17 00:00:00 2001
From: Jordy Zomer
Date: Thu, 5 Aug 2021 12:34:31 +0200
Subject: [PATCH 053/459] Remove author tag
---
.../Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql | 1 -
1 file changed, 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
index dffb262445d..aea10c30e7f 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
@@ -1,5 +1,4 @@
/**
- * @author Jordy Zomer
* @name unsiged to signed used in pointer arithmetic
* @description finds unsigned to signed conversions used in pointer arithmetic, potentially causing an out-of-bound access
* @id cpp/sign-conversion-pointer-arithmetic
From cf40d0ae4dafd7ad1e51c86887e54d2fd6aa0d57 Mon Sep 17 00:00:00 2001
From: Jordy Zomer
Date: Thu, 5 Aug 2021 16:40:49 +0200
Subject: [PATCH 054/459] Fix a typo unsiged -> unsigned
---
.../Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
index aea10c30e7f..ffcc6b8f544 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
@@ -1,5 +1,5 @@
/**
- * @name unsiged to signed used in pointer arithmetic
+ * @name unsinged to signed used in pointer arithmetic
* @description finds unsigned to signed conversions used in pointer arithmetic, potentially causing an out-of-bound access
* @id cpp/sign-conversion-pointer-arithmetic
* @kind problem
From a3bacc76f138980418bda28e4253a717535ffde1 Mon Sep 17 00:00:00 2001
From: Jordy Zomer
Date: Thu, 5 Aug 2021 23:31:12 +0200
Subject: [PATCH 055/459] Update
cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
---
.../Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
index ffcc6b8f544..f656b791674 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-787/UnsignedToSignedPointerArith.ql
@@ -1,5 +1,5 @@
/**
- * @name unsinged to signed used in pointer arithmetic
+ * @name unsigned to signed used in pointer arithmetic
* @description finds unsigned to signed conversions used in pointer arithmetic, potentially causing an out-of-bound access
* @id cpp/sign-conversion-pointer-arithmetic
* @kind problem
From babec9bf7972558cf7fee278d152e06d624eea35 Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 11:26:51 +0100
Subject: [PATCH 056/459] add data flow debugging guide
---
docs/codeql/writing-codeql-queries/codeql-queries.rst | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/codeql/writing-codeql-queries/codeql-queries.rst b/docs/codeql/writing-codeql-queries/codeql-queries.rst
index 0b1224ce15f..fb255857fc8 100644
--- a/docs/codeql/writing-codeql-queries/codeql-queries.rst
+++ b/docs/codeql/writing-codeql-queries/codeql-queries.rst
@@ -25,3 +25,4 @@ CodeQL queries are used in code scanning analyses to find problems in source cod
- :doc:`About data flow analysis `: Data flow analysis is used to compute the possible values that a variable can hold at various points in a program, determining how those values propagate through the program and where they are used.
- :doc:`Creating path queries `: You can create path queries to visualize the flow of information through a codebase.
- :doc:`Troubleshooting query performance `: Improve the performance of your CodeQL queries by following a few simple guidelines.
+- :doc:`Debugging data-flow queries using partial flow `: If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
From ad2850dd5d01240da054e7d74e54622c6a1004a4 Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 11:27:53 +0100
Subject: [PATCH 057/459] add new tutorial
---
...g-a-data-flow-query-using-partial-flow.rst | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100644 docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
diff --git a/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
new file mode 100644
index 00000000000..c8ab639d744
--- /dev/null
+++ b/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
@@ -0,0 +1,102 @@
+.. _debugging-data-flow-queries-using-partial-flow
+
+Debugging data-flow queries using partial flow
+==============================================
+
+If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
+
+A typical data-flow query looks like this:
+
+.. code-block:: ql
+
+::
+
+ class MyConfig extends TaintTracking::Configuration {
+ MyConfig() { this = "MyConfig" }
+
+ override predicate isSource(DataFlow::Node node) { node instanceof MySource }
+
+ override predicate isSink(DataFlow::Node node) { node instanceof MySink }
+ }
+
+ from MyConfig config, DataFlow::PathNode source, DataFlow::PathNode sink
+ where config.hasFlowPath(source, sink)
+ select sink.getNode(), source, sink, "Sink is reached from $@.", source.getNode(), "here"
+
+Or slightly simpler without path explanations:
+
+.. code-block:: ql
+
+ from MyConfig config, DataFlow::Node source, DataFlow::Node sink
+ where config.hasPath(source, sink)
+ select sink, "Sink is reached from $@.", source.getNode(), "here"
+
+Checking sources and sinks
+--------------------------
+
+As a first step, make sure that the source and sink definitions contain what you expect. If one of these is empty then there can never be any data flow. The easiest way to verify this is using quick evaluation: Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources.
+
+If both source and sink definitions look good then we will need to look for missing flow steps.
+
+``fieldFlowBranchLimit``
+------------------------
+
+Data-flow configurations contain a parameter called ``fieldFlowBranchLimit``. This is a slightly unfortunate, but currently necessary, performance trade-off, and a too low value can cause false negatives. It is worth a quick check to set this to a high value and see whether this causes the query to yield result. Try, for example, to add the following to your configuration:
+
+.. code-block:: ql
+
+ override int fieldFlowBranchLimit() { result = 5000 }
+
+If there are still no results and performance did not degrade to complete uselessness, then it is best to leave this set to a high value while doing further debugging.
+
+Partial flow
+------------
+
+A naive next step could be to try changing the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach makes sense and can work, it can be hard to get an overview of the results as they can be quite plentiful, and it does come with a couple of additional caveats: Performance might degrade to uselessness and we might not even see all the partial flow paths. The latter point is somewhat subtle and deserves elaboration. Since the data-flow library tries very hard to prune impossible paths and since field stores and reads must be evenly matched along a path, then we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
+
+Because of this, a ``Configuration`` comes with a mechanism for exploring partial flow that tries to deal with these caveats. This is the ``Configuration.hasPartialFlow`` predicate:
+
+.. code-block:: ql
+
+ /**
+ * Holds if there is a partial data flow path from `source` to `node`. The
+ * approximate distance between `node` and the closest source is `dist` and
+ * is restricted to be less than or equal to `explorationLimit()`. This
+ * predicate completely disregards sink definitions.
+ *
+ * This predicate is intended for dataflow exploration and debugging and may
+ * perform poorly if the number of sources is too big and/or the exploration
+ * limit is set too high without using barriers.
+ *
+ * This predicate is disabled (has no results) by default. Override
+ * `explorationLimit()` with a suitable number to enable this predicate.
+ *
+ * To use this in a `path-problem` query, import the module `PartialPathGraph`.
+ */
+ final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) {
+
+As noted in the qldoc for ``hasPartialFlow`` one must first enable this by adding an override of ``explorationLimit``. For example:
+
+.. code-block:: ql
+
+ override int explorationLimit() { result = 5 }
+
+This defines the exploration radius within which ``hasPartialFlow`` returns results.
+
+It is also generally useful to focus on a single source at a time as the starting point for the flow exploration. This is most easily done by adding some ad-hoc restriction in the ``isSource`` predicate.
+
+To do quick ad-hoc evaluations of partial flow it is often easiest to add a predicate to the query that is solely intended for quick evaluation (right-click the predicate name and choose "CodeQL: Quick Evaluation"). A good starting point is something like:
+
+.. code-block:: ql
+
+ predicate adhocPartialFlow(Callable c, PartialPathNode n, Node src, int dist) {
+ exists(MyConfig conf, PartialPathNode source |
+ conf.hasPartialFlow(source, n, dist) and
+ src = source.getNode() and
+ c = n.getNode().getEnclosingCallable()
+ )
+ }
+
+If you are focusing on a single source then the ``src`` column is of course superfluous, and you may of course also add other columns of interest based on ``n``, but including the enclosing callable and the distance to the source at the very least is generally recommended, as they can be useful columns to sort on to better inspect the results.
+
+A couple of advanced tips in order to focus the partial flow results: If flow travels a long distance following an expected path and the distance means that a lot of uninteresting flow gets included in the exploration radius then one can simply replace the source definition with a suitable node found along the way and restart the partial flow exploration from that point. Alternatively, creative use of barriers/sanitizers can be used to cut off flow paths that are uninteresting and thereby reduce the number of partial flow results to increase overview.
From 429decd7b6b258ef060623ee409a5a11bed9ebd8 Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 11:38:03 +0100
Subject: [PATCH 058/459] tweak sojme text
---
.../debugging-a-data-flow-query-using-partial-flow.rst | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
index c8ab639d744..fb69703dad8 100644
--- a/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
@@ -5,6 +5,7 @@ Debugging data-flow queries using partial flow
If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
+In CodeQL, you can use :ref:`data flow analysis ` to compute the possible values that a variable can hold at various points in a program.
A typical data-flow query looks like this:
.. code-block:: ql
@@ -31,10 +32,13 @@ Or slightly simpler without path explanations:
where config.hasPath(source, sink)
select sink, "Sink is reached from $@.", source.getNode(), "here"
+If a data-flow query that you have written does not produce any results when you expect it to, there may be a problem with your query.
+You can try to debug the potential problem by following the steps described below.
+
Checking sources and sinks
--------------------------
-As a first step, make sure that the source and sink definitions contain what you expect. If one of these is empty then there can never be any data flow. The easiest way to verify this is using quick evaluation: Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources.
+As a first step, make sure that the source and sink definitions contain what you expect. If one of these is empty then there can never be any data flow. The easiest way to verify this is using quick evaluation in CodeQL for VS Code: Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources.
If both source and sink definitions look good then we will need to look for missing flow steps.
From 18b82444066ec84b28cbdde71a0af2ba072fb7ab Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 11:47:16 +0100
Subject: [PATCH 059/459] fix link
---
.../debugging-a-data-flow-query-using-partial-flow.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
index fb69703dad8..c74b6bb635e 100644
--- a/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
@@ -1,4 +1,4 @@
-.. _debugging-data-flow-queries-using-partial-flow
+.. _debugging-data-flow-queries-using-partial-flow:
Debugging data-flow queries using partial flow
==============================================
From 8443d344a2aef8fce0d4fe59ee2d6e4c1fb1593e Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 11:58:42 +0100
Subject: [PATCH 060/459] correct article name
---
...low.rst => debugging-data-flow-queries-using-partial-flow.rst} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/codeql/writing-codeql-queries/{debugging-a-data-flow-query-using-partial-flow.rst => debugging-data-flow-queries-using-partial-flow.rst} (100%)
diff --git a/docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
similarity index 100%
rename from docs/codeql/writing-codeql-queries/debugging-a-data-flow-query-using-partial-flow.rst
rename to docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
From dcbf76621703031a730456ec4cb12e395a437957 Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 12:14:48 +0100
Subject: [PATCH 061/459] add new article to toc
---
docs/codeql/writing-codeql-queries/codeql-queries.rst | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/codeql/writing-codeql-queries/codeql-queries.rst b/docs/codeql/writing-codeql-queries/codeql-queries.rst
index fb255857fc8..7a31fc76296 100644
--- a/docs/codeql/writing-codeql-queries/codeql-queries.rst
+++ b/docs/codeql/writing-codeql-queries/codeql-queries.rst
@@ -16,6 +16,7 @@ CodeQL queries are used in code scanning analyses to find problems in source cod
about-data-flow-analysis
creating-path-queries
troubleshooting-query-performance
+ debugging-data-flow-queries-using-partial-flow
- :doc:`About CodeQL queries `: CodeQL queries are used to analyze code for issues related to security, correctness, maintainability, and readability.
- :doc:`Metadata for CodeQL queries `: Metadata tells users important information about CodeQL queries. You must include the correct query metadata in a query to be able to view query results in source code.
@@ -25,4 +26,4 @@ CodeQL queries are used in code scanning analyses to find problems in source cod
- :doc:`About data flow analysis `: Data flow analysis is used to compute the possible values that a variable can hold at various points in a program, determining how those values propagate through the program and where they are used.
- :doc:`Creating path queries `: You can create path queries to visualize the flow of information through a codebase.
- :doc:`Troubleshooting query performance `: Improve the performance of your CodeQL queries by following a few simple guidelines.
-- :doc:`Debugging data-flow queries using partial flow `: If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
+- :doc:`Debugging data-flow queries using partial flow `: If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
From dbf7487a9b300175cff605ebc930752993fbed1e Mon Sep 17 00:00:00 2001
From: james
Date: Mon, 23 Aug 2021 11:34:48 +0100
Subject: [PATCH 062/459] address review comments
---
docs/codeql/writing-codeql-queries/codeql-queries.rst | 2 +-
.../debugging-data-flow-queries-using-partial-flow.rst | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/writing-codeql-queries/codeql-queries.rst b/docs/codeql/writing-codeql-queries/codeql-queries.rst
index 7a31fc76296..9607e782355 100644
--- a/docs/codeql/writing-codeql-queries/codeql-queries.rst
+++ b/docs/codeql/writing-codeql-queries/codeql-queries.rst
@@ -26,4 +26,4 @@ CodeQL queries are used in code scanning analyses to find problems in source cod
- :doc:`About data flow analysis `: Data flow analysis is used to compute the possible values that a variable can hold at various points in a program, determining how those values propagate through the program and where they are used.
- :doc:`Creating path queries `: You can create path queries to visualize the flow of information through a codebase.
- :doc:`Troubleshooting query performance `: Improve the performance of your CodeQL queries by following a few simple guidelines.
-- :doc:`Debugging data-flow queries using partial flow `: If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
+- :doc:`Debugging data-flow queries using partial flow `: If a data-flow query doesn't produce the results you expect to see, you can use partial flow to debug the problem..
diff --git a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
index c74b6bb635e..afbd741e822 100644
--- a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
@@ -3,7 +3,7 @@
Debugging data-flow queries using partial flow
==============================================
-If a data-flow query unexpectedly produces no results, you can use partial flow to debug the problem.
+If a data-flow query doesn't produce the results you expect to see, you can use partial flow to debug the problem.
In CodeQL, you can use :ref:`data flow analysis ` to compute the possible values that a variable can hold at various points in a program.
A typical data-flow query looks like this:
@@ -32,7 +32,7 @@ Or slightly simpler without path explanations:
where config.hasPath(source, sink)
select sink, "Sink is reached from $@.", source.getNode(), "here"
-If a data-flow query that you have written does not produce any results when you expect it to, there may be a problem with your query.
+If a data-flow query that you have written doesn't produce the results you expect it to, there may be a problem with your query.
You can try to debug the potential problem by following the steps described below.
Checking sources and sinks
From 66bdbf4a283743082951cb471315b2c658a5eaa6 Mon Sep 17 00:00:00 2001
From: james
Date: Mon, 23 Aug 2021 11:35:04 +0100
Subject: [PATCH 063/459] address review comments
---
.../debugging-data-flow-queries-using-partial-flow.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
index afbd741e822..dfadf63ab4d 100644
--- a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
@@ -56,9 +56,9 @@ If there are still no results and performance did not degrade to complete useles
Partial flow
------------
-A naive next step could be to try changing the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach makes sense and can work, it can be hard to get an overview of the results as they can be quite plentiful, and it does come with a couple of additional caveats: Performance might degrade to uselessness and we might not even see all the partial flow paths. The latter point is somewhat subtle and deserves elaboration. Since the data-flow library tries very hard to prune impossible paths and since field stores and reads must be evenly matched along a path, then we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
+A naive next step could be to try changing the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach makes sense and can work in some cases, you might find that it produces so many results that it's very hard to explore the findings, which can also dramtatically affect query performance. More importnatly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
-Because of this, a ``Configuration`` comes with a mechanism for exploring partial flow that tries to deal with these caveats. This is the ``Configuration.hasPartialFlow`` predicate:
+To avoid these problems, a data-flow ``Configuration`` comes with a mechanism for exploring partial flow that tries to deal with these caveats. This is the ``Configuration.hasPartialFlow`` predicate:
.. code-block:: ql
@@ -79,7 +79,7 @@ Because of this, a ``Configuration`` comes with a mechanism for exploring partia
*/
final predicate hasPartialFlow(PartialPathNode source, PartialPathNode node, int dist) {
-As noted in the qldoc for ``hasPartialFlow`` one must first enable this by adding an override of ``explorationLimit``. For example:
+As noted in the documentation for ``hasPartialFlow`` (for example, in the `CodeQL for Java documentation __`) you must first enable this by adding an override of ``explorationLimit``. For example:
.. code-block:: ql
From 18440710b4b731a2d2ea781cf80aa7ad5b07ca51 Mon Sep 17 00:00:00 2001
From: james
Date: Mon, 23 Aug 2021 14:02:53 +0100
Subject: [PATCH 064/459] fix typos
---
.../debugging-data-flow-queries-using-partial-flow.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
index dfadf63ab4d..9d5d83244a5 100644
--- a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
@@ -56,7 +56,7 @@ If there are still no results and performance did not degrade to complete useles
Partial flow
------------
-A naive next step could be to try changing the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach makes sense and can work in some cases, you might find that it produces so many results that it's very hard to explore the findings, which can also dramtatically affect query performance. More importnatly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
+A naive next step could be to try changing the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach makes sense and can work in some cases, you might find that it produces so many results that it's very hard to explore the findings, which can also dramatically affect query performance. More importantly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
To avoid these problems, a data-flow ``Configuration`` comes with a mechanism for exploring partial flow that tries to deal with these caveats. This is the ``Configuration.hasPartialFlow`` predicate:
From e3765ced7809ae800bdade1fb1543a1e598a3426 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 23 Aug 2021 15:29:07 +0200
Subject: [PATCH 065/459] Python: Add tests for modification of defaults
---
...odificationOfParameterWithDefault.expected | 28 +++++++
.../ModificationOfParameterWithDefault.qlref | 1 +
.../test.py | 80 +++++++++++++++++++
3 files changed, 109 insertions(+)
create mode 100644 python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
create mode 100644 python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.qlref
create mode 100644 python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
new file mode 100644
index 00000000000..bf51e172916
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
@@ -0,0 +1,28 @@
+edges
+| test.py:2:12:2:12 | empty mutable value | test.py:3:5:3:5 | empty mutable value |
+| test.py:7:14:7:14 | empty mutable value | test.py:9:14:9:14 | empty mutable value |
+| test.py:9:5:9:15 | empty mutable value | test.py:10:5:10:5 | empty mutable value |
+| test.py:9:14:9:14 | empty mutable value | test.py:9:5:9:15 | empty mutable value |
+| test.py:13:13:13:13 | empty mutable value | test.py:14:5:14:5 | empty mutable value |
+| test.py:18:14:18:14 | empty mutable value | test.py:19:13:19:13 | empty mutable value |
+| test.py:19:13:19:13 | empty mutable value | test.py:13:13:13:13 | empty mutable value |
+| test.py:23:14:23:14 | non-empty mutable value | test.py:24:5:24:5 | non-empty mutable value |
+| test.py:52:17:52:17 | empty mutable value | test.py:53:5:53:5 | empty mutable value |
+| test.py:57:26:57:26 | non-empty mutable value | test.py:58:5:58:5 | non-empty mutable value |
+| test.py:62:35:62:35 | non-empty mutable value | test.py:63:5:63:5 | non-empty mutable value |
+| test.py:66:21:66:21 | empty mutable value | test.py:67:5:67:5 | empty mutable value |
+| test.py:71:26:71:26 | empty mutable value | test.py:72:21:72:21 | empty mutable value |
+| test.py:72:21:72:21 | empty mutable value | test.py:66:21:66:21 | empty mutable value |
+| test.py:76:19:76:19 | empty mutable value | test.py:78:14:78:14 | empty mutable value |
+| test.py:78:5:78:15 | empty mutable value | test.py:79:5:79:5 | empty mutable value |
+| test.py:78:14:78:14 | empty mutable value | test.py:78:5:78:15 | empty mutable value |
+#select
+| test.py:3:5:3:5 | l | test.py:2:12:2:12 | empty mutable value | test.py:3:5:3:5 | empty mutable value | $@ flows to here and is mutated. | test.py:2:12:2:12 | l | Default value |
+| test.py:10:5:10:5 | x | test.py:7:14:7:14 | empty mutable value | test.py:10:5:10:5 | empty mutable value | $@ flows to here and is mutated. | test.py:7:14:7:14 | l | Default value |
+| test.py:14:5:14:5 | l | test.py:18:14:18:14 | empty mutable value | test.py:14:5:14:5 | empty mutable value | $@ flows to here and is mutated. | test.py:18:14:18:14 | l | Default value |
+| test.py:24:5:24:5 | l | test.py:23:14:23:14 | non-empty mutable value | test.py:24:5:24:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:23:14:23:14 | l | Default value |
+| test.py:53:5:53:5 | d | test.py:52:17:52:17 | empty mutable value | test.py:53:5:53:5 | empty mutable value | $@ flows to here and is mutated. | test.py:52:17:52:17 | d | Default value |
+| test.py:58:5:58:5 | d | test.py:57:26:57:26 | non-empty mutable value | test.py:58:5:58:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:57:26:57:26 | d | Default value |
+| test.py:63:5:63:5 | d | test.py:62:35:62:35 | non-empty mutable value | test.py:63:5:63:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:62:35:62:35 | d | Default value |
+| test.py:67:5:67:5 | d | test.py:71:26:71:26 | empty mutable value | test.py:67:5:67:5 | empty mutable value | $@ flows to here and is mutated. | test.py:71:26:71:26 | d | Default value |
+| test.py:79:5:79:5 | x | test.py:76:19:76:19 | empty mutable value | test.py:79:5:79:5 | empty mutable value | $@ flows to here and is mutated. | test.py:76:19:76:19 | d | Default value |
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.qlref b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.qlref
new file mode 100644
index 00000000000..8c4044e8fee
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.qlref
@@ -0,0 +1 @@
+Functions/ModificationOfParameterWithDefault.ql
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
new file mode 100644
index 00000000000..239492f8ead
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -0,0 +1,80 @@
+# Not OK
+def simple(l = []):
+ l.append(1)
+ return l
+
+# OK
+def includes(l = []):
+ x = [0]
+ x.extend(l)
+ x.extend([1]) # FP
+ return x
+
+def extends(l):
+ l.extend([1])
+ return l
+
+# Not OK
+def deferred(l = []):
+ extends(l)
+ return l
+
+# Not OK
+def nonempty(l = [5]):
+ l.append(1)
+ return l
+
+# Not OK
+def dict(d = {}):
+ d['a'] = 1 # FN
+ return d
+
+# Not OK
+def dict_nonempty(d = {'a': 1}):
+ d['a'] = 2 # FN
+ return d
+
+# OK
+def dict_nonempty_nochange(d = {'a': 1}):
+ d['a'] = 1
+ return d
+
+def modifies(d):
+ d['a'] = 1 # FN
+ return d
+
+# Not OK
+def dict_deferred(d = {}):
+ modifies(d)
+ return d
+
+# Not OK
+def dict_method(d = {}):
+ d.update({'a': 1})
+ return d
+
+# Not OK
+def dict_method_nonempty(d = {'a': 1}):
+ d.update({'a': 2})
+ return d
+
+# OK
+def dict_method_nonempty_nochange(d = {'a': 1}):
+ d.update({'a': 1}) # FP
+ return d
+
+def modifies_method(d):
+ d.update({'a': 1})
+ return d
+
+# Not OK
+def dict_deferred_method(d = {}):
+ modifies_method(d)
+ return d
+
+# OK
+def dict_includes(d = {}):
+ x = {}
+ x.update(d)
+ x.update({'a': 1}) # FP
+ return x
From e865a290dee644a12a9cd7bdd377ecc5a9b3682b Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Tue, 24 Aug 2021 16:34:12 +0200
Subject: [PATCH 066/459] Python: straight port of query The old query uses
`pointsTo` to limit the sinks to methods on lists and dictionaries. That
constraint is omitted here which could hurt performance.
---
.../ModificationOfParameterWithDefault.ql | 90 ++-----------------
.../ModificationOfParameterWithDefault.qll | 34 +++++++
...onOfParameterWithDefaultCustomizations.qll | 90 +++++++++++++++++++
...odificationOfParameterWithDefault.expected | 63 +++++++------
.../test.py | 4 +-
5 files changed, 170 insertions(+), 111 deletions(-)
create mode 100644 python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
create mode 100644 python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
diff --git a/python/ql/src/Functions/ModificationOfParameterWithDefault.ql b/python/ql/src/Functions/ModificationOfParameterWithDefault.ql
index 03edc35fa17..9ff89d30e3f 100644
--- a/python/ql/src/Functions/ModificationOfParameterWithDefault.ql
+++ b/python/ql/src/Functions/ModificationOfParameterWithDefault.ql
@@ -12,88 +12,12 @@
*/
import python
-import semmle.python.security.Paths
+import semmle.python.functions.ModificationOfParameterWithDefault
+import DataFlow::PathGraph
-predicate safe_method(string name) {
- name = "count" or
- name = "index" or
- name = "copy" or
- name = "get" or
- name = "has_key" or
- name = "items" or
- name = "keys" or
- name = "values" or
- name = "iteritems" or
- name = "iterkeys" or
- name = "itervalues" or
- name = "__contains__" or
- name = "__getitem__" or
- name = "__getattribute__"
-}
-
-/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
-private boolean mutableDefaultValue(Parameter p) {
- exists(Dict d | p.getDefault() = d |
- exists(d.getAKey()) and result = true
- or
- not exists(d.getAKey()) and result = false
- )
- or
- exists(List l | p.getDefault() = l |
- exists(l.getAnElt()) and result = true
- or
- not exists(l.getAnElt()) and result = false
- )
-}
-
-class NonEmptyMutableValue extends TaintKind {
- NonEmptyMutableValue() { this = "non-empty mutable value" }
-}
-
-class EmptyMutableValue extends TaintKind {
- EmptyMutableValue() { this = "empty mutable value" }
-
- override boolean booleanValue() { result = false }
-}
-
-class MutableDefaultValue extends TaintSource {
- boolean nonEmpty;
-
- MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.(NameNode).getNode()) }
-
- override string toString() { result = "mutable default value" }
-
- override predicate isSourceOf(TaintKind kind) {
- nonEmpty = false and kind instanceof EmptyMutableValue
- or
- nonEmpty = true and kind instanceof NonEmptyMutableValue
- }
-}
-
-private ClassValue mutable_class() {
- result = Value::named("list") or
- result = Value::named("dict")
-}
-
-class Mutation extends TaintSink {
- Mutation() {
- exists(AugAssign a | a.getTarget().getAFlowNode() = this)
- or
- exists(Call c, Attribute a | c.getFunc() = a |
- a.getObject().getAFlowNode() = this and
- not safe_method(a.getName()) and
- this.(ControlFlowNode).pointsTo().getClass() = mutable_class()
- )
- }
-
- override predicate sinks(TaintKind kind) {
- kind instanceof EmptyMutableValue
- or
- kind instanceof NonEmptyMutableValue
- }
-}
-
-from TaintedPathSource src, TaintedPathSink sink
-where src.flowsTo(sink)
-select sink.getSink(), src, sink, "$@ flows to here and is mutated.", src.getSource(),
+from
+ ModificationOfParameterWithDefault::Configuration config, DataFlow::PathNode source,
+ DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "$@ flows to here and is mutated.", source.getNode(),
"Default value"
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
new file mode 100644
index 00000000000..914eb7a473e
--- /dev/null
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
@@ -0,0 +1,34 @@
+/**
+ * Provides a data-flow configuration for detecting modifications of a parameters default value.
+ *
+ * Note, for performance reasons: only import this file if
+ * `ModificationOfParameterWithDefault::Configuration` is needed, otherwise
+ * `ModificationOfParameterWithDefaultCustomizations` should be imported instead.
+ */
+
+private import python
+import semmle.python.dataflow.new.DataFlow
+
+/**
+ * Provides a data-flow configuration for detecting modifications of a parameters default value.
+ */
+module ModificationOfParameterWithDefault {
+ import ModificationOfParameterWithDefaultCustomizations::ModificationOfParameterWithDefault
+
+ /**
+ * A data-flow configuration for detecting modifications of a parameters default value.
+ */
+ class Configuration extends DataFlow::Configuration {
+ Configuration() { this = "ModificationOfParameterWithDefault" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
+
+ override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof BarrierGuard
+ }
+ }
+}
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
new file mode 100644
index 00000000000..90ab83e6753
--- /dev/null
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -0,0 +1,90 @@
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * modifications of a parameters default value, as well as extension points for adding your own.
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.BarrierGuards
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "command injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+module ModificationOfParameterWithDefault {
+ /**
+ * A data flow source for detecting modifications of a parameters default value.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for detecting modifications of a parameters default value.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for detecting modifications of a parameters default value.
+ */
+ abstract class Barrier extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for detecting modifications of a parameters default value.
+ */
+ abstract class BarrierGuard extends DataFlow::BarrierGuard { }
+
+ /** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
+ private boolean mutableDefaultValue(Parameter p) {
+ exists(Dict d | p.getDefault() = d |
+ exists(d.getAKey()) and result = true
+ or
+ not exists(d.getAKey()) and result = false
+ )
+ or
+ exists(List l | p.getDefault() = l |
+ exists(l.getAnElt()) and result = true
+ or
+ not exists(l.getAnElt()) and result = false
+ )
+ }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class MutableDefaultValue extends Source {
+ boolean nonEmpty;
+
+ MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) }
+ }
+
+ predicate safe_method(string name) {
+ name = "count" or
+ name = "index" or
+ name = "copy" or
+ name = "get" or
+ name = "has_key" or
+ name = "items" or
+ name = "keys" or
+ name = "values" or
+ name = "iteritems" or
+ name = "iterkeys" or
+ name = "itervalues" or
+ name = "__contains__" or
+ name = "__getitem__" or
+ name = "__getattribute__"
+ }
+
+ /**
+ * A mutation is considered a flow sink.
+ */
+ class Mutation extends Sink {
+ Mutation() {
+ exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode())
+ or
+ exists(Call c, Attribute a | c.getFunc() = a |
+ a.getObject().getAFlowNode() = this.asCfgNode() and
+ not safe_method(a.getName())
+ )
+ }
+ }
+}
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
index bf51e172916..3032f6b1f80 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
@@ -1,28 +1,39 @@
edges
-| test.py:2:12:2:12 | empty mutable value | test.py:3:5:3:5 | empty mutable value |
-| test.py:7:14:7:14 | empty mutable value | test.py:9:14:9:14 | empty mutable value |
-| test.py:9:5:9:15 | empty mutable value | test.py:10:5:10:5 | empty mutable value |
-| test.py:9:14:9:14 | empty mutable value | test.py:9:5:9:15 | empty mutable value |
-| test.py:13:13:13:13 | empty mutable value | test.py:14:5:14:5 | empty mutable value |
-| test.py:18:14:18:14 | empty mutable value | test.py:19:13:19:13 | empty mutable value |
-| test.py:19:13:19:13 | empty mutable value | test.py:13:13:13:13 | empty mutable value |
-| test.py:23:14:23:14 | non-empty mutable value | test.py:24:5:24:5 | non-empty mutable value |
-| test.py:52:17:52:17 | empty mutable value | test.py:53:5:53:5 | empty mutable value |
-| test.py:57:26:57:26 | non-empty mutable value | test.py:58:5:58:5 | non-empty mutable value |
-| test.py:62:35:62:35 | non-empty mutable value | test.py:63:5:63:5 | non-empty mutable value |
-| test.py:66:21:66:21 | empty mutable value | test.py:67:5:67:5 | empty mutable value |
-| test.py:71:26:71:26 | empty mutable value | test.py:72:21:72:21 | empty mutable value |
-| test.py:72:21:72:21 | empty mutable value | test.py:66:21:66:21 | empty mutable value |
-| test.py:76:19:76:19 | empty mutable value | test.py:78:14:78:14 | empty mutable value |
-| test.py:78:5:78:15 | empty mutable value | test.py:79:5:79:5 | empty mutable value |
-| test.py:78:14:78:14 | empty mutable value | test.py:78:5:78:15 | empty mutable value |
+| test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l |
+| test.py:13:13:13:13 | ControlFlowNode for l | test.py:14:5:14:5 | ControlFlowNode for l |
+| test.py:18:14:18:14 | ControlFlowNode for l | test.py:19:13:19:13 | ControlFlowNode for l |
+| test.py:19:13:19:13 | ControlFlowNode for l | test.py:13:13:13:13 | ControlFlowNode for l |
+| test.py:23:14:23:14 | ControlFlowNode for l | test.py:24:5:24:5 | ControlFlowNode for l |
+| test.py:52:17:52:17 | ControlFlowNode for d | test.py:53:5:53:5 | ControlFlowNode for d |
+| test.py:57:26:57:26 | ControlFlowNode for d | test.py:58:5:58:5 | ControlFlowNode for d |
+| test.py:62:35:62:35 | ControlFlowNode for d | test.py:63:5:63:5 | ControlFlowNode for d |
+| test.py:66:21:66:21 | ControlFlowNode for d | test.py:67:5:67:5 | ControlFlowNode for d |
+| test.py:71:26:71:26 | ControlFlowNode for d | test.py:72:21:72:21 | ControlFlowNode for d |
+| test.py:72:21:72:21 | ControlFlowNode for d | test.py:66:21:66:21 | ControlFlowNode for d |
+nodes
+| test.py:2:12:2:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:3:5:3:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:13:13:13:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:14:5:14:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:18:14:18:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:19:13:19:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:23:14:23:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:24:5:24:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:52:17:52:17 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:53:5:53:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:57:26:57:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:58:5:58:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:62:35:62:35 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:63:5:63:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:66:21:66:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:67:5:67:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:71:26:71:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:72:21:72:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
#select
-| test.py:3:5:3:5 | l | test.py:2:12:2:12 | empty mutable value | test.py:3:5:3:5 | empty mutable value | $@ flows to here and is mutated. | test.py:2:12:2:12 | l | Default value |
-| test.py:10:5:10:5 | x | test.py:7:14:7:14 | empty mutable value | test.py:10:5:10:5 | empty mutable value | $@ flows to here and is mutated. | test.py:7:14:7:14 | l | Default value |
-| test.py:14:5:14:5 | l | test.py:18:14:18:14 | empty mutable value | test.py:14:5:14:5 | empty mutable value | $@ flows to here and is mutated. | test.py:18:14:18:14 | l | Default value |
-| test.py:24:5:24:5 | l | test.py:23:14:23:14 | non-empty mutable value | test.py:24:5:24:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:23:14:23:14 | l | Default value |
-| test.py:53:5:53:5 | d | test.py:52:17:52:17 | empty mutable value | test.py:53:5:53:5 | empty mutable value | $@ flows to here and is mutated. | test.py:52:17:52:17 | d | Default value |
-| test.py:58:5:58:5 | d | test.py:57:26:57:26 | non-empty mutable value | test.py:58:5:58:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:57:26:57:26 | d | Default value |
-| test.py:63:5:63:5 | d | test.py:62:35:62:35 | non-empty mutable value | test.py:63:5:63:5 | non-empty mutable value | $@ flows to here and is mutated. | test.py:62:35:62:35 | d | Default value |
-| test.py:67:5:67:5 | d | test.py:71:26:71:26 | empty mutable value | test.py:67:5:67:5 | empty mutable value | $@ flows to here and is mutated. | test.py:71:26:71:26 | d | Default value |
-| test.py:79:5:79:5 | x | test.py:76:19:76:19 | empty mutable value | test.py:79:5:79:5 | empty mutable value | $@ flows to here and is mutated. | test.py:76:19:76:19 | d | Default value |
+| test.py:3:5:3:5 | ControlFlowNode for l | test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:2:12:2:12 | ControlFlowNode for l | Default value |
+| test.py:14:5:14:5 | ControlFlowNode for l | test.py:18:14:18:14 | ControlFlowNode for l | test.py:14:5:14:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:18:14:18:14 | ControlFlowNode for l | Default value |
+| test.py:24:5:24:5 | ControlFlowNode for l | test.py:23:14:23:14 | ControlFlowNode for l | test.py:24:5:24:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:23:14:23:14 | ControlFlowNode for l | Default value |
+| test.py:53:5:53:5 | ControlFlowNode for d | test.py:52:17:52:17 | ControlFlowNode for d | test.py:53:5:53:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:52:17:52:17 | ControlFlowNode for d | Default value |
+| test.py:58:5:58:5 | ControlFlowNode for d | test.py:57:26:57:26 | ControlFlowNode for d | test.py:58:5:58:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:57:26:57:26 | ControlFlowNode for d | Default value |
+| test.py:63:5:63:5 | ControlFlowNode for d | test.py:62:35:62:35 | ControlFlowNode for d | test.py:63:5:63:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:62:35:62:35 | ControlFlowNode for d | Default value |
+| test.py:67:5:67:5 | ControlFlowNode for d | test.py:71:26:71:26 | ControlFlowNode for d | test.py:67:5:67:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:71:26:71:26 | ControlFlowNode for d | Default value |
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 239492f8ead..81319ed909f 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -7,7 +7,7 @@ def simple(l = []):
def includes(l = []):
x = [0]
x.extend(l)
- x.extend([1]) # FP
+ x.extend([1])
return x
def extends(l):
@@ -76,5 +76,5 @@ def dict_deferred_method(d = {}):
def dict_includes(d = {}):
x = {}
x.update(d)
- x.update({'a': 1}) # FP
+ x.update({'a': 1})
return x
From 5bff5188ac32a656a1204eef6f5351ffe430a78e Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 25 Aug 2021 23:52:42 +0200
Subject: [PATCH 067/459] Python: switch from negative to positive list This
should avoid potentially terrible performance. Also noted the missing
syntactic constructs, as I went through the documnetation.
---
...onOfParameterWithDefaultCustomizations.qll | 56 ++++++++++++-------
1 file changed, 37 insertions(+), 19 deletions(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
index 90ab83e6753..c9be49b1fee 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -57,33 +57,51 @@ module ModificationOfParameterWithDefault {
MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) }
}
- predicate safe_method(string name) {
- name = "count" or
- name = "index" or
- name = "copy" or
- name = "get" or
- name = "has_key" or
- name = "items" or
- name = "keys" or
- name = "values" or
- name = "iteritems" or
- name = "iterkeys" or
- name = "itervalues" or
- name = "__contains__" or
- name = "__getitem__" or
- name = "__getattribute__"
+ /**
+ * A name of a list function that modifies the list.
+ * See https://docs.python.org/3/tutorial/datastructures.html#more-on-lists
+ */
+ string list_modifying_method() {
+ result in ["append", "extend", "insert", "remove", "pop", "clear", "sort", "reverse"]
}
/**
- * A mutation is considered a flow sink.
+ * A name of a dict function that modifies the dict.
+ * See https://docs.python.org/3/library/stdtypes.html#dict
+ */
+ string dict_modifying_method() { result in ["clear", "pop", "popitem", "setdefault", "update"] }
+
+ /**
+ * A mutation of the default value is a flow sink.
+ *
+ * Syntactic constructs that modify a list are:
+ * - s[i] = x
+ * - s[i:j] = t
+ * - del s[i:j]
+ * - s[i:j:k] = t
+ * - del s[i:j:k]
+ * - s += t
+ * - s *= n
+ * See https://docs.python.org/3/library/stdtypes.html#mutable-sequence-types
+ *
+ * Syntactic constructs that modify a dictionary are:
+ * - d[key] = value
+ * - del d[key]
+ * - d |= other
+ * See https://docs.python.org/3/library/stdtypes.html#dict
+ *
+ * These are all covered by:
+ * - assignment to a subscript (includes slices)
+ * - deletion of a subscript
+ * - augmented assignment to the value
*/
class Mutation extends Sink {
Mutation() {
exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode())
or
- exists(Call c, Attribute a | c.getFunc() = a |
- a.getObject().getAFlowNode() = this.asCfgNode() and
- not safe_method(a.getName())
+ exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a |
+ a.getObject() = this and
+ a.getAttributeName() in [list_modifying_method(), dict_modifying_method()]
)
}
}
From 8614563b4284b293df0cd9cf71ac1a12fbdd80ee Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 26 Aug 2021 10:56:41 +0200
Subject: [PATCH 068/459] Python: More tests of syntactic constructs
---
...odificationOfParameterWithDefault.expected | 88 +++++++++++--------
.../test.py | 44 +++++++++-
2 files changed, 95 insertions(+), 37 deletions(-)
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
index 3032f6b1f80..6bd668fe43f 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
@@ -1,39 +1,55 @@
edges
-| test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l |
-| test.py:13:13:13:13 | ControlFlowNode for l | test.py:14:5:14:5 | ControlFlowNode for l |
-| test.py:18:14:18:14 | ControlFlowNode for l | test.py:19:13:19:13 | ControlFlowNode for l |
-| test.py:19:13:19:13 | ControlFlowNode for l | test.py:13:13:13:13 | ControlFlowNode for l |
-| test.py:23:14:23:14 | ControlFlowNode for l | test.py:24:5:24:5 | ControlFlowNode for l |
-| test.py:52:17:52:17 | ControlFlowNode for d | test.py:53:5:53:5 | ControlFlowNode for d |
-| test.py:57:26:57:26 | ControlFlowNode for d | test.py:58:5:58:5 | ControlFlowNode for d |
-| test.py:62:35:62:35 | ControlFlowNode for d | test.py:63:5:63:5 | ControlFlowNode for d |
-| test.py:66:21:66:21 | ControlFlowNode for d | test.py:67:5:67:5 | ControlFlowNode for d |
-| test.py:71:26:71:26 | ControlFlowNode for d | test.py:72:21:72:21 | ControlFlowNode for d |
-| test.py:72:21:72:21 | ControlFlowNode for d | test.py:66:21:66:21 | ControlFlowNode for d |
+| test.py:17:15:17:15 | ControlFlowNode for l | test.py:18:5:18:5 | ControlFlowNode for l |
+| test.py:22:15:22:15 | ControlFlowNode for l | test.py:23:5:23:5 | ControlFlowNode for l |
+| test.py:27:12:27:12 | ControlFlowNode for l | test.py:28:5:28:5 | ControlFlowNode for l |
+| test.py:38:13:38:13 | ControlFlowNode for l | test.py:39:5:39:5 | ControlFlowNode for l |
+| test.py:43:14:43:14 | ControlFlowNode for l | test.py:44:13:44:13 | ControlFlowNode for l |
+| test.py:44:13:44:13 | ControlFlowNode for l | test.py:38:13:38:13 | ControlFlowNode for l |
+| test.py:48:14:48:14 | ControlFlowNode for l | test.py:49:5:49:5 | ControlFlowNode for l |
+| test.py:77:17:77:17 | ControlFlowNode for d | test.py:78:5:78:5 | ControlFlowNode for d |
+| test.py:82:26:82:26 | ControlFlowNode for d | test.py:83:5:83:5 | ControlFlowNode for d |
+| test.py:87:35:87:35 | ControlFlowNode for d | test.py:88:5:88:5 | ControlFlowNode for d |
+| test.py:91:21:91:21 | ControlFlowNode for d | test.py:92:5:92:5 | ControlFlowNode for d |
+| test.py:96:26:96:26 | ControlFlowNode for d | test.py:97:21:97:21 | ControlFlowNode for d |
+| test.py:97:21:97:21 | ControlFlowNode for d | test.py:91:21:91:21 | ControlFlowNode for d |
+| test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d |
+| test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d |
nodes
-| test.py:2:12:2:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:3:5:3:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:13:13:13:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:14:5:14:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:18:14:18:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:19:13:19:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:23:14:23:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:24:5:24:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
-| test.py:52:17:52:17 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:53:5:53:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:57:26:57:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:58:5:58:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:62:35:62:35 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:63:5:63:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:66:21:66:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:67:5:67:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:71:26:71:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
-| test.py:72:21:72:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:17:15:17:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:18:5:18:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:22:15:22:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:23:5:23:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:27:12:27:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:28:5:28:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:38:13:38:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:39:5:39:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:43:14:43:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:44:13:44:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:48:14:48:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:49:5:49:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:77:17:77:17 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:78:5:78:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:82:26:82:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:83:5:83:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:87:35:87:35 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:88:5:88:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:91:21:91:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:92:5:92:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:96:26:96:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:97:21:97:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:113:20:113:20 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:115:5:115:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:119:29:119:29 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:121:5:121:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
#select
-| test.py:3:5:3:5 | ControlFlowNode for l | test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:2:12:2:12 | ControlFlowNode for l | Default value |
-| test.py:14:5:14:5 | ControlFlowNode for l | test.py:18:14:18:14 | ControlFlowNode for l | test.py:14:5:14:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:18:14:18:14 | ControlFlowNode for l | Default value |
-| test.py:24:5:24:5 | ControlFlowNode for l | test.py:23:14:23:14 | ControlFlowNode for l | test.py:24:5:24:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:23:14:23:14 | ControlFlowNode for l | Default value |
-| test.py:53:5:53:5 | ControlFlowNode for d | test.py:52:17:52:17 | ControlFlowNode for d | test.py:53:5:53:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:52:17:52:17 | ControlFlowNode for d | Default value |
-| test.py:58:5:58:5 | ControlFlowNode for d | test.py:57:26:57:26 | ControlFlowNode for d | test.py:58:5:58:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:57:26:57:26 | ControlFlowNode for d | Default value |
-| test.py:63:5:63:5 | ControlFlowNode for d | test.py:62:35:62:35 | ControlFlowNode for d | test.py:63:5:63:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:62:35:62:35 | ControlFlowNode for d | Default value |
-| test.py:67:5:67:5 | ControlFlowNode for d | test.py:71:26:71:26 | ControlFlowNode for d | test.py:67:5:67:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:71:26:71:26 | ControlFlowNode for d | Default value |
+| test.py:18:5:18:5 | ControlFlowNode for l | test.py:17:15:17:15 | ControlFlowNode for l | test.py:18:5:18:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:17:15:17:15 | ControlFlowNode for l | Default value |
+| test.py:23:5:23:5 | ControlFlowNode for l | test.py:22:15:22:15 | ControlFlowNode for l | test.py:23:5:23:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:22:15:22:15 | ControlFlowNode for l | Default value |
+| test.py:28:5:28:5 | ControlFlowNode for l | test.py:27:12:27:12 | ControlFlowNode for l | test.py:28:5:28:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:27:12:27:12 | ControlFlowNode for l | Default value |
+| test.py:39:5:39:5 | ControlFlowNode for l | test.py:43:14:43:14 | ControlFlowNode for l | test.py:39:5:39:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:43:14:43:14 | ControlFlowNode for l | Default value |
+| test.py:49:5:49:5 | ControlFlowNode for l | test.py:48:14:48:14 | ControlFlowNode for l | test.py:49:5:49:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:48:14:48:14 | ControlFlowNode for l | Default value |
+| test.py:78:5:78:5 | ControlFlowNode for d | test.py:77:17:77:17 | ControlFlowNode for d | test.py:78:5:78:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:77:17:77:17 | ControlFlowNode for d | Default value |
+| test.py:83:5:83:5 | ControlFlowNode for d | test.py:82:26:82:26 | ControlFlowNode for d | test.py:83:5:83:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:82:26:82:26 | ControlFlowNode for d | Default value |
+| test.py:88:5:88:5 | ControlFlowNode for d | test.py:87:35:87:35 | ControlFlowNode for d | test.py:88:5:88:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:87:35:87:35 | ControlFlowNode for d | Default value |
+| test.py:92:5:92:5 | ControlFlowNode for d | test.py:96:26:96:26 | ControlFlowNode for d | test.py:92:5:92:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:96:26:96:26 | ControlFlowNode for d | Default value |
+| test.py:115:5:115:5 | ControlFlowNode for d | test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:113:20:113:20 | ControlFlowNode for d | Default value |
+| test.py:121:5:121:5 | ControlFlowNode for d | test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:119:29:119:29 | ControlFlowNode for d | Default value |
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 81319ed909f..60c3ff01b54 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -1,5 +1,30 @@
# Not OK
-def simple(l = []):
+def simple(l = [0]):
+ l[0] = 1 # FN
+ return l
+
+# Not OK
+def slice(l = [0]):
+ l[0:1] = 1 # FN
+ return l
+
+# Not OK
+def list_del(l = [0]):
+ del l[0] # FN
+ return l
+
+# Not OK
+def append_op(l = []):
+ l += 1
+ return l
+
+# Not OK
+def repeat_op(l = [0]):
+ l *= 3
+ return l
+
+# Not OK
+def append(l = []):
l.append(1)
return l
@@ -78,3 +103,20 @@ def dict_includes(d = {}):
x.update(d)
x.update({'a': 1})
return x
+
+# Not OK
+def dict_del(d = {'a': 1}):
+ del d['a'] # FN
+ return d
+
+# Not OK
+def dict_update_op(d = {}):
+ x = {'a': 1}
+ d |= x
+ return d
+
+# OK
+def dict_update_op_nochange(d = {}):
+ x = {}
+ d |= x # FP
+ return d
From d834cec9b93f5aaa9b5087cb4dd834070449fc03 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 26 Aug 2021 11:31:20 +0200
Subject: [PATCH 069/459] Python: test simple sanitizer
---
.../ModificationOfParameterWithDefault.expected | 4 ++++
.../Functions/ModificationOfParameterWithDefault/test.py | 6 ++++++
2 files changed, 10 insertions(+)
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
index 6bd668fe43f..c07a609dceb 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
@@ -14,6 +14,7 @@ edges
| test.py:97:21:97:21 | ControlFlowNode for d | test.py:91:21:91:21 | ControlFlowNode for d |
| test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d |
| test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d |
+| test.py:125:15:125:15 | ControlFlowNode for l | test.py:127:9:127:9 | ControlFlowNode for l |
nodes
| test.py:17:15:17:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:18:5:18:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
@@ -41,6 +42,8 @@ nodes
| test.py:115:5:115:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:119:29:119:29 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:121:5:121:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:125:15:125:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:127:9:127:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
#select
| test.py:18:5:18:5 | ControlFlowNode for l | test.py:17:15:17:15 | ControlFlowNode for l | test.py:18:5:18:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:17:15:17:15 | ControlFlowNode for l | Default value |
| test.py:23:5:23:5 | ControlFlowNode for l | test.py:22:15:22:15 | ControlFlowNode for l | test.py:23:5:23:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:22:15:22:15 | ControlFlowNode for l | Default value |
@@ -53,3 +56,4 @@ nodes
| test.py:92:5:92:5 | ControlFlowNode for d | test.py:96:26:96:26 | ControlFlowNode for d | test.py:92:5:92:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:96:26:96:26 | ControlFlowNode for d | Default value |
| test.py:115:5:115:5 | ControlFlowNode for d | test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:113:20:113:20 | ControlFlowNode for d | Default value |
| test.py:121:5:121:5 | ControlFlowNode for d | test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:119:29:119:29 | ControlFlowNode for d | Default value |
+| test.py:127:9:127:9 | ControlFlowNode for l | test.py:125:15:125:15 | ControlFlowNode for l | test.py:127:9:127:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:125:15:125:15 | ControlFlowNode for l | Default value |
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 60c3ff01b54..25ad530310e 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -120,3 +120,9 @@ def dict_update_op_nochange(d = {}):
x = {}
d |= x # FP
return d
+
+# OK
+def sanitizer(l = []):
+ if not l == []:
+ l.append(1) # FP
+ return l
From d458464e6b7a0563a3343fdc68b92ac118d7b95c Mon Sep 17 00:00:00 2001
From: Jorge <46056498+jorgectf@users.noreply.github.com>
Date: Thu, 26 Aug 2021 12:20:09 +0200
Subject: [PATCH 070/459] Apply suggestions from code review
Co-authored-by: Rasmus Wriedt Larsen
---
.../ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql | 1 +
python/ql/src/experimental/semmle/python/Concepts.qll | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql b/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
index 9899eae932f..5daaae5ff39 100644
--- a/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
+++ b/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.ql
@@ -7,6 +7,7 @@
* @tags experimental
* security
* external/cwe/cwe-522
+ * external/cwe/cwe-523
*/
// determine precision above
diff --git a/python/ql/src/experimental/semmle/python/Concepts.qll b/python/ql/src/experimental/semmle/python/Concepts.qll
index 3daacf53981..bdabebb804d 100644
--- a/python/ql/src/experimental/semmle/python/Concepts.qll
+++ b/python/ql/src/experimental/semmle/python/Concepts.qll
@@ -167,7 +167,7 @@ module LDAPBind {
abstract DataFlow::Node getPassword();
/**
- * Checks if the binding process use SSL.
+ * Holds if the binding process use SSL.
*/
abstract predicate useSSL();
}
@@ -195,7 +195,7 @@ class LDAPBind extends DataFlow::Node {
DataFlow::Node getPassword() { result = range.getPassword() }
/**
- * Checks if the binding process use SSL.
+ * Holds if the binding process use SSL.
*/
predicate useSSL() { range.useSSL() }
}
From 786edb72df5537b5a77df9d54057e47563f237e2 Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 26 Aug 2021 12:36:34 +0200
Subject: [PATCH 071/459] Update `.expected`
---
.../Security/CWE-522/LDAPInsecureAuth.expected | 16 ----------------
1 file changed, 16 deletions(-)
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected b/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
index fdcb414b981..13b5ba7b7b7 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
+++ b/python/ql/test/experimental/query-tests/Security/CWE-522/LDAPInsecureAuth.expected
@@ -1,9 +1,4 @@
edges
-| ldap3_remote.py:49:12:49:34 | ControlFlowNode for BinaryExpr | ldap3_remote.py:51:18:51:21 | ControlFlowNode for host |
-| ldap3_remote.py:88:21:88:27 | ControlFlowNode for request | ldap3_remote.py:88:21:88:32 | ControlFlowNode for Attribute |
-| ldap3_remote.py:88:21:88:32 | ControlFlowNode for Attribute | ldap3_remote.py:88:21:88:40 | ControlFlowNode for Subscript |
-| ldap3_remote.py:88:21:88:40 | ControlFlowNode for Subscript | ldap3_remote.py:90:18:90:21 | ControlFlowNode for host |
-| ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host |
| ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host |
| ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host |
| ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | ldap3_remote.py:138:21:138:32 | ControlFlowNode for Attribute |
@@ -12,14 +7,6 @@ edges
nodes
| ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
| ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
-| ldap3_remote.py:49:12:49:34 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
-| ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
-| ldap3_remote.py:88:21:88:27 | ControlFlowNode for request | semmle.label | ControlFlowNode for request |
-| ldap3_remote.py:88:21:88:32 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| ldap3_remote.py:88:21:88:40 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
-| ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
-| ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
-| ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
| ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
| ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | semmle.label | ControlFlowNode for host |
| ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | semmle.label | ControlFlowNode for BinaryExpr |
@@ -31,9 +18,6 @@ nodes
#select
| ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | $@ is authenticated insecurely. | ldap2_remote.py:45:41:45:60 | ControlFlowNode for BinaryExpr | This LDAP host |
| ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | $@ is authenticated insecurely. | ldap2_remote.py:56:41:56:60 | ControlFlowNode for BinaryExpr | This LDAP host |
-| ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | ldap3_remote.py:49:12:49:34 | ControlFlowNode for BinaryExpr | ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:51:18:51:21 | ControlFlowNode for host | This LDAP host |
-| ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | ldap3_remote.py:88:21:88:27 | ControlFlowNode for request | ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:90:18:90:21 | ControlFlowNode for host | This LDAP host |
-| ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | ldap3_remote.py:101:12:101:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:102:18:102:21 | ControlFlowNode for host | This LDAP host |
| ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | ldap3_remote.py:114:12:114:49 | ControlFlowNode for BinaryExpr | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:115:18:115:21 | ControlFlowNode for host | This LDAP host |
| ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | ldap3_remote.py:126:12:126:31 | ControlFlowNode for BinaryExpr | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:127:18:127:21 | ControlFlowNode for host | This LDAP host |
| ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | ldap3_remote.py:138:21:138:27 | ControlFlowNode for request | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | $@ is authenticated insecurely. | ldap3_remote.py:139:18:139:21 | ControlFlowNode for host | This LDAP host |
From 097c23e4379ed184320a908beada05869c81ff3c Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 26 Aug 2021 14:08:52 +0200
Subject: [PATCH 072/459] Python: add inline expectations test Consider
removing the original test
---
.../test.expected | 0
.../test.py | 38 +++++++++----------
.../test.ql | 24 ++++++++++++
3 files changed, 43 insertions(+), 19 deletions(-)
create mode 100644 python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.expected
create mode 100644 python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.ql
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.expected b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 25ad530310e..3c656611c59 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -1,31 +1,31 @@
# Not OK
def simple(l = [0]):
- l[0] = 1 # FN
+ l[0] = 1 #$ MISSING: modification=l
return l
# Not OK
def slice(l = [0]):
- l[0:1] = 1 # FN
+ l[0:1] = 1 #$ MISSING: modification=l
return l
# Not OK
def list_del(l = [0]):
- del l[0] # FN
+ del l[0] #$ MISSING: modification=l
return l
# Not OK
def append_op(l = []):
- l += 1
+ l += 1 #$ modification=l
return l
# Not OK
def repeat_op(l = [0]):
- l *= 3
+ l *= 3 #$ modification=l
return l
# Not OK
def append(l = []):
- l.append(1)
+ l.append(1) #$ modification=l
return l
# OK
@@ -36,7 +36,7 @@ def includes(l = []):
return x
def extends(l):
- l.extend([1])
+ l.extend([1]) #$ modification=l
return l
# Not OK
@@ -46,17 +46,17 @@ def deferred(l = []):
# Not OK
def nonempty(l = [5]):
- l.append(1)
+ l.append(1) #$ modification=l
return l
# Not OK
def dict(d = {}):
- d['a'] = 1 # FN
+ d['a'] = 1 #$ MISSING: modification=d
return d
# Not OK
def dict_nonempty(d = {'a': 1}):
- d['a'] = 2 # FN
+ d['a'] = 2 #$ MISSING: modification=d
return d
# OK
@@ -65,7 +65,7 @@ def dict_nonempty_nochange(d = {'a': 1}):
return d
def modifies(d):
- d['a'] = 1 # FN
+ d['a'] = 1 #$ MISSING: modification=d
return d
# Not OK
@@ -75,21 +75,21 @@ def dict_deferred(d = {}):
# Not OK
def dict_method(d = {}):
- d.update({'a': 1})
+ d.update({'a': 1}) #$ modification=d
return d
# Not OK
def dict_method_nonempty(d = {'a': 1}):
- d.update({'a': 2})
+ d.update({'a': 2}) #$ modification=d
return d
# OK
def dict_method_nonempty_nochange(d = {'a': 1}):
- d.update({'a': 1}) # FP
+ d.update({'a': 1}) #$ SPURIOUS:modification=d
return d
def modifies_method(d):
- d.update({'a': 1})
+ d.update({'a': 1}) #$ modification=d
return d
# Not OK
@@ -106,23 +106,23 @@ def dict_includes(d = {}):
# Not OK
def dict_del(d = {'a': 1}):
- del d['a'] # FN
+ del d['a'] #$ MISSING: modification=d
return d
# Not OK
def dict_update_op(d = {}):
x = {'a': 1}
- d |= x
+ d |= x #$ modification=d
return d
# OK
def dict_update_op_nochange(d = {}):
x = {}
- d |= x # FP
+ d |= x #$ SPURIOUS: modification=d
return d
# OK
def sanitizer(l = []):
if not l == []:
- l.append(1) # FP
+ l.append(1) #$ SPURIOUS: modification=l
return l
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.ql b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.ql
new file mode 100644
index 00000000000..cac05eebd22
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.ql
@@ -0,0 +1,24 @@
+import python
+import semmle.python.dataflow.new.DataFlow
+import TestUtilities.InlineExpectationsTest
+import experimental.dataflow.TestUtil.PrintNode
+import semmle.python.functions.ModificationOfParameterWithDefault
+
+class ModificationOfParameterWithDefaultTest extends InlineExpectationsTest {
+ ModificationOfParameterWithDefaultTest() { this = "ModificationOfParameterWithDefaultTest" }
+
+ override string getARelevantTag() { result = "modification" }
+
+ predicate relevant_node(DataFlow::Node sink) {
+ exists(ModificationOfParameterWithDefault::Configuration cfg | cfg.hasFlowTo(sink))
+ }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(DataFlow::Node n | relevant_node(n) |
+ n.getLocation() = location and
+ tag = "modification" and
+ value = prettyNode(n) and
+ element = n.toString()
+ )
+ }
+}
From 49ae549e892b20e254c3fe169638196d5d171a54 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Thu, 26 Aug 2021 14:29:18 +0200
Subject: [PATCH 073/459] Python: Implement modifying syntax
---
...ationOfParameterWithDefaultCustomizations.qll | 8 ++++++++
.../ModificationOfParameterWithDefault/test.py | 16 ++++++++--------
2 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
index c9be49b1fee..12255c5a641 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -97,8 +97,16 @@ module ModificationOfParameterWithDefault {
*/
class Mutation extends Sink {
Mutation() {
+ // assignment to a subscript (includes slices)
+ exists(DefinitionNode d | d.(SubscriptNode).getObject() = this.asCfgNode())
+ or
+ // deletion of a subscript
+ exists(DeletionNode d | d.getTarget().(SubscriptNode).getObject() = this.asCfgNode())
+ or
+ // augmented assignment to the value
exists(AugAssign a | a.getTarget().getAFlowNode() = this.asCfgNode())
or
+ // modifying function call
exists(DataFlow::CallCfgNode c, DataFlow::AttrRead a | c.getFunction() = a |
a.getObject() = this and
a.getAttributeName() in [list_modifying_method(), dict_modifying_method()]
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 3c656611c59..09ad7ed986c 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -1,16 +1,16 @@
# Not OK
def simple(l = [0]):
- l[0] = 1 #$ MISSING: modification=l
+ l[0] = 1 #$ modification=l
return l
# Not OK
def slice(l = [0]):
- l[0:1] = 1 #$ MISSING: modification=l
+ l[0:1] = 1 #$ modification=l
return l
# Not OK
def list_del(l = [0]):
- del l[0] #$ MISSING: modification=l
+ del l[0] #$ modification=l
return l
# Not OK
@@ -51,21 +51,21 @@ def nonempty(l = [5]):
# Not OK
def dict(d = {}):
- d['a'] = 1 #$ MISSING: modification=d
+ d['a'] = 1 #$ modification=d
return d
# Not OK
def dict_nonempty(d = {'a': 1}):
- d['a'] = 2 #$ MISSING: modification=d
+ d['a'] = 2 #$ modification=d
return d
# OK
def dict_nonempty_nochange(d = {'a': 1}):
- d['a'] = 1
+ d['a'] = 1 #$ SPURIOUS: modification=d
return d
def modifies(d):
- d['a'] = 1 #$ MISSING: modification=d
+ d['a'] = 1 #$ modification=d
return d
# Not OK
@@ -106,7 +106,7 @@ def dict_includes(d = {}):
# Not OK
def dict_del(d = {'a': 1}):
- del d['a'] #$ MISSING: modification=d
+ del d['a'] #$ modification=d
return d
# Not OK
From 64b305cf7a7c984737c90777dcdf72a7cec7d80c Mon Sep 17 00:00:00 2001
From: jorgectf
Date: Thu, 26 Aug 2021 23:29:45 +0200
Subject: [PATCH 074/459] Add `.qhelp` along with its example
---
.../Security/CWE-522/LDAPInsecureAuth.qhelp | 23 +++++++++++++++++++
.../CWE-522/examples/LDAPInsecureAuth.py | 20 ++++++++++++++++
.../Security/CWE-522/ldap3_remote.py | 2 +-
3 files changed, 44 insertions(+), 1 deletion(-)
create mode 100644 python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.qhelp
create mode 100644 python/ql/src/experimental/Security/CWE-522/examples/LDAPInsecureAuth.py
diff --git a/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.qhelp b/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.qhelp
new file mode 100644
index 00000000000..cd568432d73
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-522/LDAPInsecureAuth.qhelp
@@ -0,0 +1,23 @@
+
+
+
+
+
Failing to ensure the utilization of SSL in an LDAP connection can cause the entire communication
+to be sent in cleartext making it easier for an attacker to intercept it.
+
+
+
+
Always set use_SSL to True, call start_tls_s() or set a proper option flag (ldap.OPT_X_TLS_XXXXXX).
+
+
+
+
This example shows both good and bad ways to deal with this issue under Python 3.
+
+
The first one sets use_SSL to true as a keyword argument whereas the second one fails to provide a value for it, so
+the default one is used (False).
+
+
+
+
diff --git a/python/ql/src/experimental/Security/CWE-522/examples/LDAPInsecureAuth.py b/python/ql/src/experimental/Security/CWE-522/examples/LDAPInsecureAuth.py
new file mode 100644
index 00000000000..051ca07b0dc
--- /dev/null
+++ b/python/ql/src/experimental/Security/CWE-522/examples/LDAPInsecureAuth.py
@@ -0,0 +1,20 @@
+from ldap3 import Server, Connection, ALL
+from flask import request, Flask
+
+app = Flask(__name__)
+
+
+@app.route("/good")
+def good():
+ srv = Server(host, port, use_ssl=True)
+ conn = Connection(srv, dn, password)
+ conn.search(dn, search_filter)
+ return conn.response
+
+
+@app.route("/bad")
+def bad():
+ srv = Server(host, port)
+ conn = Connection(srv, dn, password)
+ conn.search(dn, search_filter)
+ return conn.response
diff --git a/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
index 09dadcfb0e0..dc0ef9cf977 100644
--- a/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
+++ b/python/ql/test/experimental/query-tests/Security/CWE-522/ldap3_remote.py
@@ -101,7 +101,7 @@ def eight():
host = schema + "somethingon.theinternet.com"
srv = Server(host, port=1337)
conn = Connection(srv, "dn", "password")
- conn.start_tls() # !
+ conn.start_tls()
conn.search("dn", "search_filter")
return conn.response
From a762373ad6565c5af8cfa987b6c105a414ada26f Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 30 Aug 2021 11:04:27 +0200
Subject: [PATCH 075/459] Python: Implement simple barrier guard The one found
in the original test case
---
.../ModificationOfParameterWithDefault.qll | 11 ++-
...onOfParameterWithDefaultCustomizations.qll | 53 +++++++++++++-
...odificationOfParameterWithDefault.expected | 36 ++++++++++
...odificationOfParameterWithDefault.expected | 70 +++++++++++++------
4 files changed, 143 insertions(+), 27 deletions(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
index 914eb7a473e..82acd10c3d7 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefault.qll
@@ -19,16 +19,21 @@ module ModificationOfParameterWithDefault {
* A data-flow configuration for detecting modifications of a parameters default value.
*/
class Configuration extends DataFlow::Configuration {
- Configuration() { this = "ModificationOfParameterWithDefault" }
+ boolean nonEmpty;
- override predicate isSource(DataFlow::Node source) { source instanceof Source }
+ Configuration() {
+ nonEmpty in [true, false] and
+ this = "ModificationOfParameterWithDefault:" + nonEmpty.toString()
+ }
+
+ override predicate isSource(DataFlow::Node source) { source.(Source).isNonEmpty() = nonEmpty }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isBarrier(DataFlow::Node node) { node instanceof Barrier }
override predicate isBarrierGuard(DataFlow::BarrierGuard guard) {
- guard instanceof BarrierGuard
+ guard.(BarrierGuard).blocksNonEmpty() = nonEmpty
}
}
}
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
index 12255c5a641..e7be292513e 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -16,7 +16,9 @@ module ModificationOfParameterWithDefault {
/**
* A data flow source for detecting modifications of a parameters default value.
*/
- abstract class Source extends DataFlow::Node { }
+ abstract class Source extends DataFlow::Node {
+ abstract boolean isNonEmpty();
+ }
/**
* A data flow sink for detecting modifications of a parameters default value.
@@ -31,7 +33,9 @@ module ModificationOfParameterWithDefault {
/**
* A sanitizer guard for detecting modifications of a parameters default value.
*/
- abstract class BarrierGuard extends DataFlow::BarrierGuard { }
+ abstract class BarrierGuard extends DataFlow::BarrierGuard {
+ abstract boolean blocksNonEmpty();
+ }
/** Gets the truthiness (non emptyness) of the default of `p` if that value is mutable */
private boolean mutableDefaultValue(Parameter p) {
@@ -55,6 +59,8 @@ module ModificationOfParameterWithDefault {
boolean nonEmpty;
MutableDefaultValue() { nonEmpty = mutableDefaultValue(this.asCfgNode().(NameNode).getNode()) }
+
+ override boolean isNonEmpty() { result = nonEmpty }
}
/**
@@ -113,4 +119,47 @@ module ModificationOfParameterWithDefault {
)
}
}
+
+ private class IdentityGuarded extends Expr {
+ boolean inverted;
+
+ IdentityGuarded() {
+ this = any(If i).getTest() and
+ inverted = false
+ or
+ exists(IdentityGuarded ig, UnaryExpr notExp |
+ notExp.getOp() instanceof Not and
+ ig = notExp and
+ notExp.getOperand() = this
+ |
+ inverted = ig.isInverted().booleanNot()
+ )
+ }
+
+ boolean isInverted() { result = inverted }
+ }
+
+ class IdentityGuard extends BarrierGuard {
+ ControlFlowNode checked_node;
+ boolean safe_branch;
+ boolean nonEmpty;
+
+ IdentityGuard() {
+ nonEmpty in [true, false] and
+ exists(IdentityGuarded ig |
+ this.getNode() = ig and
+ checked_node = this and
+ // The raw guard is true if the value is non-empty
+ // So we are safe either if we are looking for a non-empty value
+ // or if we are looking for an empty value and the guard is inverted.
+ safe_branch = ig.isInverted().booleanXor(nonEmpty)
+ )
+ }
+
+ override predicate checks(ControlFlowNode node, boolean branch) {
+ node = checked_node and branch = safe_branch
+ }
+
+ override boolean blocksNonEmpty() { result = nonEmpty }
+ }
}
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
index c07a609dceb..f5c6f3a6baa 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/ModificationOfParameterWithDefault.expected
@@ -1,4 +1,7 @@
edges
+| test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l |
+| test.py:7:11:7:11 | ControlFlowNode for l | test.py:8:5:8:5 | ControlFlowNode for l |
+| test.py:12:14:12:14 | ControlFlowNode for l | test.py:13:9:13:9 | ControlFlowNode for l |
| test.py:17:15:17:15 | ControlFlowNode for l | test.py:18:5:18:5 | ControlFlowNode for l |
| test.py:22:15:22:15 | ControlFlowNode for l | test.py:23:5:23:5 | ControlFlowNode for l |
| test.py:27:12:27:12 | ControlFlowNode for l | test.py:28:5:28:5 | ControlFlowNode for l |
@@ -6,16 +9,29 @@ edges
| test.py:43:14:43:14 | ControlFlowNode for l | test.py:44:13:44:13 | ControlFlowNode for l |
| test.py:44:13:44:13 | ControlFlowNode for l | test.py:38:13:38:13 | ControlFlowNode for l |
| test.py:48:14:48:14 | ControlFlowNode for l | test.py:49:5:49:5 | ControlFlowNode for l |
+| test.py:53:10:53:10 | ControlFlowNode for d | test.py:54:5:54:5 | ControlFlowNode for d |
+| test.py:58:19:58:19 | ControlFlowNode for d | test.py:59:5:59:5 | ControlFlowNode for d |
+| test.py:63:28:63:28 | ControlFlowNode for d | test.py:64:5:64:5 | ControlFlowNode for d |
+| test.py:67:14:67:14 | ControlFlowNode for d | test.py:68:5:68:5 | ControlFlowNode for d |
+| test.py:72:19:72:19 | ControlFlowNode for d | test.py:73:14:73:14 | ControlFlowNode for d |
+| test.py:73:14:73:14 | ControlFlowNode for d | test.py:67:14:67:14 | ControlFlowNode for d |
| test.py:77:17:77:17 | ControlFlowNode for d | test.py:78:5:78:5 | ControlFlowNode for d |
| test.py:82:26:82:26 | ControlFlowNode for d | test.py:83:5:83:5 | ControlFlowNode for d |
| test.py:87:35:87:35 | ControlFlowNode for d | test.py:88:5:88:5 | ControlFlowNode for d |
| test.py:91:21:91:21 | ControlFlowNode for d | test.py:92:5:92:5 | ControlFlowNode for d |
| test.py:96:26:96:26 | ControlFlowNode for d | test.py:97:21:97:21 | ControlFlowNode for d |
| test.py:97:21:97:21 | ControlFlowNode for d | test.py:91:21:91:21 | ControlFlowNode for d |
+| test.py:108:14:108:14 | ControlFlowNode for d | test.py:109:9:109:9 | ControlFlowNode for d |
| test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d |
| test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d |
| test.py:125:15:125:15 | ControlFlowNode for l | test.py:127:9:127:9 | ControlFlowNode for l |
nodes
+| test.py:2:12:2:12 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:3:5:3:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:7:11:7:11 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:8:5:8:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:12:14:12:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:13:9:13:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:17:15:17:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:18:5:18:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:22:15:22:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
@@ -28,6 +44,16 @@ nodes
| test.py:44:13:44:13 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:48:14:48:14 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:49:5:49:5 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
+| test.py:53:10:53:10 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:54:5:54:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:58:19:58:19 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:59:5:59:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:63:28:63:28 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:64:5:64:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:67:14:67:14 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:68:5:68:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:72:19:72:19 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:73:14:73:14 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:77:17:77:17 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:78:5:78:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:82:26:82:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
@@ -38,6 +64,8 @@ nodes
| test.py:92:5:92:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:96:26:96:26 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:97:21:97:21 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:108:14:108:14 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
+| test.py:109:9:109:9 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:113:20:113:20 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:115:5:115:5 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
| test.py:119:29:119:29 | ControlFlowNode for d | semmle.label | ControlFlowNode for d |
@@ -45,15 +73,23 @@ nodes
| test.py:125:15:125:15 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
| test.py:127:9:127:9 | ControlFlowNode for l | semmle.label | ControlFlowNode for l |
#select
+| test.py:3:5:3:5 | ControlFlowNode for l | test.py:2:12:2:12 | ControlFlowNode for l | test.py:3:5:3:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:2:12:2:12 | ControlFlowNode for l | Default value |
+| test.py:8:5:8:5 | ControlFlowNode for l | test.py:7:11:7:11 | ControlFlowNode for l | test.py:8:5:8:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:7:11:7:11 | ControlFlowNode for l | Default value |
+| test.py:13:9:13:9 | ControlFlowNode for l | test.py:12:14:12:14 | ControlFlowNode for l | test.py:13:9:13:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:12:14:12:14 | ControlFlowNode for l | Default value |
| test.py:18:5:18:5 | ControlFlowNode for l | test.py:17:15:17:15 | ControlFlowNode for l | test.py:18:5:18:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:17:15:17:15 | ControlFlowNode for l | Default value |
| test.py:23:5:23:5 | ControlFlowNode for l | test.py:22:15:22:15 | ControlFlowNode for l | test.py:23:5:23:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:22:15:22:15 | ControlFlowNode for l | Default value |
| test.py:28:5:28:5 | ControlFlowNode for l | test.py:27:12:27:12 | ControlFlowNode for l | test.py:28:5:28:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:27:12:27:12 | ControlFlowNode for l | Default value |
| test.py:39:5:39:5 | ControlFlowNode for l | test.py:43:14:43:14 | ControlFlowNode for l | test.py:39:5:39:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:43:14:43:14 | ControlFlowNode for l | Default value |
| test.py:49:5:49:5 | ControlFlowNode for l | test.py:48:14:48:14 | ControlFlowNode for l | test.py:49:5:49:5 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:48:14:48:14 | ControlFlowNode for l | Default value |
+| test.py:54:5:54:5 | ControlFlowNode for d | test.py:53:10:53:10 | ControlFlowNode for d | test.py:54:5:54:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:53:10:53:10 | ControlFlowNode for d | Default value |
+| test.py:59:5:59:5 | ControlFlowNode for d | test.py:58:19:58:19 | ControlFlowNode for d | test.py:59:5:59:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:58:19:58:19 | ControlFlowNode for d | Default value |
+| test.py:64:5:64:5 | ControlFlowNode for d | test.py:63:28:63:28 | ControlFlowNode for d | test.py:64:5:64:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:63:28:63:28 | ControlFlowNode for d | Default value |
+| test.py:68:5:68:5 | ControlFlowNode for d | test.py:72:19:72:19 | ControlFlowNode for d | test.py:68:5:68:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:72:19:72:19 | ControlFlowNode for d | Default value |
| test.py:78:5:78:5 | ControlFlowNode for d | test.py:77:17:77:17 | ControlFlowNode for d | test.py:78:5:78:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:77:17:77:17 | ControlFlowNode for d | Default value |
| test.py:83:5:83:5 | ControlFlowNode for d | test.py:82:26:82:26 | ControlFlowNode for d | test.py:83:5:83:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:82:26:82:26 | ControlFlowNode for d | Default value |
| test.py:88:5:88:5 | ControlFlowNode for d | test.py:87:35:87:35 | ControlFlowNode for d | test.py:88:5:88:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:87:35:87:35 | ControlFlowNode for d | Default value |
| test.py:92:5:92:5 | ControlFlowNode for d | test.py:96:26:96:26 | ControlFlowNode for d | test.py:92:5:92:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:96:26:96:26 | ControlFlowNode for d | Default value |
+| test.py:109:9:109:9 | ControlFlowNode for d | test.py:108:14:108:14 | ControlFlowNode for d | test.py:109:9:109:9 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:108:14:108:14 | ControlFlowNode for d | Default value |
| test.py:115:5:115:5 | ControlFlowNode for d | test.py:113:20:113:20 | ControlFlowNode for d | test.py:115:5:115:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:113:20:113:20 | ControlFlowNode for d | Default value |
| test.py:121:5:121:5 | ControlFlowNode for d | test.py:119:29:119:29 | ControlFlowNode for d | test.py:121:5:121:5 | ControlFlowNode for d | $@ flows to here and is mutated. | test.py:119:29:119:29 | ControlFlowNode for d | Default value |
| test.py:127:9:127:9 | ControlFlowNode for l | test.py:125:15:125:15 | ControlFlowNode for l | test.py:127:9:127:9 | ControlFlowNode for l | $@ flows to here and is mutated. | test.py:125:15:125:15 | ControlFlowNode for l | Default value |
diff --git a/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected b/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected
index 08586d02c10..c9460845e29 100644
--- a/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected
+++ b/python/ql/test/query-tests/Functions/general/ModificationOfParameterWithDefault.expected
@@ -1,24 +1,50 @@
edges
-| functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value |
-| functions_test.py:133:15:133:15 | empty mutable value | functions_test.py:134:5:134:5 | empty mutable value |
-| functions_test.py:151:25:151:25 | empty mutable value | functions_test.py:152:5:152:5 | empty mutable value |
-| functions_test.py:154:21:154:21 | empty mutable value | functions_test.py:155:5:155:5 | empty mutable value |
-| functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:158:25:158:25 | empty mutable value |
-| functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:159:21:159:21 | empty mutable value |
-| functions_test.py:158:25:158:25 | empty mutable value | functions_test.py:151:25:151:25 | empty mutable value |
-| functions_test.py:159:21:159:21 | empty mutable value | functions_test.py:154:21:154:21 | empty mutable value |
-| functions_test.py:175:28:175:28 | non-empty mutable value | functions_test.py:179:9:179:9 | non-empty mutable value |
-| functions_test.py:175:28:175:28 | non-empty mutable value | functions_test.py:181:9:181:9 | non-empty mutable value |
-| functions_test.py:188:18:188:18 | non-empty mutable value | functions_test.py:189:28:189:28 | non-empty mutable value |
-| functions_test.py:189:28:189:28 | non-empty mutable value | functions_test.py:175:28:175:28 | non-empty mutable value |
-| functions_test.py:191:18:191:18 | non-empty mutable value | functions_test.py:192:28:192:28 | non-empty mutable value |
-| functions_test.py:192:28:192:28 | non-empty mutable value | functions_test.py:175:28:175:28 | non-empty mutable value |
+| functions_test.py:39:9:39:9 | ControlFlowNode for x | functions_test.py:40:5:40:5 | ControlFlowNode for x |
+| functions_test.py:133:15:133:15 | ControlFlowNode for x | functions_test.py:134:5:134:5 | ControlFlowNode for x |
+| functions_test.py:151:25:151:25 | ControlFlowNode for x | functions_test.py:152:5:152:5 | ControlFlowNode for x |
+| functions_test.py:154:21:154:21 | ControlFlowNode for x | functions_test.py:155:5:155:5 | ControlFlowNode for x |
+| functions_test.py:157:27:157:27 | ControlFlowNode for y | functions_test.py:158:25:158:25 | ControlFlowNode for y |
+| functions_test.py:157:27:157:27 | ControlFlowNode for y | functions_test.py:159:21:159:21 | ControlFlowNode for y |
+| functions_test.py:158:25:158:25 | ControlFlowNode for y | functions_test.py:151:25:151:25 | ControlFlowNode for x |
+| functions_test.py:159:21:159:21 | ControlFlowNode for y | functions_test.py:154:21:154:21 | ControlFlowNode for x |
+| functions_test.py:166:21:166:25 | ControlFlowNode for param | functions_test.py:170:9:170:13 | ControlFlowNode for param |
+| functions_test.py:166:21:166:25 | ControlFlowNode for param | functions_test.py:170:9:170:13 | ControlFlowNode for param |
+| functions_test.py:175:28:175:28 | ControlFlowNode for x | functions_test.py:179:9:179:9 | ControlFlowNode for x |
+| functions_test.py:175:28:175:28 | ControlFlowNode for x | functions_test.py:181:9:181:9 | ControlFlowNode for x |
+| functions_test.py:188:18:188:18 | ControlFlowNode for x | functions_test.py:189:28:189:28 | ControlFlowNode for x |
+| functions_test.py:189:28:189:28 | ControlFlowNode for x | functions_test.py:175:28:175:28 | ControlFlowNode for x |
+| functions_test.py:191:18:191:18 | ControlFlowNode for x | functions_test.py:192:28:192:28 | ControlFlowNode for x |
+| functions_test.py:192:28:192:28 | ControlFlowNode for x | functions_test.py:175:28:175:28 | ControlFlowNode for x |
+nodes
+| functions_test.py:39:9:39:9 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:40:5:40:5 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:133:15:133:15 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:134:5:134:5 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:151:25:151:25 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:152:5:152:5 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:154:21:154:21 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:155:5:155:5 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:157:27:157:27 | ControlFlowNode for y | semmle.label | ControlFlowNode for y |
+| functions_test.py:158:25:158:25 | ControlFlowNode for y | semmle.label | ControlFlowNode for y |
+| functions_test.py:159:21:159:21 | ControlFlowNode for y | semmle.label | ControlFlowNode for y |
+| functions_test.py:166:21:166:25 | ControlFlowNode for param | semmle.label | ControlFlowNode for param |
+| functions_test.py:170:9:170:13 | ControlFlowNode for param | semmle.label | ControlFlowNode for param |
+| functions_test.py:170:9:170:13 | ControlFlowNode for param | semmle.label | ControlFlowNode for param |
+| functions_test.py:175:28:175:28 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:179:9:179:9 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:181:9:181:9 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:188:18:188:18 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:189:28:189:28 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:191:18:191:18 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| functions_test.py:192:28:192:28 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
#select
-| functions_test.py:40:5:40:5 | x | functions_test.py:39:9:39:9 | empty mutable value | functions_test.py:40:5:40:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:39:9:39:9 | x | Default value |
-| functions_test.py:134:5:134:5 | x | functions_test.py:133:15:133:15 | empty mutable value | functions_test.py:134:5:134:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:133:15:133:15 | x | Default value |
-| functions_test.py:152:5:152:5 | x | functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:152:5:152:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | y | Default value |
-| functions_test.py:155:5:155:5 | x | functions_test.py:157:27:157:27 | empty mutable value | functions_test.py:155:5:155:5 | empty mutable value | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | y | Default value |
-| functions_test.py:179:9:179:9 | x | functions_test.py:188:18:188:18 | non-empty mutable value | functions_test.py:179:9:179:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:188:18:188:18 | x | Default value |
-| functions_test.py:179:9:179:9 | x | functions_test.py:191:18:191:18 | non-empty mutable value | functions_test.py:179:9:179:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:191:18:191:18 | x | Default value |
-| functions_test.py:181:9:181:9 | x | functions_test.py:188:18:188:18 | non-empty mutable value | functions_test.py:181:9:181:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:188:18:188:18 | x | Default value |
-| functions_test.py:181:9:181:9 | x | functions_test.py:191:18:191:18 | non-empty mutable value | functions_test.py:181:9:181:9 | non-empty mutable value | $@ flows to here and is mutated. | functions_test.py:191:18:191:18 | x | Default value |
+| functions_test.py:40:5:40:5 | ControlFlowNode for x | functions_test.py:39:9:39:9 | ControlFlowNode for x | functions_test.py:40:5:40:5 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:39:9:39:9 | ControlFlowNode for x | Default value |
+| functions_test.py:134:5:134:5 | ControlFlowNode for x | functions_test.py:133:15:133:15 | ControlFlowNode for x | functions_test.py:134:5:134:5 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:133:15:133:15 | ControlFlowNode for x | Default value |
+| functions_test.py:152:5:152:5 | ControlFlowNode for x | functions_test.py:157:27:157:27 | ControlFlowNode for y | functions_test.py:152:5:152:5 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | ControlFlowNode for y | Default value |
+| functions_test.py:155:5:155:5 | ControlFlowNode for x | functions_test.py:157:27:157:27 | ControlFlowNode for y | functions_test.py:155:5:155:5 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:157:27:157:27 | ControlFlowNode for y | Default value |
+| functions_test.py:170:9:170:13 | ControlFlowNode for param | functions_test.py:166:21:166:25 | ControlFlowNode for param | functions_test.py:170:9:170:13 | ControlFlowNode for param | $@ flows to here and is mutated. | functions_test.py:166:21:166:25 | ControlFlowNode for param | Default value |
+| functions_test.py:170:9:170:13 | ControlFlowNode for param | functions_test.py:166:21:166:25 | ControlFlowNode for param | functions_test.py:170:9:170:13 | ControlFlowNode for param | $@ flows to here and is mutated. | functions_test.py:166:21:166:25 | ControlFlowNode for param | Default value |
+| functions_test.py:179:9:179:9 | ControlFlowNode for x | functions_test.py:188:18:188:18 | ControlFlowNode for x | functions_test.py:179:9:179:9 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:188:18:188:18 | ControlFlowNode for x | Default value |
+| functions_test.py:179:9:179:9 | ControlFlowNode for x | functions_test.py:191:18:191:18 | ControlFlowNode for x | functions_test.py:179:9:179:9 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:191:18:191:18 | ControlFlowNode for x | Default value |
+| functions_test.py:181:9:181:9 | ControlFlowNode for x | functions_test.py:188:18:188:18 | ControlFlowNode for x | functions_test.py:181:9:181:9 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:188:18:188:18 | ControlFlowNode for x | Default value |
+| functions_test.py:181:9:181:9 | ControlFlowNode for x | functions_test.py:191:18:191:18 | ControlFlowNode for x | functions_test.py:181:9:181:9 | ControlFlowNode for x | $@ flows to here and is mutated. | functions_test.py:191:18:191:18 | ControlFlowNode for x | Default value |
From 1903cb8f829949d38d10020c7986e9011866a105 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 30 Aug 2021 11:27:55 +0200
Subject: [PATCH 076/459] Python: Add change note
---
python/change-notes/2021-08-30-port-modifying-default-query.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 python/change-notes/2021-08-30-port-modifying-default-query.md
diff --git a/python/change-notes/2021-08-30-port-modifying-default-query.md b/python/change-notes/2021-08-30-port-modifying-default-query.md
new file mode 100644
index 00000000000..2fcd9a11ded
--- /dev/null
+++ b/python/change-notes/2021-08-30-port-modifying-default-query.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Updated _Modification of parameter with default_ (`py/modification-of-default-value`) query to use the new data flow library instead of the old taint tracking library and to remove the use of points-to analysis. You may see differences in the results found by the query, but overall this change should result in a more robust and accurate analysis.
From 789e2e48cff835087d0adbd265abb155217ef907 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 18 Aug 2021 09:17:49 +0200
Subject: [PATCH 077/459] C#: Remove temporary dispatch restriction
---
csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll b/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
index 37ed99c37bb..f06fbca375d 100644
--- a/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dispatch/Dispatch.qll
@@ -508,7 +508,7 @@ private module Internal {
override RuntimeCallable getADynamicTarget() {
result = getAViableInherited()
or
- result = getAViableOverrider() and strictcount(getAViableOverrider()) < 1000
+ result = getAViableOverrider()
or
// Simple case: target method cannot be overridden
result = getAStaticTarget() and
From 0de621edf97253a215150e0d52d494fe7c2244c7 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 30 Aug 2021 15:03:58 +0200
Subject: [PATCH 078/459] Python: Add qldoc
---
.../ModificationOfParameterWithDefaultCustomizations.qll | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
index e7be292513e..96dd41adfcb 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -139,6 +139,9 @@ module ModificationOfParameterWithDefault {
boolean isInverted() { result = inverted }
}
+ /**
+ * A check for the value being truthy or falsy can guard against modifying the default value.
+ */
class IdentityGuard extends BarrierGuard {
ControlFlowNode checked_node;
boolean safe_branch;
@@ -149,7 +152,7 @@ module ModificationOfParameterWithDefault {
exists(IdentityGuarded ig |
this.getNode() = ig and
checked_node = this and
- // The raw guard is true if the value is non-empty
+ // The raw guard is true if the value is non-empty.
// So we are safe either if we are looking for a non-empty value
// or if we are looking for an empty value and the guard is inverted.
safe_branch = ig.isInverted().booleanXor(nonEmpty)
From a855074588b1b007a771984ae5bd88364366e3a9 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 30 Aug 2021 15:41:51 +0200
Subject: [PATCH 079/459] Python: Try to remove py2/3 differences
---
python/ql/test/query-tests/Functions/general/options | 1 +
1 file changed, 1 insertion(+)
create mode 100644 python/ql/test/query-tests/Functions/general/options
diff --git a/python/ql/test/query-tests/Functions/general/options b/python/ql/test/query-tests/Functions/general/options
new file mode 100644
index 00000000000..2032dd9e54c
--- /dev/null
+++ b/python/ql/test/query-tests/Functions/general/options
@@ -0,0 +1 @@
+semmle-extractor-options: --max-import-depth=1 --dont-split-graph
From 7fc536db1521165ee9adcd69695e3ed78a43bcdb Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Tue, 17 Aug 2021 13:09:45 +0200
Subject: [PATCH 080/459] Data flow: Add precise call contexts to stage 2
---
.../csharp/dataflow/internal/DataFlowImpl.qll | 29 ++++++++++---------
.../dataflow/internal/DataFlowImplCommon.qll | 22 +++++++++++---
2 files changed, 33 insertions(+), 18 deletions(-)
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
index 5c2dbb30084..f2c742a52ae 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -2117,7 +2118,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll
index 728f7b56c42..f588a25a176 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImplCommon.qll
@@ -786,13 +786,18 @@ private module Cached {
}
/**
- * Holds if the call context `call` either improves virtual dispatch in
- * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ * Holds if the call context `call` improves virtual dispatch in `callable`.
*/
cached
- predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) {
reducedViableImplInCallContext(_, callable, call)
- or
+ }
+
+ /**
+ * Holds if the call context `call` allows us to prune unreachable nodes in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
@@ -846,6 +851,15 @@ private module Cached {
TAccessPathFrontSome(AccessPathFront apf)
}
+/**
+ * Holds if the call context `call` either improves virtual dispatch in
+ * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ */
+predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ recordDataFlowCallSiteDispatch(call, callable) or
+ recordDataFlowCallSiteUnreachable(call, callable)
+}
+
/**
* A `Node` at which a cast can occur such that the type should be checked.
*/
From 136c8b5192f43ea226c08a4771e4f3598f3ceb14 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 25 Aug 2021 11:02:45 +0200
Subject: [PATCH 081/459] Data flow: Improve `callMayFlowThroughFwd` join order
Before:
```
[2021-08-25 09:56:29] (1395s) Tuple counts for DataFlowImpl2::Stage3::callMayFlowThroughFwd#ff/2@111fb3:
15495496 ~5% {5} r1 = SCAN DataFlowImpl2::Stage3::fwdFlowOutFromArg#fffff#reorder_0_2_4_1_3 OUTPUT In.3, In.4, In.2 'config', In.0 'call', In.1
1450611958 ~6335% {5} r2 = JOIN r1 WITH DataFlowImpl2::Stage3::fwdFlow#fffff_03412#join_rhs ON FIRST 3 OUTPUT Lhs.3 'call', Lhs.4, Lhs.2 'config', Rhs.3, Rhs.4
7043648 ~20415% {2} r3 = JOIN r2 WITH DataFlowImpl2::Stage3::fwdFlowIsEntered#fffff#reorder_0_3_4_1_2 ON FIRST 5 OUTPUT Lhs.0 'call', Lhs.2 'config'
return r3
```
After:
```
[2021-08-25 10:57:02] (2652s) Tuple counts for DataFlowImpl2::Stage3::callMayFlowThroughFwd#ff/2@d3e27b:
15495496 ~0% {6} r1 = SCAN DataFlowImpl2::Stage3::fwdFlowOutFromArg#fffff#reorder_0_2_4_1_3 OUTPUT In.0 'call', In.1, In.2 'config', In.3, In.4, In.2 'config'
9236888 ~22% {7} r2 = JOIN r1 WITH DataFlowImpl2::Stage3::fwdFlowIsEntered#fffff#reorder_0_3_4_1_2 ON FIRST 3 OUTPUT Lhs.3, Rhs.3, Rhs.4, Lhs.4, Lhs.5, Lhs.0 'call', Lhs.2 'config'
7043648 ~20415% {2} r3 = JOIN r2 WITH DataFlowImpl2::Stage3::fwdFlow#fffff ON FIRST 5 OUTPUT Lhs.5 'call', Lhs.6 'config'
return r3
```
---
.../csharp/dataflow/internal/DataFlowImpl.qll | 21 ++++++++-----------
1 file changed, 9 insertions(+), 12 deletions(-)
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
index f2c742a52ae..bd6ca5e996b 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll
@@ -1170,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1858,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2616,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
From c3ecae503ba85620442e0635e14b83a758e3f63e Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 1 Sep 2021 19:57:44 +0200
Subject: [PATCH 082/459] Data flow: Sync files
---
.../cpp/dataflow/internal/DataFlowImpl.qll | 50 +++++++++----------
.../cpp/dataflow/internal/DataFlowImpl2.qll | 50 +++++++++----------
.../cpp/dataflow/internal/DataFlowImpl3.qll | 50 +++++++++----------
.../cpp/dataflow/internal/DataFlowImpl4.qll | 50 +++++++++----------
.../dataflow/internal/DataFlowImplCommon.qll | 22 ++++++--
.../dataflow/internal/DataFlowImplLocal.qll | 50 +++++++++----------
.../cpp/ir/dataflow/internal/DataFlowImpl.qll | 50 +++++++++----------
.../ir/dataflow/internal/DataFlowImpl2.qll | 50 +++++++++----------
.../ir/dataflow/internal/DataFlowImpl3.qll | 50 +++++++++----------
.../ir/dataflow/internal/DataFlowImpl4.qll | 50 +++++++++----------
.../dataflow/internal/DataFlowImplCommon.qll | 22 ++++++--
.../dataflow/internal/DataFlowImpl2.qll | 50 +++++++++----------
.../dataflow/internal/DataFlowImpl3.qll | 50 +++++++++----------
.../dataflow/internal/DataFlowImpl4.qll | 50 +++++++++----------
.../dataflow/internal/DataFlowImpl5.qll | 50 +++++++++----------
.../java/dataflow/internal/DataFlowImpl.qll | 50 +++++++++----------
.../java/dataflow/internal/DataFlowImpl2.qll | 50 +++++++++----------
.../java/dataflow/internal/DataFlowImpl3.qll | 50 +++++++++----------
.../java/dataflow/internal/DataFlowImpl4.qll | 50 +++++++++----------
.../java/dataflow/internal/DataFlowImpl5.qll | 50 +++++++++----------
.../java/dataflow/internal/DataFlowImpl6.qll | 50 +++++++++----------
.../dataflow/internal/DataFlowImplCommon.qll | 22 ++++++--
.../DataFlowImplForSerializability.qll | 50 +++++++++----------
.../dataflow/new/internal/DataFlowImpl.qll | 50 +++++++++----------
.../dataflow/new/internal/DataFlowImpl2.qll | 50 +++++++++----------
.../dataflow/new/internal/DataFlowImpl3.qll | 50 +++++++++----------
.../dataflow/new/internal/DataFlowImpl4.qll | 50 +++++++++----------
.../new/internal/DataFlowImplCommon.qll | 22 ++++++--
28 files changed, 648 insertions(+), 640 deletions(-)
diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
index 728f7b56c42..f588a25a176 100644
--- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
+++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
@@ -786,13 +786,18 @@ private module Cached {
}
/**
- * Holds if the call context `call` either improves virtual dispatch in
- * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ * Holds if the call context `call` improves virtual dispatch in `callable`.
*/
cached
- predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) {
reducedViableImplInCallContext(_, callable, call)
- or
+ }
+
+ /**
+ * Holds if the call context `call` allows us to prune unreachable nodes in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
@@ -846,6 +851,15 @@ private module Cached {
TAccessPathFrontSome(AccessPathFront apf)
}
+/**
+ * Holds if the call context `call` either improves virtual dispatch in
+ * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ */
+predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ recordDataFlowCallSiteDispatch(call, callable) or
+ recordDataFlowCallSiteUnreachable(call, callable)
+}
+
/**
* A `Node` at which a cast can occur such that the type should be checked.
*/
diff --git a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
+++ b/cpp/ql/lib/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll
index 728f7b56c42..f588a25a176 100644
--- a/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll
+++ b/cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll
@@ -786,13 +786,18 @@ private module Cached {
}
/**
- * Holds if the call context `call` either improves virtual dispatch in
- * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ * Holds if the call context `call` improves virtual dispatch in `callable`.
*/
cached
- predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) {
reducedViableImplInCallContext(_, callable, call)
- or
+ }
+
+ /**
+ * Holds if the call context `call` allows us to prune unreachable nodes in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
@@ -846,6 +851,15 @@ private module Cached {
TAccessPathFrontSome(AccessPathFront apf)
}
+/**
+ * Holds if the call context `call` either improves virtual dispatch in
+ * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ */
+predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ recordDataFlowCallSiteDispatch(call, callable) or
+ recordDataFlowCallSiteUnreachable(call, callable)
+}
+
/**
* A `Node` at which a cast can occur such that the type should be checked.
*/
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
+++ b/csharp/ql/lib/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImpl6.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
index 728f7b56c42..f588a25a176 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
@@ -786,13 +786,18 @@ private module Cached {
}
/**
- * Holds if the call context `call` either improves virtual dispatch in
- * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ * Holds if the call context `call` improves virtual dispatch in `callable`.
*/
cached
- predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) {
reducedViableImplInCallContext(_, callable, call)
- or
+ }
+
+ /**
+ * Holds if the call context `call` allows us to prune unreachable nodes in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
@@ -846,6 +851,15 @@ private module Cached {
TAccessPathFrontSome(AccessPathFront apf)
}
+/**
+ * Holds if the call context `call` either improves virtual dispatch in
+ * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ */
+predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ recordDataFlowCallSiteDispatch(call, callable) or
+ recordDataFlowCallSiteUnreachable(call, callable)
+}
+
/**
* A `Node` at which a cast can occur such that the type should be checked.
*/
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/DataFlowImplForSerializability.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl2.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl3.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
index 5c2dbb30084..bd6ca5e996b 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImpl4.qll
@@ -923,28 +923,29 @@ private module Stage2 {
ApOption apSome(Ap ap) { result = TBooleanSome(ap) }
- class Cc = boolean;
+ class Cc = CallContext;
- class CcCall extends Cc {
- CcCall() { this = true }
+ class CcCall = CallContextCall;
- /** Holds if this call context may be `call`. */
- predicate matchesCall(DataFlowCall call) { any() }
- }
+ class CcNoCall = CallContextNoCall;
- class CcNoCall extends Cc {
- CcNoCall() { this = false }
- }
-
- Cc ccNone() { result = false }
+ Cc ccNone() { result instanceof CallContextAny }
private class LocalCc = Unit;
bindingset[call, c, outercc]
- private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) { any() }
+ private CcCall getCallContextCall(DataFlowCall call, DataFlowCallable c, Cc outercc) {
+ checkCallContextCall(outercc, call, c) and
+ if recordDataFlowCallSiteDispatch(call, c)
+ then result = TSpecificCall(call)
+ else result = TSomeCall()
+ }
bindingset[call, c, innercc]
- private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) { any() }
+ private CcNoCall getCallContextReturn(DataFlowCallable c, DataFlowCall call, Cc innercc) {
+ checkCallContextReturn(innercc, c, call) and
+ if reducedViableImplInReturn(c, call) then result = TReturn(c, call) else result = ccNone()
+ }
bindingset[node, cc, config]
private LocalCc getLocalCc(NodeEx node, Cc cc, Configuration config) { any() }
@@ -1169,11 +1170,10 @@ private module Stage2 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -1857,11 +1857,10 @@ private module Stage3 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
@@ -2117,7 +2116,7 @@ private module Stage3 {
private predicate flowCandSummaryCtx(NodeEx node, AccessPathFront argApf, Configuration config) {
exists(AccessPathFront apf |
Stage3::revFlow(node, true, _, apf, config) and
- Stage3::fwdFlow(node, true, TAccessPathFrontSome(argApf), apf, config)
+ Stage3::fwdFlow(node, any(Stage3::CcCall ccc), TAccessPathFrontSome(argApf), apf, config)
)
}
@@ -2615,11 +2614,10 @@ private module Stage4 {
pragma[nomagic]
private predicate callMayFlowThroughFwd(DataFlowCall call, Configuration config) {
exists(Ap argAp0, NodeEx out, Cc cc, ApOption argAp, Ap ap |
- fwdFlow(out, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), ap,
- pragma[only_bind_into](config)) and
+ fwdFlow(pragma[only_bind_out](out), pragma[only_bind_out](cc), pragma[only_bind_out](argAp),
+ pragma[only_bind_out](ap), pragma[only_bind_out](config)) and
fwdFlowOutFromArg(call, out, argAp0, ap, config) and
- fwdFlowIsEntered(call, pragma[only_bind_into](cc), pragma[only_bind_into](argAp), argAp0,
- pragma[only_bind_into](config))
+ fwdFlowIsEntered(call, cc, argAp, argAp0, config)
)
}
diff --git a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll
index 728f7b56c42..f588a25a176 100644
--- a/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll
+++ b/python/ql/lib/semmle/python/dataflow/new/internal/DataFlowImplCommon.qll
@@ -786,13 +786,18 @@ private module Cached {
}
/**
- * Holds if the call context `call` either improves virtual dispatch in
- * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ * Holds if the call context `call` improves virtual dispatch in `callable`.
*/
cached
- predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ predicate recordDataFlowCallSiteDispatch(DataFlowCall call, DataFlowCallable callable) {
reducedViableImplInCallContext(_, callable, call)
- or
+ }
+
+ /**
+ * Holds if the call context `call` allows us to prune unreachable nodes in `callable`.
+ */
+ cached
+ predicate recordDataFlowCallSiteUnreachable(DataFlowCall call, DataFlowCallable callable) {
exists(Node n | getNodeEnclosingCallable(n) = callable | isUnreachableInCallCached(n, call))
}
@@ -846,6 +851,15 @@ private module Cached {
TAccessPathFrontSome(AccessPathFront apf)
}
+/**
+ * Holds if the call context `call` either improves virtual dispatch in
+ * `callable` or if it allows us to prune unreachable nodes in `callable`.
+ */
+predicate recordDataFlowCallSite(DataFlowCall call, DataFlowCallable callable) {
+ recordDataFlowCallSiteDispatch(call, callable) or
+ recordDataFlowCallSiteUnreachable(call, callable)
+}
+
/**
* A `Node` at which a cast can occur such that the type should be checked.
*/
From 9f4b7255aa8a9d9ec67a5b58debe7a1a800ed0c3 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 2 Sep 2021 10:21:07 +0300
Subject: [PATCH 083/459] Add files via upload
---
.../Security/CWE/CWE-675/DoubleRelease.c | 13 ++
.../Security/CWE/CWE-675/DoubleRelease.qhelp | 26 +++
.../Security/CWE/CWE-675/DoubleRelease.ql | 211 ++++++++++++++++++
3 files changed, 250 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.c
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.c b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.c
new file mode 100644
index 00000000000..22de8d4dde4
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.c
@@ -0,0 +1,13 @@
+...
+ fs = socket(AF_UNIX, SOCK_STREAM, 0)
+...
+ close(fs);
+ fs = -1; // GOOD
+...
+
+...
+ fs = socket(AF_UNIX, SOCK_STREAM, 0)
+...
+ close(fs);
+ if(fs) close(fs); // BAD
+...
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
new file mode 100644
index 00000000000..8ec155e100f
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
@@ -0,0 +1,26 @@
+
+
+
+
Double release of the descriptor can lead to a crash of the program. Requires the attention of developers.
+
+
+
+
We recommend that you exclude situations of possible double release.
+
+
+
+
The following example demonstrates an erroneous and corrected use of descriptor deallocation.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.ql b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.ql
new file mode 100644
index 00000000000..684ed9617b8
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.ql
@@ -0,0 +1,211 @@
+/**
+ * @name Errors When Double Release
+ * @description Double release of the descriptor can lead to a crash of the program.
+ * @kind problem
+ * @id cpp/double-release
+ * @problem.severity warning
+ * @precision medium
+ * @tags security
+ * external/cwe/cwe-675
+ * external/cwe/cwe-666
+ */
+
+import cpp
+import semmle.code.cpp.commons.File
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+import semmle.code.cpp.valuenumbering.HashCons
+
+/**
+ * A function call that potentially does not return (such as `exit`).
+ */
+class CallMayNotReturn extends FunctionCall {
+ CallMayNotReturn() {
+ // call that is known to not return
+ not exists(this.(ControlFlowNode).getASuccessor())
+ or
+ // call to another function that may not return
+ exists(CallMayNotReturn exit | getTarget() = exit.getEnclosingFunction())
+ or
+ exists(ThrowExpr tex | tex = this.(ControlFlowNode).getASuccessor())
+ }
+}
+
+/** Holds if there are no assignment expressions to the function argument. */
+pragma[inline]
+predicate checkChangeVariable(FunctionCall fc0, FunctionCall fc1, FunctionCall fc2) {
+ not exists(Expr exptmp |
+ (
+ exptmp = fc0.getArgument(0).(VariableAccess).getTarget().getAnAssignedValue() or
+ exptmp.(AddressOfExpr).getOperand() =
+ fc0.getArgument(0).(VariableAccess).getTarget().getAnAccess()
+ ) and
+ exptmp = fc1.getASuccessor*() and
+ exptmp = fc2.getAPredecessor*()
+ ) and
+ (
+ (
+ not fc0.getArgument(0) instanceof PointerFieldAccess and
+ not fc0.getArgument(0) instanceof ValueFieldAccess
+ or
+ fc0.getArgument(0).(VariableAccess).getQualifier() instanceof ThisExpr
+ )
+ or
+ not exists(Expr exptmp |
+ (
+ exptmp =
+ fc0.getArgument(0)
+ .(VariableAccess)
+ .getQualifier()
+ .(VariableAccess)
+ .getTarget()
+ .getAnAssignedValue() or
+ exptmp.(AddressOfExpr).getOperand() =
+ fc0.getArgument(0)
+ .(VariableAccess)
+ .getQualifier()
+ .(VariableAccess)
+ .getTarget()
+ .getAnAccess()
+ ) and
+ exptmp = fc1.getASuccessor*() and
+ exptmp = fc2.getAPredecessor*()
+ )
+ )
+}
+
+/** Holds if the underlying expression is a call to the close function. Provided that the function parameter does not change after the call. */
+predicate closeReturn(FunctionCall fc) {
+ fcloseCall(fc, _) and
+ not exists(Expr exptmp |
+ (
+ exptmp = fc.getArgument(0).(VariableAccess).getTarget().getAnAssignedValue() or
+ exptmp.(AddressOfExpr).getOperand() =
+ fc.getArgument(0).(VariableAccess).getTarget().getAnAccess()
+ ) and
+ exptmp = fc.getASuccessor*()
+ ) and
+ (
+ (
+ not fc.getArgument(0) instanceof PointerFieldAccess and
+ not fc.getArgument(0) instanceof ValueFieldAccess
+ or
+ fc.getArgument(0).(VariableAccess).getQualifier() instanceof ThisExpr
+ )
+ or
+ not exists(Expr exptmp |
+ (
+ exptmp =
+ fc.getArgument(0)
+ .(VariableAccess)
+ .getQualifier()
+ .(VariableAccess)
+ .getTarget()
+ .getAnAssignedValue() or
+ exptmp.(AddressOfExpr).getOperand() =
+ fc.getArgument(0)
+ .(VariableAccess)
+ .getQualifier()
+ .(VariableAccess)
+ .getTarget()
+ .getAnAccess()
+ ) and
+ exptmp = fc.getASuccessor*()
+ )
+ )
+}
+
+/** Holds if the underlying expression is a call to the close function. Provided that the function parameter does not change before the call. */
+predicate closeWithoutChangeBefore(FunctionCall fc) {
+ fcloseCall(fc, _) and
+ not exists(Expr exptmp |
+ (
+ exptmp = fc.getArgument(0).(VariableAccess).getTarget().getAnAssignedValue() or
+ exptmp.(AddressOfExpr).getOperand() =
+ fc.getArgument(0).(VariableAccess).getTarget().getAnAccess()
+ ) and
+ exptmp = fc.getAPredecessor*()
+ ) and
+ (
+ (
+ not fc.getArgument(0) instanceof PointerFieldAccess and
+ not fc.getArgument(0) instanceof ValueFieldAccess
+ or
+ fc.getArgument(0).(VariableAccess).getQualifier() instanceof ThisExpr
+ )
+ or
+ not exists(Expr exptmp |
+ (
+ exptmp =
+ fc.getArgument(0)
+ .(VariableAccess)
+ .getQualifier()
+ .(VariableAccess)
+ .getTarget()
+ .getAnAssignedValue() or
+ exptmp.(AddressOfExpr).getOperand() =
+ fc.getArgument(0)
+ .(VariableAccess)
+ .getQualifier()
+ .(VariableAccess)
+ .getTarget()
+ .getAnAccess()
+ ) and
+ exptmp = fc.getAPredecessor*()
+ )
+ )
+}
+
+/** Holds, if a sequential call of the specified functions is possible, via a higher-level function call. */
+predicate callInOtherFunctions(FunctionCall fc, FunctionCall fc1) {
+ exists(FunctionCall fec1, FunctionCall fec2 |
+ // fec1.getTarget() != fec2.getTarget() and
+ fc.getEnclosingFunction() != fc1.getEnclosingFunction() and
+ fec1 = fc.getEnclosingFunction().getACallToThisFunction() and
+ fec2 = fc1.getEnclosingFunction().getACallToThisFunction() and
+ fec1.getASuccessor*() = fec2 and
+ checkChangeVariable(fc, fec1, fec2)
+ )
+}
+
+/** Holds if successive calls to close functions are possible. */
+predicate interDoubleCloseFunctions(FunctionCall fc, FunctionCall fc1) {
+ fcloseCall(fc, _) and
+ fcloseCall(fc1, _) and
+ fc != fc1 and
+ fc.getASuccessor*() = fc1 and
+ checkChangeVariable(fc, fc, fc1)
+}
+
+/** Holds if the first arguments of the two functions are similar. */
+predicate similarArguments(FunctionCall fc, FunctionCall fc1) {
+ globalValueNumber(fc.getArgument(0)) = globalValueNumber(fc1.getArgument(0))
+ or
+ fc.getArgument(0).(VariableAccess).getTarget() = fc1.getArgument(0).(VariableAccess).getTarget() and
+ (
+ not fc.getArgument(0) instanceof PointerFieldAccess and
+ not fc.getArgument(0) instanceof ValueFieldAccess
+ or
+ fc.getArgument(0).(VariableAccess).getQualifier() instanceof ThisExpr
+ )
+ or
+ fc.getArgument(0).(VariableAccess).getTarget() = fc1.getArgument(0).(VariableAccess).getTarget() and
+ (
+ fc.getArgument(0) instanceof PointerFieldAccess or
+ fc.getArgument(0) instanceof ValueFieldAccess
+ ) and
+ hashCons(fc.getArgument(0)) = hashCons(fc1.getArgument(0))
+}
+
+from FunctionCall fc, FunctionCall fc1
+where
+ not exists(CallMayNotReturn fctmp | fctmp = fc.getASuccessor*()) and
+ not exists(IfStmt ifs | ifs.getCondition().getAChild*() = fc) and
+ (
+ closeReturn(fc) and
+ closeWithoutChangeBefore(fc1) and
+ callInOtherFunctions(fc, fc1)
+ or
+ interDoubleCloseFunctions(fc, fc1)
+ ) and
+ similarArguments(fc, fc1)
+select fc, "Second call to the $@ function is possible.", fc1, fc1.getTarget().getName()
From 1e88470ad8e852caffcc2afc6edde0bd10e278f6 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Thu, 2 Sep 2021 10:22:49 +0300
Subject: [PATCH 084/459] Add files via upload
---
.../semmle/tests/DoubleRelease.expected | 3 +
.../CWE-675/semmle/tests/DoubleRelease.qlref | 1 +
.../CWE/CWE-675/semmle/tests/test.cpp | 83 +++++++++++++++++++
3 files changed, 87 insertions(+)
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/test.cpp
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.expected
new file mode 100644
index 00000000000..8bed519ab4a
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.expected
@@ -0,0 +1,3 @@
+| test.cpp:20:3:20:8 | call to fclose | Second call to the $@ function is possible. | test.cpp:21:3:21:8 | call to fclose | fclose |
+| test.cpp:31:3:31:8 | call to fclose | Second call to the $@ function is possible. | test.cpp:32:3:32:8 | call to fclose | fclose |
+| test.cpp:38:3:38:8 | call to fclose | Second call to the $@ function is possible. | test.cpp:44:3:44:8 | call to fclose | fclose |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.qlref
new file mode 100644
index 00000000000..3edd226abaa
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/DoubleRelease.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-675/DoubleRelease.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/test.cpp
new file mode 100644
index 00000000000..986a95b1ce9
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-675/semmle/tests/test.cpp
@@ -0,0 +1,83 @@
+#define NULL (0)
+typedef int FILE;
+FILE *fopen(const char *filename, const char *mode);
+int fclose(FILE *stream);
+extern FILE * fe;
+void test1()
+{
+ FILE *f;
+
+ f = fopen("myFile.txt", "wt");
+ fclose(f); // GOOD
+ f = NULL;
+}
+
+void test2()
+{
+ FILE *f;
+
+ f = fopen("myFile.txt", "wt");
+ fclose(f); // BAD
+ fclose(f);
+}
+
+void test3()
+{
+ FILE *f;
+ FILE *g;
+
+ f = fopen("myFile.txt", "wt");
+ g = f;
+ fclose(f); // BAD
+ fclose(g);
+}
+
+int fGtest4_1()
+{
+ fe = fopen("myFile.txt", "wt");
+ fclose(fe); // BAD
+ return -1;
+}
+
+int fGtest4_2()
+{
+ fclose(fe);
+ return -1;
+}
+
+void Gtest4()
+{
+ fGtest4_1();
+ fGtest4_2();
+}
+
+int fGtest5_1()
+{
+ fe = fopen("myFile.txt", "wt");
+ fclose(fe); // GOOD
+ fe = NULL;
+ return -1;
+}
+
+int fGtest5_2()
+{
+ fclose(fe);
+ return -1;
+}
+
+void Gtest5()
+{
+ fGtest5_1();
+ fGtest5_2();
+}
+
+int main(int argc, char *argv[])
+{
+ test1();
+ test2();
+ test3();
+
+ Gtest4();
+ Gtest5();
+ return 0;
+}
From b39bb24fcffc82785ce92e824b0c6d1bdf95fa9b Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 31 Aug 2021 09:20:27 +0200
Subject: [PATCH 085/459] Python: Add more SQLAlchemy tests
---
.../frameworks/sqlalchemy/new_tests.py | 402 ++++++++++++++++++
1 file changed, 402 insertions(+)
create mode 100644 python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
new file mode 100644
index 00000000000..aff8c0d2fca
--- /dev/null
+++ b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -0,0 +1,402 @@
+import sqlalchemy
+import sqlalchemy.orm
+
+# SQLAlchemy is slowly migrating to a 2.0 version, and as part of 1.4 release have a 2.0
+# style (forwards compatible) API that _can_ be adopted. So these tests are marked with
+# either v1.4 or v2.0, such that we cover both.
+
+raw_sql = "select 'FOO'"
+text_sql = sqlalchemy.text(raw_sql)
+
+Base = sqlalchemy.orm.declarative_base()
+
+# ==============================================================================
+# v1.4
+# ==============================================================================
+
+print("v1.4")
+
+# Engine see https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine
+
+engine = sqlalchemy.create_engine("sqlite+pysqlite:///:memory:", echo=True)
+
+result = engine.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+result = engine.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+result = engine.execute(text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+scalar_result = engine.scalar(raw_sql) # $ getSql=raw_sql
+assert scalar_result == "FOO"
+scalar_result = engine.scalar(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+
+# engine with custom execution options
+# see https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine.execution_options
+engine_with_custom_exe_opts = engine.execution_options(foo=42)
+result = engine_with_custom_exe_opts.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+even_more_opts = engine_with_custom_exe_opts.execution_options(bar=43)
+result = even_more_opts.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+# Connection see https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection
+conn = engine.connect()
+conn: sqlalchemy.engine.base.Connection
+
+result = conn.execute(raw_sql) # $ getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+result = conn.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+result = conn.execute(text_sql) # $ getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+result = conn.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+# scalar
+scalar_result = conn.scalar(raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+scalar_result = conn.scalar(object_=raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+
+scalar_result = conn.scalar(text_sql) # $ MISSING: getSql=text_sql
+assert scalar_result == "FOO"
+scalar_result = conn.scalar(object_=text_sql) # $ MISSING: getSql=text_sql
+assert scalar_result == "FOO"
+
+
+# exec_driver_sql
+result = conn.exec_driver_sql(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+# construction by object
+conn = sqlalchemy.engine.base.Connection(engine)
+result = conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+# branched connection
+branched_conn = conn.connect()
+result = branched_conn.execute(text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+# raw connection
+raw_conn = conn.connection
+result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+cursor = raw_conn.cursor()
+cursor.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert cursor.fetchall() == [("FOO",)]
+cursor.close()
+
+raw_conn = engine.raw_connection()
+result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+# connection with custom execution options
+conn_with_custom_exe_opts = conn.execution_options(bar=1337)
+result = conn_with_custom_exe_opts.execute(text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+# Session -- is what you use to work with the ORM layer
+# see https://docs.sqlalchemy.org/en/14/orm/session_basics.html
+# and https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session
+
+session = sqlalchemy.orm.Session(engine)
+
+result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+result = session.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+result = session.execute(text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+result = session.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+# scalar
+scalar_result = session.scalar(raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+scalar_result = session.scalar(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+
+scalar_result = session.scalar(text_sql) # $ MISSING: getSql=text_sql
+assert scalar_result == "FOO"
+scalar_result = session.scalar(statement=text_sql) # $ MISSING: getSql=text_sql
+assert scalar_result == "FOO"
+
+# other ways to construct a session
+with sqlalchemy.orm.Session(engine) as session:
+ result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ assert result.fetchall() == [("FOO",)]
+
+Session = sqlalchemy.orm.sessionmaker(engine)
+session = Session()
+
+result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+with Session() as session:
+ result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ assert result.fetchall() == [("FOO",)]
+
+with Session.begin() as session:
+ result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ assert result.fetchall() == [("FOO",)]
+
+# Querying (1.4)
+# see https://docs.sqlalchemy.org/en/14/orm/session_basics.html#querying-1-x-style
+
+# to do so we first need a model
+
+class For14(Base):
+ __tablename__ = "for14"
+
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
+ description = sqlalchemy.Column(sqlalchemy.String)
+
+Base.metadata.create_all(engine)
+
+# add a test-entry
+test_entry = For14(id=14, description="test")
+session = sqlalchemy.orm.Session(engine)
+session.add(test_entry)
+session.commit()
+assert session.query(For14).all()[0].id == 14
+
+# and now we can do the actual querying
+
+text_foo = sqlalchemy.text("'FOO'")
+
+# filter_by is only vulnerable to injection if sqlalchemy.text is used, which is evident
+# from the logs produced if this file is run
+# that is, first filter_by results in the SQL
+#
+# SELECT for14.id AS for14_id, for14.description AS for14_description
+# FROM for14
+# WHERE for14.description = ?
+#
+# which is then called with the argument `'FOO'`
+#
+# and the second filter_by results in the SQL
+#
+# SELECT for14.id AS for14_id, for14.description AS for14_description
+# FROM for14
+# WHERE for14.description = 'FOO'
+#
+# which is then called without any arguments
+assert session.query(For14).filter_by(description="'FOO'").all() == []
+query = session.query(For14).filter_by(description=text_foo) # $ MISSING: getSql=text_foo
+assert query.all() == []
+
+# `filter` provides more general filtering
+# see https://docs.sqlalchemy.org/en/14/orm/tutorial.html#common-filter-operators
+# and https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.filter
+assert session.query(For14).filter(For14.description == "'FOO'").all() == []
+query = session.query(For14).filter(For14.description == text_foo) # $ MISSING: getSql=text_foo
+assert query.all() == []
+
+assert session.query(For14).filter(For14.description.like("'FOO'")).all() == []
+query = session.query(For14).filter(For14.description.like(text_foo)) # $ MISSING: getSql=text_foo
+assert query.all() == []
+
+# `where` is alias for `filter`
+assert session.query(For14).where(For14.description == "'FOO'").all() == []
+query = session.query(For14).where(For14.description == text_foo) # $ MISSING: getSql=text_foo
+assert query.all() == []
+
+
+# not possible to do SQL injection on `.get`
+try:
+ session.query(For14).get(text_foo)
+except sqlalchemy.exc.InterfaceError:
+ pass
+
+# group_by
+assert session.query(For14).group_by(For14.description).all() != []
+query = session.query(For14).group_by(text_foo) # $ MISSING: getSql=text_foo
+assert query.all() != []
+
+# having (only used in connection with group_by)
+assert session.query(For14).group_by(For14.description).having(
+ sqlalchemy.func.count(For14.id) > 2
+).all() == []
+query = session.query(For14).group_by(For14.description).having(text_foo) # $ MISSING: getSql=text_foo
+assert query.all() == []
+
+# order_by
+assert session.query(For14).order_by(For14.description).all() != []
+query = session.query(For14).order_by(text_foo) # $ MISSING: getSql=text_foo
+assert query.all() != []
+
+# TODO: likewise, it should be possible to target the criterion for:
+# - `join` https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.join
+# - `outerjoin` https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.outerjoin
+
+# specifying session later on
+# see https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.with_session
+query = sqlalchemy.orm.Query(For14).filter(For14.description == text_foo) # $ MISSING: getSql=text_foo
+assert query.with_session(session).all() == []
+
+# ==============================================================================
+# v2.0
+# ==============================================================================
+import sqlalchemy.future
+
+print("-"*80)
+print("v2.0 style")
+
+# For Engine, see https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Engine
+engine = sqlalchemy.create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
+future_engine = sqlalchemy.future.create_engine("sqlite+pysqlite:///:memory:", echo=True)
+
+# in 2.0 you are not allowed to execute things directly on the engine
+try:
+ engine.execute(raw_sql)
+ raise Exception("above not allowed in 2.0")
+except NotImplementedError:
+ pass
+try:
+ engine.execute(text_sql)
+ raise Exception("above not allowed in 2.0")
+except NotImplementedError:
+ pass
+
+
+# `connect` returns a new Connection object.
+# see https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection
+print("v2.0 engine.connect")
+with engine.connect() as conn:
+ conn: sqlalchemy.future.Connection
+
+ # in 2.0 you are not allowed to use raw strings like this:
+ try:
+ conn.execute(raw_sql) # $ SPURIOUS: getSql=raw_sql
+ raise Exception("above not allowed in 2.0")
+ except sqlalchemy.exc.ObjectNotExecutableError:
+ pass
+
+ result = conn.execute(text_sql) # $ getSql=text_sql
+ assert result.fetchall() == [("FOO",)]
+ result = conn.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+ assert result.fetchall() == [("FOO",)]
+
+ result = conn.exec_driver_sql(raw_sql) # $ MISSING: getSql=raw_sql
+ assert result.fetchall() == [("FOO",)]
+
+ raw_conn = conn.connection
+ result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ assert result.fetchall() == [("FOO",)]
+
+ # branching not allowed in 2.0
+ try:
+ branched_conn = conn.connect()
+ raise Exception("above not allowed in 2.0")
+ except NotImplementedError:
+ pass
+
+ # connection with custom execution options
+ conn_with_custom_exe_opts = conn.execution_options(bar=1337)
+ result = conn_with_custom_exe_opts.execute(text_sql) # $ MISSING: getSql=text_sql
+ assert result.fetchall() == [("FOO",)]
+
+ # `scalar` is shorthand helper
+ try:
+ conn.scalar(raw_sql)
+ except sqlalchemy.exc.ObjectNotExecutableError:
+ pass
+ scalar_result = conn.scalar(text_sql) # $ MISSING: getSql=text_sql
+ assert scalar_result == "FOO"
+ scalar_result = conn.scalar(statement=text_sql) # $ MISSING: getSql=text_sql
+ assert scalar_result == "FOO"
+
+ # This is a contrived example
+ select = sqlalchemy.select(sqlalchemy.text("'BAR'"))
+ result = conn.execute(select) # $ getSql=select
+ assert result.fetchall() == [("BAR",)]
+
+ # This is a contrived example
+ select = sqlalchemy.select(sqlalchemy.literal_column("'BAZ'"))
+ result = conn.execute(select) # $ getSql=select
+ assert result.fetchall() == [("BAZ",)]
+
+with future_engine.connect() as conn:
+ result = conn.execute(text_sql) # $ MISSING: getSql=text_sql
+ assert result.fetchall() == [("FOO",)]
+
+# `begin` returns a new Connection object with a transaction begun.
+print("v2.0 engine.begin")
+with engine.begin() as conn:
+ result = conn.execute(text_sql) # $ getSql=text_sql
+ assert result.fetchall() == [("FOO",)]
+
+# construction by object
+conn = sqlalchemy.future.Connection(engine)
+result = conn.execute(text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+# raw_connection
+
+raw_conn = engine.raw_connection()
+result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+cursor = raw_conn.cursor()
+cursor.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert cursor.fetchall() == [("FOO",)]
+cursor.close()
+
+# Session (2.0)
+session = sqlalchemy.orm.Session(engine, future=True)
+
+result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+result = session.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("FOO",)]
+
+result = session.execute(text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+result = session.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+assert result.fetchall() == [("FOO",)]
+
+# scalar
+scalar_result = session.scalar(raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+scalar_result = session.scalar(statement=raw_sql) # $ MISSING: getSql=raw_sql
+assert scalar_result == "FOO"
+
+scalar_result = session.scalar(text_sql) # $ MISSING: getSql=text_sql
+assert scalar_result == "FOO"
+scalar_result = session.scalar(statement=text_sql) # $ MISSING: getSql=text_sql
+assert scalar_result == "FOO"
+
+# Querying (2.0)
+
+class For20(Base):
+ __tablename__ = "for20"
+
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
+ description = sqlalchemy.Column(sqlalchemy.String)
+
+For20.metadata.create_all(engine)
+
+# add a test-entry
+test_entry = For20(id=20, description="test")
+session = sqlalchemy.orm.Session(engine, future=True)
+session.add(test_entry)
+session.commit()
+assert session.query(For20).all()[0].id == 20
+
+# and now we can do the actual querying
+# see https://docs.sqlalchemy.org/en/14/orm/session_basics.html#querying-2-0-style
+
+statement = sqlalchemy.select(For20)
+result = session.execute(statement)
+assert result.scalars().all()[0].id == 20
+
+statement = sqlalchemy.select(For20).where(For20.description == text_foo) # $ MISSING: getSql=text_foo
+result = session.execute(statement)
+assert result.scalars().all() == []
From fe143c7dfad65a035e657c5706a864c2a3b6e363 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 14:58:55 +0200
Subject: [PATCH 086/459] Python: Rewrite most of SQLAlchemy modeling
---
.../semmle/python/frameworks/SqlAlchemy.qll | 332 +++++++++++++-----
.../frameworks/sqlalchemy/new_tests.py | 86 ++---
2 files changed, 290 insertions(+), 128 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
index 00d550d9844..c7f2d53040e 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
@@ -1,6 +1,8 @@
/**
- * Provides classes modeling security-relevant aspects of the 'SqlAlchemy' package.
- * See https://pypi.org/project/SQLAlchemy/.
+ * Provides classes modeling security-relevant aspects of the `SQLAlchemy` PyPI package.
+ * See
+ * - https://pypi.org/project/SQLAlchemy/
+ * - https://docs.sqlalchemy.org/en/14/index.html
*/
private import python
@@ -10,93 +12,209 @@ private import semmle.python.ApiGraphs
private import semmle.python.Concepts
private import experimental.semmle.python.Concepts
+/**
+ * Provides models for the `SQLAlchemy` PyPI package.
+ * See
+ * - https://pypi.org/project/SQLAlchemy/
+ * - https://docs.sqlalchemy.org/en/14/index.html
+ */
private module SqlAlchemy {
/**
- * Returns an instantization of a SqlAlchemy Session object.
- * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session and
- * https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker
- */
- private API::Node getSqlAlchemySessionInstance() {
- result = API::moduleImport("sqlalchemy.orm").getMember("Session").getReturn() or
- result = API::moduleImport("sqlalchemy.orm").getMember("sessionmaker").getReturn().getReturn()
- }
-
- /**
- * Returns an instantization of a SqlAlchemy Engine object.
- * See https://docs.sqlalchemy.org/en/14/core/engines.html#sqlalchemy.create_engine
- */
- private API::Node getSqlAlchemyEngineInstance() {
- result = API::moduleImport("sqlalchemy").getMember("create_engine").getReturn()
- }
-
- /**
- * Returns an instantization of a SqlAlchemy Query object.
- * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
- */
- private API::Node getSqlAlchemyQueryInstance() {
- result = getSqlAlchemySessionInstance().getMember("query").getReturn()
- }
-
- /**
- * A call to `execute` meant to execute an SQL expression
- * See the following links:
- * - https://docs.sqlalchemy.org/en/14/core/connections.html?highlight=execute#sqlalchemy.engine.Connection.execute
- * - https://docs.sqlalchemy.org/en/14/core/connections.html?highlight=execute#sqlalchemy.engine.Engine.execute
- * - https://docs.sqlalchemy.org/en/14/orm/session_api.html?highlight=execute#sqlalchemy.orm.Session.execute
- */
- private class SqlAlchemyExecuteCall extends DataFlow::CallCfgNode, SqlExecution::Range {
- SqlAlchemyExecuteCall() {
- // new way
- this = getSqlAlchemySessionInstance().getMember("execute").getACall() or
- this =
- getSqlAlchemyEngineInstance()
- .getMember(["connect", "begin"])
- .getReturn()
- .getMember("execute")
- .getACall()
- }
-
- override DataFlow::Node getSql() { result = this.getArg(0) }
- }
-
- /**
- * A call to `scalar` meant to execute an SQL expression
- * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.scalar and
- * https://docs.sqlalchemy.org/en/14/core/connections.html?highlight=scalar#sqlalchemy.engine.Engine.scalar
- */
- private class SqlAlchemyScalarCall extends DataFlow::CallCfgNode, SqlExecution::Range {
- SqlAlchemyScalarCall() {
- this =
- [getSqlAlchemySessionInstance(), getSqlAlchemyEngineInstance()]
- .getMember("scalar")
- .getACall()
- }
-
- override DataFlow::Node getSql() { result = this.getArg(0) }
- }
-
- /**
- * A call on a Query object
- * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
- */
- private class SqlAlchemyQueryCall extends DataFlow::CallCfgNode, SqlExecution::Range {
- SqlAlchemyQueryCall() {
- this =
- getSqlAlchemyQueryInstance()
- .getMember(any(SqlAlchemyVulnerableMethodNames methodName))
- .getACall()
- }
-
- override DataFlow::Node getSql() { result = this.getArg(0) }
- }
-
- /**
- * This class represents a list of methods vulnerable to sql injection.
+ * Provides models for the `sqlalchemy.engine.Engine` and `sqlalchemy.future.Engine` classes.
*
- * See https://github.com/jty-team/codeql/pull/2#issue-611592361
+ * These are so similar that we model both in the same way.
+ *
+ * See
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Engine
*/
- private class SqlAlchemyVulnerableMethodNames extends string {
- SqlAlchemyVulnerableMethodNames() { this in ["filter", "filter_by", "group_by", "order_by"] }
+ module Engine {
+ /** Gets a reference to a SQLAlchemy Engine class. */
+ private API::Node classRef() {
+ result = API::moduleImport("sqlalchemy").getMember("engine").getMember("Engine")
+ or
+ result = API::moduleImport("sqlalchemy").getMember("future").getMember("Engine")
+ }
+
+ /**
+ * A source of instances of a SQLAlchemy Engine, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `Engine::instance()` to get references to instances of a SQLAlchemy Engine.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ private class EngineConstruction extends InstanceSource, DataFlow::CallCfgNode {
+ EngineConstruction() {
+ this = classRef().getACall()
+ or
+ this = API::moduleImport("sqlalchemy").getMember("create_engine").getACall()
+ or
+ this =
+ API::moduleImport("sqlalchemy").getMember("future").getMember("create_engine").getACall()
+ or
+ this.(DataFlow::MethodCallNode).calls(instance(), "execution_options")
+ }
+ }
+
+ /** Gets a reference to an instance of a SQLAlchemy Engine. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of a SQLAlchemy Engine. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+ }
+
+ /**
+ * Provides models for the `sqlalchemy.engine.base.Connection` and `sqlalchemy.future.Connection` classes.
+ *
+ * These are so similar that we model both in the same way.
+ *
+ * See
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection
+ */
+ module Connection {
+ /** Gets a reference to a SQLAlchemy Connection class. */
+ private API::Node classRef() {
+ result =
+ API::moduleImport("sqlalchemy")
+ .getMember("engine")
+ .getMember("base")
+ .getMember("Connection")
+ or
+ result = API::moduleImport("sqlalchemy").getMember("future").getMember("Connection")
+ }
+
+ /**
+ * A source of instances of a SQLAlchemy Connection, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `Connection::instance()` to get references to instances of a SQLAlchemy Connection.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ private class ConnectionConstruction extends InstanceSource, DataFlow::CallCfgNode {
+ ConnectionConstruction() {
+ this = classRef().getACall()
+ or
+ this.(DataFlow::MethodCallNode).calls(Engine::instance(), ["begin", "connect"])
+ or
+ this.(DataFlow::MethodCallNode).calls(instance(), "connect")
+ }
+ }
+
+ /** Gets a reference to an instance of a SQLAlchemy Connection. */
+ private DataFlow::TypeTrackingNode instance(DataFlow::TypeTracker t) {
+ t.start() and
+ result instanceof InstanceSource
+ or
+ exists(DataFlow::TypeTracker t2 | result = instance(t2).track(t2, t))
+ }
+
+ /** Gets a reference to an instance of a SQLAlchemy Connection. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+ }
+
+ /**
+ * Provides models for the `sqlalchemy.orm.Session` class
+ *
+ * See
+ * - https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session
+ * - https://docs.sqlalchemy.org/en/14/orm/session_basics.html
+ */
+ module Session {
+ /** Gets a reference to the `sqlalchemy.orm.Session` class. */
+ private API::Node classRef() {
+ result = API::moduleImport("sqlalchemy").getMember("orm").getMember("Session")
+ }
+
+ /**
+ * A source of instances of `sqlalchemy.orm.Session`, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `Session::instance()` to get references to instances of `sqlalchemy.orm.Session`.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ private class SessionConstruction extends InstanceSource, DataFlow::CallCfgNode {
+ SessionConstruction() {
+ this = classRef().getACall()
+ or
+ this =
+ API::moduleImport("sqlalchemy")
+ .getMember("orm")
+ .getMember("sessionmaker")
+ .getReturn()
+ .getACall()
+ }
+ }
+
+ /** Gets a reference to an instance of `sqlalchemy.orm.Session`. */
+ private DataFlow::TypeTrackingNode 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 `sqlalchemy.orm.Session`. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+ }
+
+ /**
+ * A call to `execute` on a SQLAlchemy Engine, Connection, or Session.
+ * See
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine.execute
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection.execute
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection.execute
+ * - https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.execute
+ */
+ private class SqlAlchemyExecuteCall extends DataFlow::MethodCallNode, SqlExecution::Range {
+ SqlAlchemyExecuteCall() {
+ this.calls(Engine::instance(), "execute")
+ or
+ this.calls(Connection::instance(), "execute")
+ or
+ this.calls(Session::instance(), "execute")
+ }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("statement")] }
+ }
+
+ /**
+ * A call to `scalar` on a SQLAlchemy Engine, Connection, or Session.
+ * See
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine.scalar
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection.scalar
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection.scalar
+ * - https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session.scalar
+ */
+ private class SqlAlchemyScalarCall extends DataFlow::MethodCallNode, SqlExecution::Range {
+ SqlAlchemyScalarCall() {
+ this.calls(Engine::instance(), "scalar")
+ or
+ this.calls(Connection::instance(), "scalar")
+ or
+ this.calls(Session::instance(), "scalar")
+ }
+
+ override DataFlow::Node getSql() {
+ result in [this.getArg(0), this.getArgByName("statement"), this.getArgByName("object_")]
+ }
}
/**
@@ -146,3 +264,47 @@ private module SqlAlchemy {
override DataFlow::Node getAnInput() { result = this.getArg(0) }
}
}
+
+private module OldModeling {
+ /**
+ * Returns an instantization of a SqlAlchemy Session object.
+ * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session and
+ * https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker
+ */
+ private API::Node getSqlAlchemySessionInstance() {
+ result = API::moduleImport("sqlalchemy.orm").getMember("Session").getReturn() or
+ result = API::moduleImport("sqlalchemy.orm").getMember("sessionmaker").getReturn().getReturn()
+ }
+
+ /**
+ * Returns an instantization of a SqlAlchemy Query object.
+ * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
+ */
+ private API::Node getSqlAlchemyQueryInstance() {
+ result = getSqlAlchemySessionInstance().getMember("query").getReturn()
+ }
+
+ /**
+ * A call on a Query object
+ * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
+ */
+ private class SqlAlchemyQueryCall extends DataFlow::CallCfgNode, SqlExecution::Range {
+ SqlAlchemyQueryCall() {
+ this =
+ getSqlAlchemyQueryInstance()
+ .getMember(any(SqlAlchemyVulnerableMethodNames methodName))
+ .getACall()
+ }
+
+ override DataFlow::Node getSql() { result = this.getArg(0) }
+ }
+
+ /**
+ * This class represents a list of methods vulnerable to sql injection.
+ *
+ * See https://github.com/jty-team/codeql/pull/2#issue-611592361
+ */
+ private class SqlAlchemyVulnerableMethodNames extends string {
+ SqlAlchemyVulnerableMethodNames() { this in ["filter", "filter_by", "group_by", "order_by"] }
+ }
+}
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
index aff8c0d2fca..4ff94966712 100644
--- a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
+++ b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -20,26 +20,26 @@ print("v1.4")
engine = sqlalchemy.create_engine("sqlite+pysqlite:///:memory:", echo=True)
-result = engine.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = engine.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = engine.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+result = engine.execute(statement=raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = engine.execute(text_sql) # $ MISSING: getSql=text_sql
+result = engine.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
scalar_result = engine.scalar(raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = engine.scalar(statement=raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = engine.scalar(statement=raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
# engine with custom execution options
# see https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Engine.execution_options
engine_with_custom_exe_opts = engine.execution_options(foo=42)
-result = engine_with_custom_exe_opts.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = engine_with_custom_exe_opts.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
even_more_opts = engine_with_custom_exe_opts.execution_options(bar=43)
-result = even_more_opts.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = even_more_opts.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
# Connection see https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection
@@ -48,23 +48,23 @@ conn: sqlalchemy.engine.base.Connection
result = conn.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = conn.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+result = conn.execute(statement=raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
result = conn.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
-result = conn.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+result = conn.execute(statement=text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# scalar
-scalar_result = conn.scalar(raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = conn.scalar(raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = conn.scalar(object_=raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = conn.scalar(object_=raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = conn.scalar(text_sql) # $ MISSING: getSql=text_sql
+scalar_result = conn.scalar(text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
-scalar_result = conn.scalar(object_=text_sql) # $ MISSING: getSql=text_sql
+scalar_result = conn.scalar(object_=text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
@@ -74,12 +74,12 @@ assert result.fetchall() == [("FOO",)]
# construction by object
conn = sqlalchemy.engine.base.Connection(engine)
-result = conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = conn.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
# branched connection
branched_conn = conn.connect()
-result = branched_conn.execute(text_sql) # $ MISSING: getSql=text_sql
+result = branched_conn.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# raw connection
@@ -107,40 +107,40 @@ assert result.fetchall() == [("FOO",)]
session = sqlalchemy.orm.Session(engine)
-result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = session.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = session.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+result = session.execute(statement=raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = session.execute(text_sql) # $ MISSING: getSql=text_sql
+result = session.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
-result = session.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+result = session.execute(statement=text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# scalar
-scalar_result = session.scalar(raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = session.scalar(raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = session.scalar(statement=raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = session.scalar(statement=raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = session.scalar(text_sql) # $ MISSING: getSql=text_sql
+scalar_result = session.scalar(text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
-scalar_result = session.scalar(statement=text_sql) # $ MISSING: getSql=text_sql
+scalar_result = session.scalar(statement=text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
# other ways to construct a session
with sqlalchemy.orm.Session(engine) as session:
- result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ result = session.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
Session = sqlalchemy.orm.sessionmaker(engine)
session = Session()
-result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = session.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
with Session() as session:
- result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ result = session.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
with Session.begin() as session:
@@ -255,12 +255,12 @@ future_engine = sqlalchemy.future.create_engine("sqlite+pysqlite:///:memory:", e
# in 2.0 you are not allowed to execute things directly on the engine
try:
- engine.execute(raw_sql)
+ engine.execute(raw_sql) # $ SPURIOUS: getSql=raw_sql
raise Exception("above not allowed in 2.0")
except NotImplementedError:
pass
try:
- engine.execute(text_sql)
+ engine.execute(text_sql) # $ SPURIOUS: getSql=text_sql
raise Exception("above not allowed in 2.0")
except NotImplementedError:
pass
@@ -281,7 +281,7 @@ with engine.connect() as conn:
result = conn.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
- result = conn.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+ result = conn.execute(statement=text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
result = conn.exec_driver_sql(raw_sql) # $ MISSING: getSql=raw_sql
@@ -305,12 +305,12 @@ with engine.connect() as conn:
# `scalar` is shorthand helper
try:
- conn.scalar(raw_sql)
+ conn.scalar(raw_sql) # $ SPURIOUS: getSql=raw_sql
except sqlalchemy.exc.ObjectNotExecutableError:
pass
- scalar_result = conn.scalar(text_sql) # $ MISSING: getSql=text_sql
+ scalar_result = conn.scalar(text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
- scalar_result = conn.scalar(statement=text_sql) # $ MISSING: getSql=text_sql
+ scalar_result = conn.scalar(statement=text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
# This is a contrived example
@@ -324,7 +324,7 @@ with engine.connect() as conn:
assert result.fetchall() == [("BAZ",)]
with future_engine.connect() as conn:
- result = conn.execute(text_sql) # $ MISSING: getSql=text_sql
+ result = conn.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# `begin` returns a new Connection object with a transaction begun.
@@ -335,7 +335,7 @@ with engine.begin() as conn:
# construction by object
conn = sqlalchemy.future.Connection(engine)
-result = conn.execute(text_sql) # $ MISSING: getSql=text_sql
+result = conn.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# raw_connection
@@ -352,25 +352,25 @@ cursor.close()
# Session (2.0)
session = sqlalchemy.orm.Session(engine, future=True)
-result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = session.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = session.execute(statement=raw_sql) # $ MISSING: getSql=raw_sql
+result = session.execute(statement=raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
-result = session.execute(text_sql) # $ MISSING: getSql=text_sql
+result = session.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
-result = session.execute(statement=text_sql) # $ MISSING: getSql=text_sql
+result = session.execute(statement=text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# scalar
-scalar_result = session.scalar(raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = session.scalar(raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = session.scalar(statement=raw_sql) # $ MISSING: getSql=raw_sql
+scalar_result = session.scalar(statement=raw_sql) # $ getSql=raw_sql
assert scalar_result == "FOO"
-scalar_result = session.scalar(text_sql) # $ MISSING: getSql=text_sql
+scalar_result = session.scalar(text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
-scalar_result = session.scalar(statement=text_sql) # $ MISSING: getSql=text_sql
+scalar_result = session.scalar(statement=text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
# Querying (2.0)
@@ -394,9 +394,9 @@ assert session.query(For20).all()[0].id == 20
# see https://docs.sqlalchemy.org/en/14/orm/session_basics.html#querying-2-0-style
statement = sqlalchemy.select(For20)
-result = session.execute(statement)
+result = session.execute(statement) # $ getSql=statement
assert result.scalars().all()[0].id == 20
statement = sqlalchemy.select(For20).where(For20.description == text_foo) # $ MISSING: getSql=text_foo
-result = session.execute(statement)
+result = session.execute(statement) # $ getSql=statement
assert result.scalars().all() == []
From 2acf518037014ed0b43f4ea85a7b93ecb80c5539 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 15:00:52 +0200
Subject: [PATCH 087/459] Python: Model `exec_driver_sql`
---
.../semmle/python/frameworks/SqlAlchemy.qll | 12 ++++++++++++
.../library-tests/frameworks/sqlalchemy/new_tests.py | 4 ++--
2 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
index c7f2d53040e..4a7ad47bc96 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
@@ -195,6 +195,18 @@ private module SqlAlchemy {
override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("statement")] }
}
+ /**
+ * A call to `exec_driver_sql` on a SQLAlchemy Connection.
+ * See
+ * - https://docs.sqlalchemy.org/en/14/core/connections.html#sqlalchemy.engine.Connection.exec_driver_sql
+ * - https://docs.sqlalchemy.org/en/14/core/future.html#sqlalchemy.future.Connection.exec_driver_sql
+ */
+ private class SqlAlchemyExecDriverSqlCall extends DataFlow::MethodCallNode, SqlExecution::Range {
+ SqlAlchemyExecDriverSqlCall() { this.calls(Connection::instance(), "exec_driver_sql") }
+
+ override DataFlow::Node getSql() { result in [this.getArg(0), this.getArgByName("statement")] }
+ }
+
/**
* A call to `scalar` on a SQLAlchemy Engine, Connection, or Session.
* See
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
index 4ff94966712..a310ad87ef6 100644
--- a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
+++ b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -69,7 +69,7 @@ assert scalar_result == "FOO"
# exec_driver_sql
-result = conn.exec_driver_sql(raw_sql) # $ MISSING: getSql=raw_sql
+result = conn.exec_driver_sql(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
# construction by object
@@ -284,7 +284,7 @@ with engine.connect() as conn:
result = conn.execute(statement=text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
- result = conn.exec_driver_sql(raw_sql) # $ MISSING: getSql=raw_sql
+ result = conn.exec_driver_sql(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
raw_conn = conn.connection
From 1ab04a72767515f7e3a1106a60ef461e165ca607 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 15:03:13 +0200
Subject: [PATCH 088/459] Python: Model `Connection.execution_options`
---
.../src/experimental/semmle/python/frameworks/SqlAlchemy.qll | 2 ++
.../library-tests/frameworks/sqlalchemy/new_tests.py | 4 ++--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
index 4a7ad47bc96..3389a1830dd 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
@@ -111,6 +111,8 @@ private module SqlAlchemy {
this.(DataFlow::MethodCallNode).calls(Engine::instance(), ["begin", "connect"])
or
this.(DataFlow::MethodCallNode).calls(instance(), "connect")
+ or
+ this.(DataFlow::MethodCallNode).calls(instance(), "execution_options")
}
}
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
index a310ad87ef6..cc012951839 100644
--- a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
+++ b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -98,7 +98,7 @@ assert result.fetchall() == [("FOO",)]
# connection with custom execution options
conn_with_custom_exe_opts = conn.execution_options(bar=1337)
-result = conn_with_custom_exe_opts.execute(text_sql) # $ MISSING: getSql=text_sql
+result = conn_with_custom_exe_opts.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# Session -- is what you use to work with the ORM layer
@@ -300,7 +300,7 @@ with engine.connect() as conn:
# connection with custom execution options
conn_with_custom_exe_opts = conn.execution_options(bar=1337)
- result = conn_with_custom_exe_opts.execute(text_sql) # $ MISSING: getSql=text_sql
+ result = conn_with_custom_exe_opts.execute(text_sql) # $ getSql=text_sql
assert result.fetchall() == [("FOO",)]
# `scalar` is shorthand helper
From feb2303e1f3222b5b0b68650392b1cd2b877cb5c Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 15:13:11 +0200
Subject: [PATCH 089/459] Python: Model the underlying DB-API connection
---
.../semmle/python/frameworks/SqlAlchemy.qll | 40 +++++++++++++++++++
.../frameworks/sqlalchemy/new_tests.py | 12 +++---
2 files changed, 46 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
index 3389a1830dd..78210ba3fb5 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
@@ -11,6 +11,9 @@ private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.ApiGraphs
private import semmle.python.Concepts
private import experimental.semmle.python.Concepts
+// This import is done like this to avoid importing the deprecated top-level things that
+// would pollute the namespace
+private import semmle.python.frameworks.PEP249::PEP249 as PEP249
/**
* Provides models for the `SQLAlchemy` PyPI package.
@@ -128,6 +131,43 @@ private module SqlAlchemy {
DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
}
+ /**
+ * Provides models for the underlying DB-API Connection of a SQLAlchemy Connection.
+ *
+ * See https://docs.sqlalchemy.org/en/14/core/connections.html#dbapi-connections.
+ */
+ module DBAPIConnection {
+ /**
+ * A source of instances of DB-API Connections, extend this class to model new instances.
+ *
+ * This can include instantiations of the class, return values from function
+ * calls, or a special parameter that will be set when functions are called by an external
+ * library.
+ *
+ * Use the predicate `DBAPIConnection::instance()` to get references to instances of DB-API Connections.
+ */
+ abstract class InstanceSource extends DataFlow::LocalSourceNode { }
+
+ private class DBAPIConnectionSources extends InstanceSource, PEP249::Connection::InstanceSource {
+ DBAPIConnectionSources() {
+ this.(DataFlow::MethodCallNode).calls(Engine::instance(), "raw_connection")
+ or
+ this.(DataFlow::AttrRead).accesses(Connection::instance(), "connection")
+ }
+ }
+
+ /** Gets a reference to an instance of DB-API Connections. */
+ private DataFlow::TypeTrackingNode 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 DB-API Connections. */
+ DataFlow::Node instance() { instance(DataFlow::TypeTracker::end()).flowsTo(result) }
+ }
+
/**
* Provides models for the `sqlalchemy.orm.Session` class
*
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
index cc012951839..64231db1f5f 100644
--- a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
+++ b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -84,16 +84,16 @@ assert result.fetchall() == [("FOO",)]
# raw connection
raw_conn = conn.connection
-result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
cursor = raw_conn.cursor()
-cursor.execute(raw_sql) # $ MISSING: getSql=raw_sql
+cursor.execute(raw_sql) # $ getSql=raw_sql
assert cursor.fetchall() == [("FOO",)]
cursor.close()
raw_conn = engine.raw_connection()
-result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
# connection with custom execution options
@@ -288,7 +288,7 @@ with engine.connect() as conn:
assert result.fetchall() == [("FOO",)]
raw_conn = conn.connection
- result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
# branching not allowed in 2.0
@@ -341,11 +341,11 @@ assert result.fetchall() == [("FOO",)]
# raw_connection
raw_conn = engine.raw_connection()
-result = raw_conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+result = raw_conn.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
cursor = raw_conn.cursor()
-cursor.execute(raw_sql) # $ MISSING: getSql=raw_sql
+cursor.execute(raw_sql) # $ getSql=raw_sql
assert cursor.fetchall() == [("FOO",)]
cursor.close()
From 91442e100c6179cd34a3c29e196ffba27f037f26 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 15:14:31 +0200
Subject: [PATCH 090/459] Python: Model `sessionmaker().begin()`
---
.../experimental/semmle/python/frameworks/SqlAlchemy.qll | 8 ++++++++
.../library-tests/frameworks/sqlalchemy/new_tests.py | 2 +-
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
index 78210ba3fb5..2712c756597 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
@@ -202,6 +202,14 @@ private module SqlAlchemy {
.getMember("sessionmaker")
.getReturn()
.getACall()
+ or
+ this =
+ API::moduleImport("sqlalchemy")
+ .getMember("orm")
+ .getMember("sessionmaker")
+ .getReturn()
+ .getMember("begin")
+ .getACall()
}
}
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
index 64231db1f5f..7e12fa60ee3 100644
--- a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
+++ b/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -144,7 +144,7 @@ with Session() as session:
assert result.fetchall() == [("FOO",)]
with Session.begin() as session:
- result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ result = session.execute(raw_sql) # $ getSql=raw_sql
assert result.fetchall() == [("FOO",)]
# Querying (1.4)
From ba99e218754b6668fb8d1122f94efba8813dd4e1 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 20:43:18 +0200
Subject: [PATCH 091/459] Python: Remove modeling of `sqlescapy` PyPI package
I've never seen this being used in real code, and this library doesn't
have a lot of traction, so I would rather not commit to supporting it
(which includes verifying that it actually makes things safe).
Personally I don't think this is the right approach for avoiding SQL
injection either.
---
.../semmle/python/frameworks/SqlAlchemy.qll | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
index 2712c756597..2967450f10d 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
@@ -312,19 +312,6 @@ private module SqlAlchemy {
)
}
}
-
- /**
- * Gets a reference to `sqlescapy.sqlescape`.
- *
- * See https://pypi.org/project/sqlescapy/
- */
- class SQLEscapySanitizerCall extends DataFlow::CallCfgNode, SQLEscape::Range {
- SQLEscapySanitizerCall() {
- this = API::moduleImport("sqlescapy").getMember("sqlescape").getACall()
- }
-
- override DataFlow::Node getAnInput() { result = this.getArg(0) }
- }
}
private module OldModeling {
From 81dbe36e9902277df14561c7a915506b2fc13f3c Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 1 Sep 2021 21:57:43 +0200
Subject: [PATCH 092/459] Python: Promote `SQLAlchemy` modeling
Due to the split between `src/` and `lib/`, I was not really able to do
the next step without having moved the SQLAlchemy modeling over to be in
`lib/` as well.
---
docs/codeql/support/reusables/frameworks.rst | 1 +
python/change-notes/2021-09-02-add-SQLAlchemy-modeling.md | 2 ++
python/ql/lib/semmle/python/Frameworks.qll | 3 ++-
.../semmle/python/frameworks/SqlAlchemy.qll | 1 -
.../library-tests/frameworks/sqlalchemy/ConceptsTest.expected | 0
.../library-tests/frameworks/sqlalchemy/ConceptsTest.ql | 0
.../frameworks/sqlalchemy/InlineTaintTest.expected | 0
.../library-tests/frameworks/sqlalchemy/InlineTaintTest.ql | 0
.../library-tests/frameworks/sqlalchemy/SqlExecution.py | 0
.../library-tests/frameworks/sqlalchemy/new_tests.py | 0
.../library-tests/frameworks/sqlalchemy/taint_test.py | 0
11 files changed, 5 insertions(+), 2 deletions(-)
create mode 100644 python/change-notes/2021-09-02-add-SQLAlchemy-modeling.md
rename python/ql/{src/experimental => lib}/semmle/python/frameworks/SqlAlchemy.qll (99%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/ConceptsTest.expected (100%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/ConceptsTest.ql (100%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/InlineTaintTest.expected (100%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql (100%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/SqlExecution.py (100%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/new_tests.py (100%)
rename python/ql/test/{experimental => }/library-tests/frameworks/sqlalchemy/taint_test.py (100%)
diff --git a/docs/codeql/support/reusables/frameworks.rst b/docs/codeql/support/reusables/frameworks.rst
index 59899b8c58b..39c9cbba83b 100644
--- a/docs/codeql/support/reusables/frameworks.rst
+++ b/docs/codeql/support/reusables/frameworks.rst
@@ -177,6 +177,7 @@ Python built-in support
psycopg2, Database
sqlite3, Database
peewee, Database ORM
+ SQLAlchemy, Database ORM
cryptography, Cryptography library
pycryptodome, Cryptography library
pycryptodomex, Cryptography library
diff --git a/python/change-notes/2021-09-02-add-SQLAlchemy-modeling.md b/python/change-notes/2021-09-02-add-SQLAlchemy-modeling.md
new file mode 100644
index 00000000000..9ba7a72614b
--- /dev/null
+++ b/python/change-notes/2021-09-02-add-SQLAlchemy-modeling.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Added modeling of SQL execution in the `SQLAlchemy` PyPI package, resulting in additional sinks for the SQL Injection query (`py/sql-injection`). This modeling was originally [submitted as a contribution by @mrthankyou](https://github.com/github/codeql/pull/5680).
diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll
index c6499e13508..591094ff208 100644
--- a/python/ql/lib/semmle/python/Frameworks.qll
+++ b/python/ql/lib/semmle/python/Frameworks.qll
@@ -20,13 +20,14 @@ private import semmle.python.frameworks.MarkupSafe
private import semmle.python.frameworks.Multidict
private import semmle.python.frameworks.Mysql
private import semmle.python.frameworks.MySQLdb
+private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Psycopg2
private import semmle.python.frameworks.PyMySQL
private import semmle.python.frameworks.Rsa
private import semmle.python.frameworks.Simplejson
+private import semmle.python.frameworks.SqlAlchemy
private import semmle.python.frameworks.Stdlib
private import semmle.python.frameworks.Tornado
-private import semmle.python.frameworks.Peewee
private import semmle.python.frameworks.Twisted
private import semmle.python.frameworks.Ujson
private import semmle.python.frameworks.Yaml
diff --git a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
similarity index 99%
rename from python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
rename to python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
index 2967450f10d..b06d91ab559 100644
--- a/python/ql/src/experimental/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
@@ -10,7 +10,6 @@ private import semmle.python.dataflow.new.DataFlow
private import semmle.python.dataflow.new.TaintTracking
private import semmle.python.ApiGraphs
private import semmle.python.Concepts
-private import experimental.semmle.python.Concepts
// This import is done like this to avoid importing the deprecated top-level things that
// would pollute the namespace
private import semmle.python.frameworks.PEP249::PEP249 as PEP249
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.expected
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/ConceptsTest.expected
rename to python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.expected
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.ql
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/ConceptsTest.ql
rename to python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.ql
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.expected
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/InlineTaintTest.expected
rename to python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.expected
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql
rename to python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/SqlExecution.py b/python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/SqlExecution.py
rename to python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/new_tests.py
rename to python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py
diff --git a/python/ql/test/experimental/library-tests/frameworks/sqlalchemy/taint_test.py b/python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py
similarity index 100%
rename from python/ql/test/experimental/library-tests/frameworks/sqlalchemy/taint_test.py
rename to python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py
From c34d6d116274dec8891ec3ccbf8d7c32db517501 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 2 Sep 2021 10:13:12 +0200
Subject: [PATCH 093/459] Python: Add query to handle SQLAlchemy TextClause
Injection
instead of doing this via taint-steps. See description in code/tests.
---
...add-SQLAlchemyTextClauseInjection-query.md | 2 +
.../semmle/python/frameworks/SqlAlchemy.qll | 130 ++++++++----------
.../dataflow/SQLAlchemyTextClause.qll | 35 +++++
.../SQLAlchemyTextClauseCustomizations.qll | 56 ++++++++
.../SQLAlchemyTextClauseInjection.qhelp | 52 +++++++
.../CWE-089/SQLAlchemyTextClauseInjection.ql | 23 ++++
.../sqlalchemy_textclause_injection.py | 34 +++++
.../frameworks/sqlalchemy/ConceptsTest.ql | 1 -
.../frameworks/sqlalchemy/InlineTaintTest.ql | 1 -
.../frameworks/sqlalchemy/SqlExecution.py | 8 +-
.../frameworks/sqlalchemy/new_tests.py | 68 ++++-----
.../frameworks/sqlalchemy/taint_test.py | 30 +++-
.../SQLAlchemyTextClauseInjection.expected | 34 +++++
.../SQLAlchemyTextClauseInjection.qlref | 1 +
.../test.py | 43 ++++++
15 files changed, 391 insertions(+), 127 deletions(-)
create mode 100644 python/change-notes/2021-09-02-add-SQLAlchemyTextClauseInjection-query.md
create mode 100644 python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClause.qll
create mode 100644 python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClauseCustomizations.qll
create mode 100644 python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp
create mode 100644 python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.ql
create mode 100644 python/ql/src/Security/CWE-089/examples/sqlalchemy_textclause_injection.py
create mode 100644 python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
create mode 100644 python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.qlref
create mode 100644 python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py
diff --git a/python/change-notes/2021-09-02-add-SQLAlchemyTextClauseInjection-query.md b/python/change-notes/2021-09-02-add-SQLAlchemyTextClauseInjection-query.md
new file mode 100644
index 00000000000..ad842e60b63
--- /dev/null
+++ b/python/change-notes/2021-09-02-add-SQLAlchemyTextClauseInjection-query.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Introduced a new query _SQLAlchemy TextClause built from user-controlled sources_ (`py/sqlalchemy-textclause-injection`) to alert if user-input is added to a TextClause from SQLAlchemy, since that can lead to SQL injection.
diff --git a/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
index b06d91ab559..56a97ade24c 100644
--- a/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
@@ -15,12 +15,14 @@ private import semmle.python.Concepts
private import semmle.python.frameworks.PEP249::PEP249 as PEP249
/**
+ * INTERNAL: Do not use.
+ *
* Provides models for the `SQLAlchemy` PyPI package.
* See
* - https://pypi.org/project/SQLAlchemy/
* - https://docs.sqlalchemy.org/en/14/index.html
*/
-private module SqlAlchemy {
+module SqlAlchemy {
/**
* Provides models for the `sqlalchemy.engine.Engine` and `sqlalchemy.future.Engine` classes.
*
@@ -279,80 +281,62 @@ private module SqlAlchemy {
}
/**
- * Additional taint-steps for `sqlalchemy.text()`
+ * Provides models for the `sqlalchemy.sql.expression.TextClause` class,
+ * which represents a textual SQL string directly.
*
- * See https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text
- * See https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.TextClause
+ * ```py
+ * session.query(For14).filter_by(description=sqlalchemy.text(f"'{user_input}'")).all()
+ * ```
+ *
+ * Initially I wanted to add lots of additional taint steps for such that the normal
+ * SQL injection query would be able to find cases as the one above where an ORM query
+ * includes a TextClause that includes user-input directly... But that presented 2
+ * problems:
+ *
+ * - which part of the query construction above should be marked as SQL to fit our
+ * `SqlExecution` concept. Nothing really fits this well, since all the SQL
+ * execution happens under the hood.
+ * - This would require a LOT of modeling for these additional taint steps, since
+ * there are many many constructs we would need to have models for. (see the 2
+ * examples below)
+ *
+ * So instead we flag user-input to a TextClause with its' own query
+ * (`py/sqlalchemy-textclause-injection`). And so we don't highlight any parts of an
+ * ORM constructed query such as these as containing SQL, and don't need the additional
+ * taint steps either.
+ *
+ * See
+ * - https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.TextClause.
+ * - https://docs.sqlalchemy.org/en/14/core/sqlelement.html#sqlalchemy.sql.expression.text
*/
- class SqlAlchemyTextAdditionalTaintSteps extends TaintTracking::AdditionalTaintStep {
- override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
- exists(DataFlow::CallCfgNode call |
- (
- call = API::moduleImport("sqlalchemy").getMember("text").getACall()
- or
- call = API::moduleImport("sqlalchemy").getMember("sql").getMember("text").getACall()
- or
- call =
- API::moduleImport("sqlalchemy")
- .getMember("sql")
- .getMember("expression")
- .getMember("text")
- .getACall()
- or
- call =
- API::moduleImport("sqlalchemy")
- .getMember("sql")
- .getMember("expression")
- .getMember("TextClause")
- .getACall()
- ) and
- nodeFrom in [call.getArg(0), call.getArgByName("text")] and
- nodeTo = call
- )
+ module TextClause {
+ /**
+ * A construction of a `sqlalchemy.sql.expression.TextClause`, which represents a
+ * textual SQL string directly.
+ */
+ class TextClauseConstruction extends DataFlow::CallCfgNode {
+ TextClauseConstruction() {
+ this = API::moduleImport("sqlalchemy").getMember("text").getACall()
+ or
+ this = API::moduleImport("sqlalchemy").getMember("sql").getMember("text").getACall()
+ or
+ this =
+ API::moduleImport("sqlalchemy")
+ .getMember("sql")
+ .getMember("expression")
+ .getMember("text")
+ .getACall()
+ or
+ this =
+ API::moduleImport("sqlalchemy")
+ .getMember("sql")
+ .getMember("expression")
+ .getMember("TextClause")
+ .getACall()
+ }
+
+ /** Gets the argument that specifies the SQL text. */
+ DataFlow::Node getTextArg() { result in [this.getArg(0), this.getArgByName("text")] }
}
}
}
-
-private module OldModeling {
- /**
- * Returns an instantization of a SqlAlchemy Session object.
- * See https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.Session and
- * https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker
- */
- private API::Node getSqlAlchemySessionInstance() {
- result = API::moduleImport("sqlalchemy.orm").getMember("Session").getReturn() or
- result = API::moduleImport("sqlalchemy.orm").getMember("sessionmaker").getReturn().getReturn()
- }
-
- /**
- * Returns an instantization of a SqlAlchemy Query object.
- * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
- */
- private API::Node getSqlAlchemyQueryInstance() {
- result = getSqlAlchemySessionInstance().getMember("query").getReturn()
- }
-
- /**
- * A call on a Query object
- * See https://docs.sqlalchemy.org/en/14/orm/query.html?highlight=query#sqlalchemy.orm.Query
- */
- private class SqlAlchemyQueryCall extends DataFlow::CallCfgNode, SqlExecution::Range {
- SqlAlchemyQueryCall() {
- this =
- getSqlAlchemyQueryInstance()
- .getMember(any(SqlAlchemyVulnerableMethodNames methodName))
- .getACall()
- }
-
- override DataFlow::Node getSql() { result = this.getArg(0) }
- }
-
- /**
- * This class represents a list of methods vulnerable to sql injection.
- *
- * See https://github.com/jty-team/codeql/pull/2#issue-611592361
- */
- private class SqlAlchemyVulnerableMethodNames extends string {
- SqlAlchemyVulnerableMethodNames() { this in ["filter", "filter_by", "group_by", "order_by"] }
- }
-}
diff --git a/python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClause.qll b/python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClause.qll
new file mode 100644
index 00000000000..a4e65fe3120
--- /dev/null
+++ b/python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClause.qll
@@ -0,0 +1,35 @@
+/**
+ * Provides a taint-tracking configuration for detecting "SQLAlchemy TextClause injection" vulnerabilities.
+ *
+ * Note, for performance reasons: only import this file if
+ * `SQLAlchemyTextClause::Configuration` is needed, otherwise
+ * `SQLAlchemyTextClauseCustomizations` should be imported instead.
+ */
+
+private import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+
+/**
+ * Provides a taint-tracking configuration for detecting "SQLAlchemy TextClause injection" vulnerabilities.
+ */
+module SQLAlchemyTextClause {
+ import SQLAlchemyTextClauseCustomizations::SQLAlchemyTextClause
+
+ /**
+ * A taint-tracking configuration for detecting "SQLAlchemy TextClause injection" vulnerabilities.
+ */
+ class Configuration extends TaintTracking::Configuration {
+ Configuration() { this = "SQLAlchemyTextClause" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof Source }
+
+ override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
+
+ override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof SanitizerGuard
+ }
+ }
+}
diff --git a/python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClauseCustomizations.qll b/python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClauseCustomizations.qll
new file mode 100644
index 00000000000..fa75a543ade
--- /dev/null
+++ b/python/ql/lib/semmle/python/security/dataflow/SQLAlchemyTextClauseCustomizations.qll
@@ -0,0 +1,56 @@
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "SQLAlchemy TextClause injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.Concepts
+private import semmle.python.dataflow.new.RemoteFlowSources
+private import semmle.python.dataflow.new.BarrierGuards
+private import semmle.python.frameworks.SqlAlchemy
+
+/**
+ * Provides default sources, sinks and sanitizers for detecting
+ * "SQLAlchemy TextClause injection"
+ * vulnerabilities, as well as extension points for adding your own.
+ */
+module SQLAlchemyTextClause {
+ /**
+ * A data flow source for "SQLAlchemy TextClause injection" vulnerabilities.
+ */
+ abstract class Source extends DataFlow::Node { }
+
+ /**
+ * A data flow sink for "SQLAlchemy TextClause injection" vulnerabilities.
+ */
+ abstract class Sink extends DataFlow::Node { }
+
+ /**
+ * A sanitizer for "SQLAlchemy TextClause injection" vulnerabilities.
+ */
+ abstract class Sanitizer extends DataFlow::Node { }
+
+ /**
+ * A sanitizer guard for "SQLAlchemy TextClause injection" vulnerabilities.
+ */
+ abstract class SanitizerGuard extends DataFlow::BarrierGuard { }
+
+ /**
+ * A source of remote user input, considered as a flow source.
+ */
+ class RemoteFlowSourceAsSource extends Source, RemoteFlowSource { }
+
+ /**
+ * The text argument of a SQLAlchemy TextClause construction, considered as a flow sink.
+ */
+ class TextArgAsSink extends Sink {
+ TextArgAsSink() { this = any(SqlAlchemy::TextClause::TextClauseConstruction tcc).getTextArg() }
+ }
+
+ /**
+ * A comparison with a constant string, considered as a sanitizer-guard.
+ */
+ class StringConstCompareAsSanitizerGuard extends SanitizerGuard, StringConstCompare { }
+}
diff --git a/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp b/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp
new file mode 100644
index 00000000000..fd87df3882c
--- /dev/null
+++ b/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp
@@ -0,0 +1,52 @@
+
+
+
+
+
+The TextClause class in the SQLAlchemy PyPI package represents
+a textual SQL string directly. If user-input is added to it without sufficient
+sanitization, a user may be able to run malicious database queries, since the
+TextClause is inserted directly into the final SQL.
+
+
+
+
+Don't allow user-input to be added to a TextClause, instead construct your
+full query with constructs from the ORM, or use query parameters for user-input.
+
+
+
+
+
+In the following snippet, a user is fetched from the database using three
+different queries.
+
+
+
+In the first case, the final query string is built by directly including a user-supplied
+input. The parameter may include quote characters, so this code is vulnerable to a SQL
+injection attack.
+
+
+
+In the second case, the query is built using ORM models, but part of it is using a
+TextClause directly including a user-supplied input. Since the
+TextClause is inserted directly into the final SQL, this code is vulnerable
+to a SQL injection attack.
+
+
+
+In the third case, the query is built fully using the ORM models, so in the end, the
+user-supplied input will passed passed to the database using query parameters. The
+database connector library will take care of escaping and inserting quotes as needed.
+
+
+
diff --git a/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.ql b/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.ql
new file mode 100644
index 00000000000..4e4f747bf20
--- /dev/null
+++ b/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.ql
@@ -0,0 +1,23 @@
+/**
+ * @name SQLAlchemy TextClause built from user-controlled sources
+ * @description Building a TextClause query from user-controlled sources is vulnerable to insertion of
+ * malicious SQL code by the user.
+ * @kind path-problem
+ * @problem.severity error
+ * @security-severity 8.8
+ * @precision high
+ * @id py/sqlalchemy-textclause-injection
+ * @tags security
+ * external/cwe/cwe-089
+ * external/owasp/owasp-a1
+ */
+
+import python
+import semmle.python.security.dataflow.SQLAlchemyTextClause
+import DataFlow::PathGraph
+
+from SQLAlchemyTextClause::Configuration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink,
+ "This SQLAlchemy TextClause depends on $@, which could lead to SQL injection.", source.getNode(),
+ "a user-provided value"
diff --git a/python/ql/src/Security/CWE-089/examples/sqlalchemy_textclause_injection.py b/python/ql/src/Security/CWE-089/examples/sqlalchemy_textclause_injection.py
new file mode 100644
index 00000000000..030c27202de
--- /dev/null
+++ b/python/ql/src/Security/CWE-089/examples/sqlalchemy_textclause_injection.py
@@ -0,0 +1,34 @@
+from flask import Flask, request
+import sqlalchemy
+import sqlalchemy.orm
+
+app = Flask(__name__)
+engine = sqlalchemy.create_engine(...)
+Base = sqlalchemy.orm.declarative_base()
+
+
+class User(Base):
+ __tablename__ = "users"
+
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
+ username = sqlalchemy.Column(sqlalchemy.String)
+
+
+@app.route("/users/")
+def show_user(username):
+ session = sqlalchemy.orm.Session(engine)
+
+ # BAD, normal SQL injection
+ stmt = sqlalchemy.text("SELECT * FROM users WHERE username = '{}'".format(username))
+ results = session.execute(stmt).fetchall()
+
+ # BAD, allows SQL injection
+ username_formatted_for_sql = sqlalchemy.text("'{}'".format(username))
+ stmt = sqlalchemy.select(User).where(User.username == username_formatted_for_sql)
+ results = session.execute(stmt).scalars().all()
+
+ # GOOD, does not allow for SQL injection
+ stmt = sqlalchemy.select(User).where(User.username == username)
+ results = session.execute(stmt).scalars().all()
+
+ ...
diff --git a/python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.ql
index b097eb15648..b557a0bccb6 100644
--- a/python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.ql
+++ b/python/ql/test/library-tests/frameworks/sqlalchemy/ConceptsTest.ql
@@ -1,3 +1,2 @@
import python
import experimental.meta.ConceptsTest
-import experimental.semmle.python.frameworks.SqlAlchemy
diff --git a/python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql
index 2ddb342c62f..027ad8667be 100644
--- a/python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql
+++ b/python/ql/test/library-tests/frameworks/sqlalchemy/InlineTaintTest.ql
@@ -1,2 +1 @@
import experimental.meta.InlineTaintTest
-import experimental.semmle.python.frameworks.SqlAlchemy
diff --git a/python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py b/python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py
index 7731e80e534..a7c9af9da32 100644
--- a/python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py
+++ b/python/ql/test/library-tests/frameworks/sqlalchemy/SqlExecution.py
@@ -47,10 +47,10 @@ with engine.begin() as connection:
# Injection requiring the text() taint-step
t = text("some sql")
-session.query(User).filter(t) # $ getSql=t
-session.query(User).group_by(User.id).having(t) # $ getSql=User.id MISSING: getSql=t
-session.query(User).group_by(t).first() # $ getSql=t
-session.query(User).order_by(t).first() # $ getSql=t
+session.query(User).filter(t)
+session.query(User).group_by(User.id).having(t)
+session.query(User).group_by(t).first()
+session.query(User).order_by(t).first()
query = select(User).where(User.name == t) # $ MISSING: getSql=t
with engine.connect() as conn:
diff --git a/python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py b/python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py
index 7e12fa60ee3..0946cc0da40 100644
--- a/python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py
+++ b/python/ql/test/library-tests/frameworks/sqlalchemy/new_tests.py
@@ -189,57 +189,41 @@ text_foo = sqlalchemy.text("'FOO'")
#
# which is then called without any arguments
assert session.query(For14).filter_by(description="'FOO'").all() == []
-query = session.query(For14).filter_by(description=text_foo) # $ MISSING: getSql=text_foo
+query = session.query(For14).filter_by(description=text_foo)
assert query.all() == []
+# Initially I wanted to add lots of additional taint steps such that the normal SQL
+# injection query would find these cases where an ORM query includes a TextClause that
+# includes user-input directly... But that presented 2 problems:
+#
+# - which part of the query construction above should be marked as SQL to fit our
+# `SqlExecution` concept. Nothing really fits this well, since all the SQL execution
+# happens under the hood.
+# - This would require a LOT of modeling for these additional taint steps, since there
+# are many many constructs we would need to have models for. (see the 2 examples below)
+#
+# So instead we flag user-input to a TextClause with its' own query. And so we don't
+# highlight any parts of an ORM constructed query such as these as containing SQL.
+
# `filter` provides more general filtering
# see https://docs.sqlalchemy.org/en/14/orm/tutorial.html#common-filter-operators
# and https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.filter
assert session.query(For14).filter(For14.description == "'FOO'").all() == []
-query = session.query(For14).filter(For14.description == text_foo) # $ MISSING: getSql=text_foo
+query = session.query(For14).filter(For14.description == text_foo)
assert query.all() == []
assert session.query(For14).filter(For14.description.like("'FOO'")).all() == []
-query = session.query(For14).filter(For14.description.like(text_foo)) # $ MISSING: getSql=text_foo
+query = session.query(For14).filter(For14.description.like(text_foo))
assert query.all() == []
-# `where` is alias for `filter`
-assert session.query(For14).where(For14.description == "'FOO'").all() == []
-query = session.query(For14).where(For14.description == text_foo) # $ MISSING: getSql=text_foo
-assert query.all() == []
-
-
-# not possible to do SQL injection on `.get`
-try:
- session.query(For14).get(text_foo)
-except sqlalchemy.exc.InterfaceError:
- pass
-
-# group_by
-assert session.query(For14).group_by(For14.description).all() != []
-query = session.query(For14).group_by(text_foo) # $ MISSING: getSql=text_foo
-assert query.all() != []
-
-# having (only used in connection with group_by)
-assert session.query(For14).group_by(For14.description).having(
- sqlalchemy.func.count(For14.id) > 2
-).all() == []
-query = session.query(For14).group_by(For14.description).having(text_foo) # $ MISSING: getSql=text_foo
-assert query.all() == []
-
-# order_by
-assert session.query(For14).order_by(For14.description).all() != []
-query = session.query(For14).order_by(text_foo) # $ MISSING: getSql=text_foo
-assert query.all() != []
-
-# TODO: likewise, it should be possible to target the criterion for:
-# - `join` https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.join
-# - `outerjoin` https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.outerjoin
-
-# specifying session later on
-# see https://docs.sqlalchemy.org/en/14/orm/query.html#sqlalchemy.orm.Query.with_session
-query = sqlalchemy.orm.Query(For14).filter(For14.description == text_foo) # $ MISSING: getSql=text_foo
-assert query.with_session(session).all() == []
+# There are many other possibilities for ending up with SQL injection, including the
+# following (not an exhaustive list):
+# - `where` (alias for `filter`)
+# - `group_by`
+# - `having`
+# - `order_by`
+# - `join`
+# - `outerjoin`
# ==============================================================================
# v2.0
@@ -374,6 +358,8 @@ scalar_result = session.scalar(statement=text_sql) # $ getSql=text_sql
assert scalar_result == "FOO"
# Querying (2.0)
+# uses a slightly different style than 1.4 -- see note about not modeling
+# ORM query construction as SQL execution at the 1.4 query tests.
class For20(Base):
__tablename__ = "for20"
@@ -397,6 +383,6 @@ statement = sqlalchemy.select(For20)
result = session.execute(statement) # $ getSql=statement
assert result.scalars().all()[0].id == 20
-statement = sqlalchemy.select(For20).where(For20.description == text_foo) # $ MISSING: getSql=text_foo
+statement = sqlalchemy.select(For20).where(For20.description == text_foo)
result = session.execute(statement) # $ getSql=statement
assert result.scalars().all() == []
diff --git a/python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py b/python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py
index 91f8987132f..9e9764a411f 100644
--- a/python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py
+++ b/python/ql/test/library-tests/frameworks/sqlalchemy/taint_test.py
@@ -1,12 +1,28 @@
import sqlalchemy
+ensure_tainted = ensure_not_tainted = print
+TAINTED_STRING = "TAINTED_STRING"
+
def test_taint():
ts = TAINTED_STRING
- ensure_tainted(
- ts, # $ tainted
- sqlalchemy.text(ts), # $ tainted
- sqlalchemy.sql.text(ts),# $ tainted
- sqlalchemy.sql.expression.text(ts),# $ tainted
- sqlalchemy.sql.expression.TextClause(ts),# $ tainted
- )
+ ensure_tainted(ts) # $ tainted
+
+ t1 = sqlalchemy.text(ts)
+ t2 = sqlalchemy.text(text=ts)
+ t3 = sqlalchemy.sql.text(ts)
+ t4 = sqlalchemy.sql.text(text=ts)
+ t5 = sqlalchemy.sql.expression.text(ts)
+ t6 = sqlalchemy.sql.expression.text(text=ts)
+ t7 = sqlalchemy.sql.expression.TextClause(ts)
+ t8 = sqlalchemy.sql.expression.TextClause(text=ts)
+
+ # Since we flag user-input to a TextClause with its' own query, we don't want to
+ # have a taint-step for it as that would lead to us also giving an alert for normal
+ # SQL-injection... and double alerting like this does not seem desireable.
+ ensure_not_tainted(t1, t2, t3, t4, t5, t6, t7, t8)
+
+ for text in [t1, t2, t3, t4, t5, t6, t7, t8]:
+ assert isinstance(text, sqlalchemy.sql.expression.TextClause)
+
+test_taint()
diff --git a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
new file mode 100644
index 00000000000..c2b95826a90
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
@@ -0,0 +1,34 @@
+edges
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:22:28:22:87 | ControlFlowNode for Attribute() |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:26:50:26:72 | ControlFlowNode for Attribute() |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:36:26:36:33 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:37:31:37:38 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:38:30:38:37 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:39:35:39:42 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:40:41:40:48 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:41:46:41:53 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:42:47:42:54 | ControlFlowNode for username |
+| test.py:18:15:18:22 | ControlFlowNode for username | test.py:43:52:43:59 | ControlFlowNode for username |
+nodes
+| test.py:18:15:18:22 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:22:28:22:87 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| test.py:26:50:26:72 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| test.py:36:26:36:33 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:37:31:37:38 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:38:30:38:37 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:39:35:39:42 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:40:41:40:48 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:41:46:41:53 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:42:47:42:54 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:43:52:43:59 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+#select
+| test.py:22:28:22:87 | ControlFlowNode for Attribute() | test.py:18:15:18:22 | ControlFlowNode for username | test.py:22:28:22:87 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:26:50:26:72 | ControlFlowNode for Attribute() | test.py:18:15:18:22 | ControlFlowNode for username | test.py:26:50:26:72 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:36:26:36:33 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:36:26:36:33 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:37:31:37:38 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:37:31:37:38 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:38:30:38:37 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:38:30:38:37 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:39:35:39:42 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:39:35:39:42 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:40:41:40:48 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:40:41:40:48 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:41:46:41:53 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:41:46:41:53 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:42:47:42:54 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:42:47:42:54 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:43:52:43:59 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:43:52:43:59 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
diff --git a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.qlref b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.qlref
new file mode 100644
index 00000000000..ae8c3cc69b6
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.qlref
@@ -0,0 +1 @@
+Security/CWE-089/SQLAlchemyTextClauseInjection.ql
diff --git a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py
new file mode 100644
index 00000000000..4ec56b85853
--- /dev/null
+++ b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py
@@ -0,0 +1,43 @@
+from flask import Flask, request
+import sqlalchemy
+import sqlalchemy.orm
+
+app = Flask(__name__)
+engine = sqlalchemy.create_engine(...)
+Base = sqlalchemy.orm.declarative_base()
+
+
+class User(Base):
+ __tablename__ = "users"
+
+ id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
+ username = sqlalchemy.Column(sqlalchemy.String)
+
+
+@app.route("/users/")
+def show_user(username):
+ session = sqlalchemy.orm.Session(engine)
+
+ # BAD, normal SQL injection
+ stmt = sqlalchemy.text("SELECT * FROM users WHERE username = '{}'".format(username))
+ results = session.execute(stmt).fetchall()
+
+ # BAD, allows SQL injection
+ username_formatted_for_sql = sqlalchemy.text("'{}'".format(username))
+ stmt = sqlalchemy.select(User).where(User.username == username_formatted_for_sql)
+ results = session.execute(stmt).scalars().all()
+
+ # GOOD, does not allow for SQL injection
+ stmt = sqlalchemy.select(User).where(User.username == username)
+ results = session.execute(stmt).scalars().all()
+
+
+ # All of these should be flagged by query
+ t1 = sqlalchemy.text(username)
+ t2 = sqlalchemy.text(text=username)
+ t3 = sqlalchemy.sql.text(username)
+ t4 = sqlalchemy.sql.text(text=username)
+ t5 = sqlalchemy.sql.expression.text(username)
+ t6 = sqlalchemy.sql.expression.text(text=username)
+ t7 = sqlalchemy.sql.expression.TextClause(username)
+ t8 = sqlalchemy.sql.expression.TextClause(text=username)
From f1744890b17cf944f3c5c9996f8be3b3e52bc8ae Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 2 Sep 2021 10:36:23 +0200
Subject: [PATCH 094/459] Python: Add tests for `Flask-SQLAlchemy`
---
.../flask_sqlalchemy/ConceptsTest.expected | 0
.../flask_sqlalchemy/ConceptsTest.ql | 2 +
.../flask_sqlalchemy/InlineTaintTest.expected | 3 +
.../flask_sqlalchemy/InlineTaintTest.ql | 1 +
.../flask_sqlalchemy/SqlExecution.py | 51 +++++++++++++++
.../SQLAlchemyTextClauseInjection.expected | 62 +++++++++----------
.../test.py | 8 +++
7 files changed, 96 insertions(+), 31 deletions(-)
create mode 100644 python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.expected
create mode 100644 python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.ql
create mode 100644 python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.expected
create mode 100644 python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.ql
create mode 100644 python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py
diff --git a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.ql
new file mode 100644
index 00000000000..b557a0bccb6
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/ConceptsTest.ql
@@ -0,0 +1,2 @@
+import python
+import experimental.meta.ConceptsTest
diff --git a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.expected
new file mode 100644
index 00000000000..79d760d87f4
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.expected
@@ -0,0 +1,3 @@
+argumentToEnsureNotTaintedNotMarkedAsSpurious
+untaintedArgumentToEnsureTaintedNotMarkedAsMissing
+failures
diff --git a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.ql
new file mode 100644
index 00000000000..027ad8667be
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/InlineTaintTest.ql
@@ -0,0 +1 @@
+import experimental.meta.InlineTaintTest
diff --git a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py
new file mode 100644
index 00000000000..f0f7db6130e
--- /dev/null
+++ b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py
@@ -0,0 +1,51 @@
+# pip install Flask-SQLAlchemy
+from flask import Flask
+from flask_sqlalchemy import SQLAlchemy
+import sqlalchemy
+
+app = Flask(__name__)
+app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite+pysqlite:///:memory:"
+db = SQLAlchemy(app)
+
+# re-exports all things from `sqlalchemy` and `sqlalchemy.orm` under instances of `SQLAlchemy`
+# see
+# - https://github.com/pallets/flask-sqlalchemy/blob/931ec00d1e27f51508e05706eef41cc4419a0b32/src/flask_sqlalchemy/__init__.py#L765
+# - https://github.com/pallets/flask-sqlalchemy/blob/931ec00d1e27f51508e05706eef41cc4419a0b32/src/flask_sqlalchemy/__init__.py#L99-L109
+
+assert str(type(db.text("Foo"))) == ""
+
+# also has engine/session instantiated
+
+raw_sql = "SELECT 'Foo'"
+
+conn = db.engine.connect()
+result = conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("Foo",)]
+
+conn = db.get_engine().connect()
+result = conn.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("Foo",)]
+
+result = db.session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("Foo",)]
+
+Session = db.create_session(options={})
+session = Session()
+result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("Foo",)]
+
+Session = db.create_session(options={})
+with Session.begin() as session:
+ result = session.execute(raw_sql) # $ MISSING: getSql=raw_sql
+ assert result.fetchall() == [("Foo",)]
+
+result = db.create_scoped_session().execute(raw_sql) # $ MISSING: getSql=raw_sql
+assert result.fetchall() == [("Foo",)]
+
+
+# text
+t = db.text("foo")
+assert isinstance(t, sqlalchemy.sql.expression.TextClause)
+
+t = db.text(text="foo")
+assert isinstance(t, sqlalchemy.sql.expression.TextClause)
diff --git a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
index c2b95826a90..36c86ead3d5 100644
--- a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
+++ b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
@@ -1,34 +1,34 @@
edges
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:22:28:22:87 | ControlFlowNode for Attribute() |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:26:50:26:72 | ControlFlowNode for Attribute() |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:36:26:36:33 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:37:31:37:38 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:38:30:38:37 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:39:35:39:42 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:40:41:40:48 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:41:46:41:53 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:42:47:42:54 | ControlFlowNode for username |
-| test.py:18:15:18:22 | ControlFlowNode for username | test.py:43:52:43:59 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:27:28:27:87 | ControlFlowNode for Attribute() |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:31:50:31:72 | ControlFlowNode for Attribute() |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:41:26:41:33 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:42:31:42:38 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:43:30:43:37 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:44:35:44:42 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:45:41:45:48 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:46:46:46:53 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:47:47:47:54 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:48:52:48:59 | ControlFlowNode for username |
nodes
-| test.py:18:15:18:22 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:22:28:22:87 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| test.py:26:50:26:72 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
-| test.py:36:26:36:33 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:37:31:37:38 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:38:30:38:37 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:39:35:39:42 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:40:41:40:48 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:41:46:41:53 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:42:47:42:54 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
-| test.py:43:52:43:59 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:27:28:27:87 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| test.py:31:50:31:72 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
+| test.py:41:26:41:33 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:42:31:42:38 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:43:30:43:37 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:44:35:44:42 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:45:41:45:48 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:46:46:46:53 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:47:47:47:54 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:48:52:48:59 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
#select
-| test.py:22:28:22:87 | ControlFlowNode for Attribute() | test.py:18:15:18:22 | ControlFlowNode for username | test.py:22:28:22:87 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:26:50:26:72 | ControlFlowNode for Attribute() | test.py:18:15:18:22 | ControlFlowNode for username | test.py:26:50:26:72 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:36:26:36:33 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:36:26:36:33 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:37:31:37:38 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:37:31:37:38 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:38:30:38:37 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:38:30:38:37 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:39:35:39:42 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:39:35:39:42 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:40:41:40:48 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:40:41:40:48 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:41:46:41:53 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:41:46:41:53 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:42:47:42:54 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:42:47:42:54 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
-| test.py:43:52:43:59 | ControlFlowNode for username | test.py:18:15:18:22 | ControlFlowNode for username | test.py:43:52:43:59 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:18:15:18:22 | ControlFlowNode for username | a user-provided value |
+| test.py:27:28:27:87 | ControlFlowNode for Attribute() | test.py:23:15:23:22 | ControlFlowNode for username | test.py:27:28:27:87 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:31:50:31:72 | ControlFlowNode for Attribute() | test.py:23:15:23:22 | ControlFlowNode for username | test.py:31:50:31:72 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:41:26:41:33 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:41:26:41:33 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:42:31:42:38 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:42:31:42:38 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:43:30:43:37 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:43:30:43:37 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:44:35:44:42 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:44:35:44:42 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:45:41:45:48 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:45:41:45:48 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:46:46:46:53 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:46:46:46:53 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:47:47:47:54 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:47:47:47:54 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:48:52:48:59 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:48:52:48:59 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
diff --git a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py
index 4ec56b85853..a54d64517d4 100644
--- a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py
+++ b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/test.py
@@ -1,11 +1,16 @@
from flask import Flask, request
import sqlalchemy
import sqlalchemy.orm
+from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
engine = sqlalchemy.create_engine(...)
Base = sqlalchemy.orm.declarative_base()
+app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite+pysqlite:///:memory:"
+db = SQLAlchemy(app)
+
+
class User(Base):
__tablename__ = "users"
@@ -41,3 +46,6 @@ def show_user(username):
t6 = sqlalchemy.sql.expression.text(text=username)
t7 = sqlalchemy.sql.expression.TextClause(username)
t8 = sqlalchemy.sql.expression.TextClause(text=username)
+
+ t9 = db.text(username)
+ t10 = db.text(text=username)
From d55f18f8e3ed98ec37711ccef328610f0d2a8207 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 2 Sep 2021 10:48:24 +0200
Subject: [PATCH 095/459] Python: Add modeling of `Flask-SQLAlchemy`
---
docs/codeql/support/reusables/frameworks.rst | 1 +
...021-09-02-add-Flask-SQLAlchemy-modeling.md | 2 +
python/ql/lib/semmle/python/Frameworks.qll | 1 +
.../python/frameworks/FlaskSqlAlchemy.qll | 56 +++++++++++++++++++
.../semmle/python/frameworks/SqlAlchemy.qll | 12 ++--
.../flask_sqlalchemy/SqlExecution.py | 12 ++--
6 files changed, 73 insertions(+), 11 deletions(-)
create mode 100644 python/change-notes/2021-09-02-add-Flask-SQLAlchemy-modeling.md
create mode 100644 python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll
diff --git a/docs/codeql/support/reusables/frameworks.rst b/docs/codeql/support/reusables/frameworks.rst
index 39c9cbba83b..0846786db63 100644
--- a/docs/codeql/support/reusables/frameworks.rst
+++ b/docs/codeql/support/reusables/frameworks.rst
@@ -176,6 +176,7 @@ Python built-in support
mysqlclient, Database
psycopg2, Database
sqlite3, Database
+ Flask-SQLAlchemy, Database ORM
peewee, Database ORM
SQLAlchemy, Database ORM
cryptography, Cryptography library
diff --git a/python/change-notes/2021-09-02-add-Flask-SQLAlchemy-modeling.md b/python/change-notes/2021-09-02-add-Flask-SQLAlchemy-modeling.md
new file mode 100644
index 00000000000..82c7dce9cfd
--- /dev/null
+++ b/python/change-notes/2021-09-02-add-Flask-SQLAlchemy-modeling.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Added modeling of SQL execution in the `Flask-SQLAlchemy` PyPI package, resulting in additional sinks for the SQL Injection query (`py/sql-injection`).
diff --git a/python/ql/lib/semmle/python/Frameworks.qll b/python/ql/lib/semmle/python/Frameworks.qll
index 591094ff208..b3ff235c3ee 100644
--- a/python/ql/lib/semmle/python/Frameworks.qll
+++ b/python/ql/lib/semmle/python/Frameworks.qll
@@ -13,6 +13,7 @@ private import semmle.python.frameworks.Dill
private import semmle.python.frameworks.Django
private import semmle.python.frameworks.Fabric
private import semmle.python.frameworks.Flask
+private import semmle.python.frameworks.FlaskSqlAlchemy
private import semmle.python.frameworks.Idna
private import semmle.python.frameworks.Invoke
private import semmle.python.frameworks.Jmespath
diff --git a/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll b/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll
new file mode 100644
index 00000000000..08ba276e6ce
--- /dev/null
+++ b/python/ql/lib/semmle/python/frameworks/FlaskSqlAlchemy.qll
@@ -0,0 +1,56 @@
+/**
+ * Provides classes modeling security-relevant aspects of the `Flask-SQLAlchemy` PyPI package
+ * (imported by `flask_sqlalchemy`).
+ * See
+ * - https://pypi.org/project/Flask-SQLAlchemy/
+ * - https://flask-sqlalchemy.palletsprojects.com/en/2.x/
+ */
+
+private import python
+private import semmle.python.dataflow.new.DataFlow
+private import semmle.python.dataflow.new.TaintTracking
+private import semmle.python.ApiGraphs
+private import semmle.python.Concepts
+private import semmle.python.frameworks.SqlAlchemy
+
+/**
+ * INTERNAL: Do not use.
+ *
+ * Provides models for the `Flask-SQLAlchemy` PyPI package (imported by `flask_sqlalchemy`).
+ * See
+ * - https://pypi.org/project/Flask-SQLAlchemy/
+ * - https://flask-sqlalchemy.palletsprojects.com/en/2.x/
+ */
+private module FlaskSqlAlchemy {
+ /** Gets an instance of `flask_sqlalchemy.SQLAlchemy` */
+ private API::Node dbInstance() {
+ result = API::moduleImport("flask_sqlalchemy").getMember("SQLAlchemy").getReturn()
+ }
+
+ /** A call to the `text` method on a DB. */
+ private class DbTextCall extends SqlAlchemy::TextClause::TextClauseConstruction {
+ DbTextCall() { this = dbInstance().getMember("text").getACall() }
+ }
+
+ /** Access on a DB resulting in an Engine */
+ private class DbEngine extends SqlAlchemy::Engine::InstanceSource {
+ DbEngine() {
+ this = dbInstance().getMember("engine").getAUse()
+ or
+ this = dbInstance().getMember("get_engine").getACall()
+ }
+ }
+
+ /** Access on a DB resulting in a Session */
+ private class DbSession extends SqlAlchemy::Session::InstanceSource {
+ DbSession() {
+ this = dbInstance().getMember("session").getAUse()
+ or
+ this = dbInstance().getMember("create_session").getReturn().getACall()
+ or
+ this = dbInstance().getMember("create_session").getReturn().getMember("begin").getACall()
+ or
+ this = dbInstance().getMember("create_scoped_session").getACall()
+ }
+ }
+}
diff --git a/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
index 56a97ade24c..fa3da97bf29 100644
--- a/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
@@ -314,8 +314,13 @@ module SqlAlchemy {
* A construction of a `sqlalchemy.sql.expression.TextClause`, which represents a
* textual SQL string directly.
*/
- class TextClauseConstruction extends DataFlow::CallCfgNode {
- TextClauseConstruction() {
+ abstract class TextClauseConstruction extends DataFlow::CallCfgNode {
+ /** Gets the argument that specifies the SQL text. */
+ DataFlow::Node getTextArg() { result in [this.getArg(0), this.getArgByName("text")] }
+ }
+
+ class DefaultTextClauseConstruction extends TextClauseConstruction {
+ DefaultTextClauseConstruction() {
this = API::moduleImport("sqlalchemy").getMember("text").getACall()
or
this = API::moduleImport("sqlalchemy").getMember("sql").getMember("text").getACall()
@@ -334,9 +339,6 @@ module SqlAlchemy {
.getMember("TextClause")
.getACall()
}
-
- /** Gets the argument that specifies the SQL text. */
- DataFlow::Node getTextArg() { result in [this.getArg(0), this.getArgByName("text")] }
}
}
}
diff --git a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py
index f0f7db6130e..7560aff2d30 100644
--- a/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py
+++ b/python/ql/test/library-tests/frameworks/flask_sqlalchemy/SqlExecution.py
@@ -19,27 +19,27 @@ assert str(type(db.text("Foo"))) == "
Date: Thu, 2 Sep 2021 16:02:04 +0200
Subject: [PATCH 096/459] Python: Fix .qhelp
---
.../ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp | 1 +
1 file changed, 1 insertion(+)
diff --git a/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp b/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp
index fd87df3882c..022fd4741da 100644
--- a/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp
+++ b/python/ql/src/Security/CWE-089/SQLAlchemyTextClauseInjection.qhelp
@@ -9,6 +9,7 @@ The TextClause class in the SQLAlchemy PyPI package re
a textual SQL string directly. If user-input is added to it without sufficient
sanitization, a user may be able to run malicious database queries, since the
TextClause is inserted directly into the final SQL.
+
From 414bf12f867b210fc72296bd1f4c021023b52cc7 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 2 Sep 2021 16:03:25 +0200
Subject: [PATCH 097/459] Python: Fix DefaultTextClauseConstruction
---
python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
index fa3da97bf29..879456e4700 100644
--- a/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
+++ b/python/ql/lib/semmle/python/frameworks/SqlAlchemy.qll
@@ -319,7 +319,8 @@ module SqlAlchemy {
DataFlow::Node getTextArg() { result in [this.getArg(0), this.getArgByName("text")] }
}
- class DefaultTextClauseConstruction extends TextClauseConstruction {
+ /** `TextClause` constructions from the `sqlalchemy` package. */
+ private class DefaultTextClauseConstruction extends TextClauseConstruction {
DefaultTextClauseConstruction() {
this = API::moduleImport("sqlalchemy").getMember("text").getACall()
or
From 9f590dbf2d3ed436bbdd24e475ae129fb0c5ff47 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 2 Sep 2021 16:04:25 +0200
Subject: [PATCH 098/459] Python: Fix `.expected`
After we now model `db.text()` calls from Flask-SQLAlchemy
---
.../SQLAlchemyTextClauseInjection.expected | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
index 36c86ead3d5..3c51294a467 100644
--- a/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
+++ b/python/ql/test/query-tests/Security/CWE-089-SQLAlchemyTextClauseInjection/SQLAlchemyTextClauseInjection.expected
@@ -9,6 +9,8 @@ edges
| test.py:23:15:23:22 | ControlFlowNode for username | test.py:46:46:46:53 | ControlFlowNode for username |
| test.py:23:15:23:22 | ControlFlowNode for username | test.py:47:47:47:54 | ControlFlowNode for username |
| test.py:23:15:23:22 | ControlFlowNode for username | test.py:48:52:48:59 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:50:18:50:25 | ControlFlowNode for username |
+| test.py:23:15:23:22 | ControlFlowNode for username | test.py:51:24:51:31 | ControlFlowNode for username |
nodes
| test.py:23:15:23:22 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
| test.py:27:28:27:87 | ControlFlowNode for Attribute() | semmle.label | ControlFlowNode for Attribute() |
@@ -21,6 +23,8 @@ nodes
| test.py:46:46:46:53 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
| test.py:47:47:47:54 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
| test.py:48:52:48:59 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:50:18:50:25 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
+| test.py:51:24:51:31 | ControlFlowNode for username | semmle.label | ControlFlowNode for username |
#select
| test.py:27:28:27:87 | ControlFlowNode for Attribute() | test.py:23:15:23:22 | ControlFlowNode for username | test.py:27:28:27:87 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
| test.py:31:50:31:72 | ControlFlowNode for Attribute() | test.py:23:15:23:22 | ControlFlowNode for username | test.py:31:50:31:72 | ControlFlowNode for Attribute() | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
@@ -32,3 +36,5 @@ nodes
| test.py:46:46:46:53 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:46:46:46:53 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
| test.py:47:47:47:54 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:47:47:47:54 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
| test.py:48:52:48:59 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:48:52:48:59 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:50:18:50:25 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:50:18:50:25 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
+| test.py:51:24:51:31 | ControlFlowNode for username | test.py:23:15:23:22 | ControlFlowNode for username | test.py:51:24:51:31 | ControlFlowNode for username | This SQLAlchemy TextClause depends on $@, which could lead to SQL injection. | test.py:23:15:23:22 | ControlFlowNode for username | a user-provided value |
From dbc95cadb4fba72d634ca64c6429a03b573d88bc Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Thu, 2 Sep 2021 14:39:51 +0100
Subject: [PATCH 099/459] language reference entry for non-extending subtypes
---
docs/codeql/ql-language-reference/types.rst | 55 ++++++++++++++++++++-
1 file changed, 54 insertions(+), 1 deletion(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index ae37230f05c..dcec60d099c 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -78,7 +78,7 @@ To define a class, you write:
#. The keyword ``class``.
#. The name of the class. This is an `identifier `_
starting with an uppercase letter.
-#. The types to extend.
+#. The base types that the class is derived from via `extends` and/or `instanceof`
#. The :ref:`body of the class `, enclosed in braces.
For example:
@@ -112,6 +112,8 @@ the :ref:`class domain type `). A class inherits all member predic
base types.
A class can extend multiple types. For more information, see ":ref:`multiple-inheritance`."
+Classes can also specialise other types without extending the class interface via `instanceof`,
+see ":ref:`instanceof-extensions`.".
To be valid, a class:
- Must not extend itself.
@@ -380,6 +382,57 @@ from ``OneTwoThree`` and ``int``.
must :ref:`override ` those definitions to avoid ambiguity.
:ref:`Super expressions ` are often useful in this situation.
+
+.. _instanceof-extensions:
+
+Non-extending subtypes
+======================
+
+Besides extending base types, classes can also declare `instanceof` relationships with other types.
+
+.. code-block:: ql
+ class Foo extends int {
+ Foo() { this in [1 .. 10] }
+
+ string foo_method() { result = "foo" }
+ }
+
+ class Bar extends int instanceof Foo {
+ string bar_method() { result = super.foo_method() }
+ }
+
+In this example, the characteristic predicate from `Foo` also applies to `Bar`.
+However, `foo_method` is not exposed in `Bar`, so the query `select any(Bar b).foo_method()`
+results in a compile time error. Note from the example that it is still possible to access
+methods from instanceof supertypes from within the specialising class with the `super` keyword.
+
+Crucially, the base class methods are not just hidden.
+Instead, the extension relationship is sewered.
+This has deep implications on method resolution when complex class hierarchies are involved.
+The following example demonstrates this.
+
+
+.. code-block:: ql
+ class Interface extends int {
+ Interface() { this in [1 .. 100] }
+ string foo() { result = "" }
+ }
+
+ class Foo extends Interface {
+ Foo() { this in [1 .. 10] }
+ override string foo() { result = "foo" }
+ }
+
+ class Bar extends Interface instanceof Foo {
+ override string foo() { result = "bar" }
+ }
+
+Here, the method `Bar::foo` does not override `Foo::foo`.
+Instead, it overrides only `Interface::foo`.
+This means that `select any(Foo b).foo()` yields only `foo`.
+Had `bar been defined as `extends Foo`, then `select any(Foo b).foo()` would yield `bar`.
+
+
.. _character-types:
.. _domain-types:
From 81a9ce2baa325a8a5a0f864e4cc3bd43835b9010 Mon Sep 17 00:00:00 2001
From: james
Date: Thu, 2 Sep 2021 16:40:29 +0100
Subject: [PATCH 100/459] polish text
---
.../analyzing-your-projects.rst | 2 ++
...g-data-flow-queries-using-partial-flow.rst | 29 +++++++++++++------
2 files changed, 22 insertions(+), 9 deletions(-)
diff --git a/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst b/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst
index b9a275cfb41..5ddd6850644 100644
--- a/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst
+++ b/docs/codeql/codeql-for-visual-studio-code/analyzing-your-projects.rst
@@ -79,6 +79,8 @@ You can see all quick queries that you've run in the current session in the Quer
Once you're happy with your quick query, you should save it in a QL pack so you can access it later. For more information, see ":ref:`About QL packs `."
+.. _running-a-specific-part-of-a-query-or-library:
+
Running a specific part of a query or library
----------------------------------------------
diff --git a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
index 9d5d83244a5..e812b4d29dc 100644
--- a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
@@ -24,7 +24,7 @@ A typical data-flow query looks like this:
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Sink is reached from $@.", source.getNode(), "here"
-Or slightly simpler without path explanations:
+The same query can be slightly simplified by rewriting it without :ref:`path explanations `:
.. code-block:: ql
@@ -38,25 +38,26 @@ You can try to debug the potential problem by following the steps described belo
Checking sources and sinks
--------------------------
-As a first step, make sure that the source and sink definitions contain what you expect. If one of these is empty then there can never be any data flow. The easiest way to verify this is using quick evaluation in CodeQL for VS Code: Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources.
+Initially, you should make sure that the ``source`` and ``sink`` definitions contain what you expect. If either the ``source`` or ``sink`` is empty then there can never be any data flow. The easiest way to check this is using quick evaluation in CodeQL for VS Code. Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources. For more information, see :ref:`Analyzing your projects <
+.. _running-a-specific-part-of-a-query-or-library>` in the CodeQL for VS Code help.
If both source and sink definitions look good then we will need to look for missing flow steps.
``fieldFlowBranchLimit``
------------------------
-Data-flow configurations contain a parameter called ``fieldFlowBranchLimit``. This is a slightly unfortunate, but currently necessary, performance trade-off, and a too low value can cause false negatives. It is worth a quick check to set this to a high value and see whether this causes the query to yield result. Try, for example, to add the following to your configuration:
+Data-flow configurations contain a parameter called ``fieldFlowBranchLimit``. If the value is set too high, you may experience performance degradation, but if it's too low you may miss results. When debugging data flow try setting ``fieldFlowBranchLimit`` to a high value and see whether your query generates more results. For example, try adding the following to your configuration:
.. code-block:: ql
override int fieldFlowBranchLimit() { result = 5000 }
-If there are still no results and performance did not degrade to complete uselessness, then it is best to leave this set to a high value while doing further debugging.
+If there are still no results and performance is still useable, then it is best to leave this set to a high value while doing further debugging.
Partial flow
------------
-A naive next step could be to try changing the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach makes sense and can work in some cases, you might find that it produces so many results that it's very hard to explore the findings, which can also dramatically affect query performance. More importantly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
+A naive next step could be to change the ``sink`` definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the ``source``\ s. While this approach may work in some cases, you might find that it produces so many results that it's very hard to explore the findings. It can can also dramatically affect query performance. More importantly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
To avoid these problems, a data-flow ``Configuration`` comes with a mechanism for exploring partial flow that tries to deal with these caveats. This is the ``Configuration.hasPartialFlow`` predicate:
@@ -87,9 +88,9 @@ As noted in the documentation for ``hasPartialFlow`` (for example, in the `CodeQ
This defines the exploration radius within which ``hasPartialFlow`` returns results.
-It is also generally useful to focus on a single source at a time as the starting point for the flow exploration. This is most easily done by adding some ad-hoc restriction in the ``isSource`` predicate.
+It is also useful to focus on a single ``source`` at a time as the starting point for the flow exploration. This is most easily done by adding a temporary restriction in the ``isSource`` predicate.
-To do quick ad-hoc evaluations of partial flow it is often easiest to add a predicate to the query that is solely intended for quick evaluation (right-click the predicate name and choose "CodeQL: Quick Evaluation"). A good starting point is something like:
+To do quick evaluations of partial flow it is often easiest to add a predicate to the query that is solely intended for quick evaluation (right-click the predicate name and choose "CodeQL: Quick Evaluation"). A good starting point is something like:
.. code-block:: ql
@@ -101,6 +102,16 @@ To do quick ad-hoc evaluations of partial flow it is often easiest to add a pred
)
}
-If you are focusing on a single source then the ``src`` column is of course superfluous, and you may of course also add other columns of interest based on ``n``, but including the enclosing callable and the distance to the source at the very least is generally recommended, as they can be useful columns to sort on to better inspect the results.
+If you are focusing on a single ``source`` then the ``src`` column is meaningless. You may of course also add other columns of interest based on ``n``, but including the enclosing callable and the distance to the source at the very least is generally recommended, as they can be useful columns to sort on to better inspect the results.
-A couple of advanced tips in order to focus the partial flow results: If flow travels a long distance following an expected path and the distance means that a lot of uninteresting flow gets included in the exploration radius then one can simply replace the source definition with a suitable node found along the way and restart the partial flow exploration from that point. Alternatively, creative use of barriers/sanitizers can be used to cut off flow paths that are uninteresting and thereby reduce the number of partial flow results to increase overview.
+
+If you see a large number of partial flow results, you can focus them in a couple of ways:
+
+- If flow travels a long distance following an expected path, that can result in a lot of uninteresting flow being included in the exploration radius. To reduce the amount of uninteresting flow, you can replace the ``source`` definition with a suitable ``node`` that appears along the path and restart the partial flow exploration from that point.
+- Creative use of barriers and sanitizers can be used to cut off flow paths that are uninteresting. This also reduces the number of partial flow results to explore while debugging.
+
+Further reading
+----------------
+
+- :ref:`About data flow analysis `
+- :ref:`Creating path queries `
\ No newline at end of file
From 2e995839bb45ea2b861f3851c45721470e9850a0 Mon Sep 17 00:00:00 2001
From: james
Date: Thu, 2 Sep 2021 16:46:23 +0100
Subject: [PATCH 101/459] fix link
---
.../debugging-data-flow-queries-using-partial-flow.rst | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
index e812b4d29dc..b661a68fcd1 100644
--- a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
@@ -38,8 +38,7 @@ You can try to debug the potential problem by following the steps described belo
Checking sources and sinks
--------------------------
-Initially, you should make sure that the ``source`` and ``sink`` definitions contain what you expect. If either the ``source`` or ``sink`` is empty then there can never be any data flow. The easiest way to check this is using quick evaluation in CodeQL for VS Code. Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources. For more information, see :ref:`Analyzing your projects <
-.. _running-a-specific-part-of-a-query-or-library>` in the CodeQL for VS Code help.
+Initially, you should make sure that the ``source`` and ``sink`` definitions contain what you expect. If either the ``source`` or ``sink`` is empty then there can never be any data flow. The easiest way to check this is using quick evaluation in CodeQL for VS Code. Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources. For more information, see :ref:`Analyzing your projects ` in the CodeQL for VS Code help.
If both source and sink definitions look good then we will need to look for missing flow steps.
From dbda1bf5c0277aedcc0903a277d28d010b653cd1 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Thu, 2 Sep 2021 17:30:36 +0100
Subject: [PATCH 102/459] Update docs/codeql/ql-language-reference/types.rst
Co-authored-by: Chris Smowton
---
docs/codeql/ql-language-reference/types.rst | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index dcec60d099c..784b8750862 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -406,8 +406,7 @@ However, `foo_method` is not exposed in `Bar`, so the query `select any(Bar b).f
results in a compile time error. Note from the example that it is still possible to access
methods from instanceof supertypes from within the specialising class with the `super` keyword.
-Crucially, the base class methods are not just hidden.
-Instead, the extension relationship is sewered.
+Crucially, the base class methods are not just hidden. The extension relationship is severed.
This has deep implications on method resolution when complex class hierarchies are involved.
The following example demonstrates this.
From ee13efbffd2098ab6796d2ec5c17030846bc6b86 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Thu, 2 Sep 2021 17:31:55 +0100
Subject: [PATCH 103/459] some whitesapce fixes
---
docs/codeql/ql-language-reference/types.rst | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index 784b8750862..1216454bcf2 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -382,7 +382,6 @@ from ``OneTwoThree`` and ``int``.
must :ref:`override ` those definitions to avoid ambiguity.
:ref:`Super expressions ` are often useful in this situation.
-
.. _instanceof-extensions:
Non-extending subtypes
@@ -391,6 +390,7 @@ Non-extending subtypes
Besides extending base types, classes can also declare `instanceof` relationships with other types.
.. code-block:: ql
+
class Foo extends int {
Foo() { this in [1 .. 10] }
@@ -410,8 +410,8 @@ Crucially, the base class methods are not just hidden. The extension relationshi
This has deep implications on method resolution when complex class hierarchies are involved.
The following example demonstrates this.
-
.. code-block:: ql
+
class Interface extends int {
Interface() { this in [1 .. 100] }
string foo() { result = "" }
@@ -431,7 +431,6 @@ Instead, it overrides only `Interface::foo`.
This means that `select any(Foo b).foo()` yields only `foo`.
Had `bar been defined as `extends Foo`, then `select any(Foo b).foo()` would yield `bar`.
-
.. _character-types:
.. _domain-types:
From 75d367a6c5cebae0b305b3c7c98964cf58e56f7c Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 1 Sep 2021 13:55:59 +0100
Subject: [PATCH 104/459] C++: Add ad-hoc SAMATE Juliet test cases (that were
previously internal). Directory structures cleaned up in a few places.
---
..._Path_Traversal__char_console_fopen_11.cpp | 164 +++++
.../CWE/CWE-022/SAMATE/TaintedPath.expected | 17 +
.../CWE/CWE-022/SAMATE/TaintedPath.qlref | 1 +
.../CWE/CWE-078/SAMATE/ExecTainted.expected | 1 +
.../CWE/CWE-078/SAMATE/ExecTainted.qlref | 1 +
.../Security/CWE/CWE-078/SAMATE/tests.cpp | 58 ++
.../UncontrolledProcessOperation.expected | 30 +
.../UncontrolledProcessOperation.qlref | 1 +
.../UncontrolledProcessOperation/test.cpp | 132 ++++
.../CWE-119/SAMATE/BadlyBoundedWrite.expected | 4 +
.../CWE-119/SAMATE/BadlyBoundedWrite.qlref | 1 +
.../SAMATE/OffsetUseBeforeRangeCheck.expected | 0
.../SAMATE/OffsetUseBeforeRangeCheck.qlref | 1 +
.../CWE-119/SAMATE/OverflowBuffer.expected | 19 +
.../CWE/CWE-119/SAMATE/OverflowBuffer.qlref | 1 +
.../SAMATE/OverflowDestination.expected | 0
.../CWE-119/SAMATE/OverflowDestination.qlref | 1 +
.../CWE-119/SAMATE/OverflowStatic.expected | 2 +
.../CWE/CWE-119/SAMATE/OverflowStatic.qlref | 1 +
.../CWE/CWE-119/SAMATE/OverrunWrite.expected | 0
.../CWE/CWE-119/SAMATE/OverrunWrite.qlref | 1 +
.../CWE-119/SAMATE/OverrunWriteFloat.expected | 0
.../CWE-119/SAMATE/OverrunWriteFloat.qlref | 1 +
.../SAMATE/StrncpyFlippedArgs.expected | 3 +
.../CWE-119/SAMATE/StrncpyFlippedArgs.qlref | 1 +
.../CWE-119/SAMATE/UnboundedWrite.expected | 3 +
.../CWE/CWE-119/SAMATE/UnboundedWrite.qlref | 1 +
.../Security/CWE/CWE-119/SAMATE/tests.cpp | 673 ++++++++++++++++++
...Based_Buffer_Overflow__c_CWE129_fgets_01.c | 65 ++
.../ImproperArrayIndexValidation.expected | 1 +
.../ImproperArrayIndexValidation.qlref | 1 +
.../connect_socket_w32_vsnprintf_01/test.c | 126 ++++
.../test.expected | 17 +
.../test.qlref | 1 +
.../CWE-134/SAMATE/console_fprintf_01/test.c | 161 +++++
.../SAMATE/console_fprintf_01/test.expected | 17 +
.../SAMATE/console_fprintf_01/test.qlref | 1 +
.../SAMATE/environment_fprintf_01/test.c | 136 ++++
.../environment_fprintf_01/test.expected | 17 +
.../SAMATE/environment_fprintf_01/test.qlref | 1 +
.../CWE-190/SAMATE/ArithmeticTainted.expected | 13 +
.../CWE-190/SAMATE/ArithmeticTainted.qlref | 1 +
.../SAMATE/ArithmeticUncontrolled.expected | 77 ++
.../SAMATE/ArithmeticUncontrolled.qlref | 1 +
.../ArithmeticWithExtremeValues.expected | 0
.../SAMATE/ArithmeticWithExtremeValues.qlref | 1 +
.../SAMATE/IntegerOverflowTainted.expected | 1 +
.../SAMATE/IntegerOverflowTainted.qlref | 1 +
.../Security/CWE/CWE-190/SAMATE/examples.cpp | 70 ++
.../SAMATE/IntegerOverflowTainted.expected | 1 +
.../SAMATE/IntegerOverflowTainted.qlref | 1 +
.../Security/CWE/CWE-197/SAMATE/tests.cpp | 74 ++
.../CWE-497/SAMATE/ExposedSystemData.expected | 1 +
.../CWE-497/SAMATE/ExposedSystemData.qlref | 1 +
.../CWE/CWE-497/SAMATE/OutputWrite.expected | 7 +
.../CWE/CWE-497/SAMATE/OutputWrite.ql | 4 +
.../Security/CWE/CWE-497/SAMATE/tests.c | 72 ++
.../DangerousUseOfCin.expected | 1 +
.../DangerousUseOfCin/DangerousUseOfCin.qlref | 1 +
.../CWE-676/SAMATE/DangerousUseOfCin/test.cpp | 69 ++
.../SAMATE/FileMayNotBeClosed.expected | 0
.../CWE-772/SAMATE/FileMayNotBeClosed.qlref | 1 +
.../CWE-772/SAMATE/FileNeverClosed.expected | 3 +
.../CWE/CWE-772/SAMATE/FileNeverClosed.qlref | 1 +
.../SAMATE/MemoryMayNotBeFreed.expected | 2 +
.../CWE-772/SAMATE/MemoryMayNotBeFreed.qlref | 1 +
.../CWE-772/SAMATE/MemoryNeverFreed.expected | 2 +
.../CWE/CWE-772/SAMATE/MemoryNeverFreed.qlref | 1 +
.../Security/CWE/CWE-772/SAMATE/tests.cpp | 333 +++++++++
69 files changed, 2403 insertions(+)
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.c
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.ql
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.qlref
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
new file mode 100644
index 00000000000..216258fd707
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
@@ -0,0 +1,164 @@
+/**
+ * This test case is closely based on CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
+ * from the SAMATE test suite.
+ */
+
+#define NULL (0)
+
+typedef size_t time_t;
+time_t time(time_t *timer);
+void srand(unsigned int seed);
+
+typedef struct {} FILE;
+extern FILE *stdin;
+FILE *fopen(const char *filename, const char *mode);
+int fclose(FILE *stream);
+#define FILENAME_MAX (4096)
+
+typedef unsigned long size_t;
+size_t strlen(const char *s);
+char *strcat(char *s1, const char *s2);
+char *fgets(char *s, int n, FILE *stream);
+
+int globalReturnsTrue()
+{
+ return 1;
+}
+
+int globalReturnsFalse()
+{
+ return 0;
+}
+
+void printLine(const char *str);
+
+#define BASEPATH "c:\\temp\\"
+#define FOPEN fopen
+
+namespace CWE23_Relative_Path_Traversal__char_console_fopen_11
+{
+
+void bad()
+{
+ char * data;
+ char dataBuffer[FILENAME_MAX] = BASEPATH;
+ data = dataBuffer;
+ if(globalReturnsTrue())
+ {
+ {
+ /* Read input from the console */
+ size_t dataLen = strlen(data);
+ /* if there is room in data, read into it from the console */
+ if (FILENAME_MAX-dataLen > 1)
+ {
+ /* POTENTIAL FLAW: Read data from the console */
+ if (fgets(data+dataLen, (int)(FILENAME_MAX-dataLen), stdin) != NULL)
+ {
+ /* The next few lines remove the carriage return from the string that is
+ * inserted by fgets() */
+ dataLen = strlen(data);
+ if (dataLen > 0 && data[dataLen-1] == '\n')
+ {
+ data[dataLen-1] = '\0';
+ }
+ }
+ else
+ {
+ printLine("fgets() failed");
+ /* Restore NUL terminator if fgets fails */
+ data[dataLen] = '\0';
+ }
+ }
+ }
+ }
+ {
+ FILE *pFile = NULL;
+ /* POTENTIAL FLAW: Possibly opening a file without validating the file name or path */
+ pFile = FOPEN(data, "wb+");
+ if (pFile != NULL)
+ {
+ fclose(pFile);
+ }
+ }
+}
+
+/* goodG2B1() - use goodsource and badsink by changing the globalReturnsTrue() to globalReturnsFalse() */
+static void goodG2B1()
+{
+ char * data;
+ char dataBuffer[FILENAME_MAX] = BASEPATH;
+ data = dataBuffer;
+ if(globalReturnsFalse())
+ {
+ /* INCIDENTAL: CWE 561 Dead Code, the code below will never run */
+ printLine("Benign, fixed string");
+ }
+ else
+ {
+ /* FIX: Use a fixed file name */
+ strcat(data, "file.txt");
+ }
+ {
+ FILE *pFile = NULL;
+ /* POTENTIAL FLAW: Possibly opening a file without validating the file name or path */
+ pFile = FOPEN(data, "wb+");
+ if (pFile != NULL)
+ {
+ fclose(pFile);
+ }
+ }
+}
+
+/* goodG2B2() - use goodsource and badsink by reversing the blocks in the if statement */
+static void goodG2B2()
+{
+ char * data;
+ char dataBuffer[FILENAME_MAX] = BASEPATH;
+ data = dataBuffer;
+ if(globalReturnsTrue())
+ {
+ /* FIX: Use a fixed file name */
+ strcat(data, "file.txt");
+ }
+ {
+ FILE *pFile = NULL;
+ /* POTENTIAL FLAW: Possibly opening a file without validating the file name or path */
+ pFile = FOPEN(data, "wb+");
+ if (pFile != NULL)
+ {
+ fclose(pFile);
+ }
+ }
+}
+
+void good()
+{
+ goodG2B1();
+ goodG2B2();
+}
+
+} /* close namespace */
+
+/* Below is the main(). It is only used when building this testcase on
+ its own for testing or for building a binary to use in testing binary
+ analysis tools. It is not used when compiling all the testcases as one
+ application, which is how source code analysis tools are tested. */
+
+using namespace CWE23_Relative_Path_Traversal__char_console_fopen_11; /* so that we can use good and bad easily */
+
+int main(int argc, char * argv[])
+{
+ /* seed randomness */
+ srand( (unsigned)time(NULL) );
+#ifndef OMITGOOD
+ printLine("Calling good()...");
+ good();
+ printLine("Finished good()");
+#endif /* OMITGOOD */
+#ifndef OMITBAD
+ printLine("Calling bad()...");
+ bad();
+ printLine("Finished bad()");
+#endif /* OMITBAD */
+ return 0;
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.expected
new file mode 100644
index 00000000000..ebd575376fd
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.expected
@@ -0,0 +1,17 @@
+edges
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | ... + ... | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | (const char *)... |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | ... + ... | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | ... + ... | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data indirection |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | fgets output argument | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | (const char *)... |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | fgets output argument | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | fgets output argument | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data indirection |
+nodes
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | ... + ... | semmle.label | ... + ... |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | fgets output argument | semmle.label | fgets output argument |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | (const char *)... | semmle.label | (const char *)... |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | (const char *)... | semmle.label | (const char *)... |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data | semmle.label | data |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data indirection | semmle.label | data indirection |
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data indirection | semmle.label | data indirection |
+#select
+| CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | ... + ... | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:77:23:77:26 | data | This argument to a file access function is derived from $@ and then passed to fopen(filename) | CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp:55:27:55:38 | ... + ... | user input (fgets) |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.qlref
new file mode 100644
index 00000000000..1677939387d
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-022/TaintedPath.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.expected
new file mode 100644
index 00000000000..7a7858134ab
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.expected
@@ -0,0 +1 @@
+| tests.cpp:55:16:55:19 | data | This argument to an OS command is derived from $@ and then passed to system(string) | tests.cpp:35:34:35:39 | call to getenv | user input (getenv) |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.qlref
new file mode 100644
index 00000000000..9fe2347270f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-078/ExecTainted.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
new file mode 100644
index 00000000000..4b9c1343f6c
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
@@ -0,0 +1,58 @@
+//semmle-extractor-options: --edg --target --edg win64
+
+// A selection of tests from the samate framework for rule CWE-78.
+
+// library types, functions etc
+#define NULL (0)
+typedef unsigned long size_t;
+size_t strlen(const char *s);
+char *strncat(char *s1, const char *s2, size_t n);n);
+char *getenv(const char *name);
+int system(const char *string);
+void exit(int status);
+
+#define FULL_COMMAND "dir "
+#define ENV_VARIABLE "ADD"
+#define GETENV getenv
+#define SYSTEM system
+
+void printLine(const char *str);
+
+// ----------
+
+/* The static variable below is used to drive control flow in the source function */
+static int badStatic = 0;
+
+static char * badSource(char * data)
+{
+ if(badStatic)
+ {
+ {
+ /* Append input from an environment variable to data */
+ size_t dataLen = strlen(data);
+ char * environment = GETENV(ENV_VARIABLE);
+ /* If there is data in the environment variable */
+ if (environment != NULL)
+ {
+ /* POTENTIAL FLAW: Read data from an environment variable */
+ strncat(data+dataLen, environment, 100-dataLen-1);
+ }
+ }
+ }
+ return data;
+}
+
+void CWE78_OS_Command_Injection__char_environment_system_21_bad()
+{
+ char * data;
+ char data_buf[100] = FULL_COMMAND;
+ data = data_buf;
+ badStatic = 1; /* true */
+ data = badSource(data);
+ /* POTENTIAL FLAW: Execute command in data possibly leading to command injection [NOT DETECTED] */
+ if (SYSTEM(data) != 0)
+ {
+ printLine("command execution failed!");
+ exit(1);
+ }
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected
new file mode 100644
index 00000000000..bcaff619bd2
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected
@@ -0,0 +1,30 @@
+edges
+| test.cpp:35:73:35:76 | *data | test.cpp:41:32:41:35 | (LPCSTR)... |
+| test.cpp:35:73:35:76 | *data | test.cpp:41:32:41:35 | data |
+| test.cpp:35:73:35:76 | *data | test.cpp:41:32:41:35 | data indirection |
+| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | (LPCSTR)... |
+| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | data |
+| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | data |
+| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | data indirection |
+| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:17:71:22 | data |
+| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:17:71:22 | data |
+| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:24:71:27 | data indirection |
+| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:24:71:27 | data indirection |
+| test.cpp:71:17:71:22 | data | test.cpp:35:73:35:76 | data |
+| test.cpp:71:24:71:27 | data indirection | test.cpp:35:73:35:76 | *data |
+nodes
+| test.cpp:35:73:35:76 | *data | semmle.label | *data |
+| test.cpp:35:73:35:76 | data | semmle.label | data |
+| test.cpp:41:32:41:35 | (LPCSTR)... | semmle.label | (LPCSTR)... |
+| test.cpp:41:32:41:35 | (LPCSTR)... | semmle.label | (LPCSTR)... |
+| test.cpp:41:32:41:35 | data | semmle.label | data |
+| test.cpp:41:32:41:35 | data | semmle.label | data |
+| test.cpp:41:32:41:35 | data | semmle.label | data |
+| test.cpp:41:32:41:35 | data indirection | semmle.label | data indirection |
+| test.cpp:41:32:41:35 | data indirection | semmle.label | data indirection |
+| test.cpp:62:30:62:35 | call to getenv | semmle.label | call to getenv |
+| test.cpp:62:30:62:35 | call to getenv | semmle.label | call to getenv |
+| test.cpp:71:17:71:22 | data | semmle.label | data |
+| test.cpp:71:24:71:27 | data indirection | semmle.label | data indirection |
+#select
+| test.cpp:41:32:41:35 | data | test.cpp:62:30:62:35 | call to getenv | test.cpp:41:32:41:35 | data | The value of this argument may come from $@ and is being passed to LoadLibraryA | test.cpp:62:30:62:35 | call to getenv | call to getenv |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.qlref
new file mode 100644
index 00000000000..a9ca1db5199
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-114/UncontrolledProcessOperation.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp
new file mode 100644
index 00000000000..cf1f98e1d53
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp
@@ -0,0 +1,132 @@
+// Samate test cases for CWE-114
+
+typedef unsigned long size_t;
+typedef unsigned int BOOL;
+typedef const char *LPCSTR;
+typedef void *HMODULE;
+#define NULL (0)
+
+size_t strlen(const char *s);
+char *strncat(char *s1, const char *s2, size_t n);
+
+HMODULE LoadLibraryA(LPCSTR libname);
+BOOL FreeLibrary(HMODULE hModule);
+
+char *getenv(const char *name);
+
+#define GETENV getenv
+#define ENV_VARIABLE "ADD"
+
+void printLine(const char *msg);
+
+// --- CWE114_Process_Control__w32_char_environment_82 ---
+
+class CWE114_Process_Control__w32_char_environment_82_base
+{
+public:
+ /* pure virtual function */
+ virtual void action(char * data) = 0;
+};
+
+class CWE114_Process_Control__w32_char_environment_82_bad : public CWE114_Process_Control__w32_char_environment_82_base
+{
+public:
+ void action(char * data);
+};
+
+void CWE114_Process_Control__w32_char_environment_82_bad::action(char * data)
+{
+ {
+ HMODULE hModule;
+ /* POTENTIAL FLAW: If the path to the library is not specified, an attacker may be able to
+ * replace his own file with the intended library */
+ hModule = LoadLibraryA(data);
+ if (hModule != NULL)
+ {
+ FreeLibrary(hModule);
+ printLine("Library loaded and freed successfully");
+ }
+ else
+ {
+ printLine("Unable to load library");
+ }
+ }
+}
+
+void bad()
+{
+ char * data;
+ char dataBuffer[100] = "";
+ data = dataBuffer;
+ {
+ /* Append input from an environment variable to data */
+ size_t dataLen = strlen(data);
+ char * environment = GETENV(ENV_VARIABLE);
+ /* If there is data in the environment variable */
+ if (environment != NULL)
+ {
+ /* POTENTIAL FLAW: Read data from an environment variable */
+ strncat(data+dataLen, environment, 100-dataLen-1);
+ }
+ }
+ CWE114_Process_Control__w32_char_environment_82_base* baseObject = new CWE114_Process_Control__w32_char_environment_82_bad;
+ baseObject->action(data);
+ delete baseObject;
+}
+
+// --- CWE114_Process_Control__w32_char_console_33 ---
+
+typedef struct {} FILE;
+char *fgets(char *s, int n, FILE *stream);
+FILE *stdin;
+
+void CWE114_Process_Control__w32_char_console_33_bad()
+{
+ char * data;
+ char * &dataRef = data;
+ char dataBuffer[100] = "";
+ data = dataBuffer;
+ {
+ /* Read input from the console */
+ size_t dataLen = strlen(data);
+ /* if there is room in data, read into it from the console */
+ if (100-dataLen > 1)
+ {
+ /* POTENTIAL FLAW: Read data from the console [NOT DETECTED] */
+ if (fgets(data+dataLen, (int)(100-dataLen), stdin) != NULL)
+ {
+ /* The next few lines remove the carriage return from the string that is
+ * inserted by fgets() */
+ dataLen = strlen(data);
+ if (dataLen > 0 && data[dataLen-1] == '\n')
+ {
+ data[dataLen-1] = '\0';
+ }
+ }
+ else
+ {
+ printLine("fgets() failed");
+ /* Restore NUL terminator if fgets fails */
+ data[dataLen] = '\0';
+ }
+ }
+ }
+ {
+ char * data = dataRef;
+ {
+ HMODULE hModule;
+ /* POTENTIAL FLAW: If the path to the library is not specified, an attacker may be able to
+ * replace his own file with the intended library */
+ hModule = LoadLibraryA(data);
+ if (hModule != NULL)
+ {
+ FreeLibrary(hModule);
+ printLine("Library loaded and freed successfully");
+ }
+ else
+ {
+ printLine("Unable to load library");
+ }
+ }
+ }
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected
new file mode 100644
index 00000000000..bd96138bb9f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected
@@ -0,0 +1,4 @@
+| tests.cpp:352:13:352:19 | call to strncat | This 'call to strncat' operation is limited to 100 bytes but the destination is only 50 bytes. |
+| tests.cpp:454:9:454:15 | call to wcsncpy | This 'call to wcsncpy' operation is limited to 198 bytes but the destination is only 100 bytes. |
+| tests.cpp:483:9:483:16 | call to swprintf | This 'call to swprintf' operation is limited to 200 bytes but the destination is only 100 bytes. |
+| tests.cpp:632:13:632:20 | call to swprintf | This 'call to swprintf' operation is limited to 200 bytes but the destination is only 100 bytes. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.qlref
new file mode 100644
index 00000000000..9636c74d0a8
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-120/BadlyBoundedWrite.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.qlref
new file mode 100644
index 00000000000..d934901f174
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OffsetUseBeforeRangeCheck.qlref
@@ -0,0 +1 @@
+Best Practices/Likely Errors/OffsetUseBeforeRangeCheck.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected
new file mode 100644
index 00000000000..3621eb1d127
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected
@@ -0,0 +1,19 @@
+| tests.cpp:47:9:47:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:34:10:34:18 | charFirst | destination buffer |
+| tests.cpp:62:9:62:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:34:10:34:18 | charFirst | destination buffer |
+| tests.cpp:173:9:173:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:166:20:166:25 | call to malloc | destination buffer |
+| tests.cpp:174:9:174:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:166:20:166:25 | call to malloc | array |
+| tests.cpp:194:9:194:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:183:10:183:22 | dataBadBuffer | destination buffer |
+| tests.cpp:194:9:194:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:187:12:187:24 | dataBadBuffer | destination buffer |
+| tests.cpp:195:9:195:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:183:10:183:22 | dataBadBuffer | array |
+| tests.cpp:195:9:195:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:187:12:187:24 | dataBadBuffer | array |
+| tests.cpp:214:9:214:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:203:36:203:41 | call to alloca | destination buffer |
+| tests.cpp:214:9:214:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:207:12:207:24 | dataBadBuffer | destination buffer |
+| tests.cpp:215:9:215:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:203:36:203:41 | call to alloca | array |
+| tests.cpp:215:9:215:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:207:12:207:24 | dataBadBuffer | array |
+| tests.cpp:239:9:239:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:223:36:223:41 | call to alloca | array |
+| tests.cpp:239:9:239:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:227:12:227:24 | dataBadBuffer | array |
+| tests.cpp:263:9:263:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:247:10:247:22 | dataBadBuffer | array |
+| tests.cpp:263:9:263:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:251:12:251:24 | dataBadBuffer | array |
+| tests.cpp:386:9:386:14 | call to memcpy | This 'memcpy' operation accesses 40 bytes but the $@ is only 10 bytes. | tests.cpp:382:19:382:24 | call to alloca | destination buffer |
+| tests.cpp:436:9:436:19 | access to array | This array indexing operation accesses byte offset 199 but the $@ is only 100 bytes. | tests.cpp:424:12:424:26 | new[] | array |
+| tests.cpp:455:9:455:19 | access to array | This array indexing operation accesses byte offset 199 but the $@ is only 100 bytes. | tests.cpp:447:12:447:26 | new[] | array |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.qlref
new file mode 100644
index 00000000000..5c2bacec579
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-119/OverflowBuffer.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.qlref
new file mode 100644
index 00000000000..a4213e22fcd
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowDestination.qlref
@@ -0,0 +1 @@
+Critical/OverflowDestination.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected
new file mode 100644
index 00000000000..e9cf380d0e6
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected
@@ -0,0 +1,2 @@
+| tests.cpp:47:51:47:72 | sizeof() | Potential buffer-overflow: 'charFirst' has size 16 not 32. |
+| tests.cpp:62:52:62:74 | sizeof() | Potential buffer-overflow: 'charFirst' has size 16 not 32. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.qlref
new file mode 100644
index 00000000000..9ff1c3b33dc
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.qlref
@@ -0,0 +1 @@
+Critical/OverflowStatic.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.qlref
new file mode 100644
index 00000000000..f6c962c1a7b
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWrite.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-120/OverrunWrite.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.qlref
new file mode 100644
index 00000000000..757d1592e83
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverrunWriteFloat.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-120/OverrunWriteFloat.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected
new file mode 100644
index 00000000000..b376553baee
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected
@@ -0,0 +1,3 @@
+| tests.cpp:292:13:292:19 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
+| tests.cpp:308:4:308:10 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
+| tests.cpp:454:9:454:15 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.qlref
new file mode 100644
index 00000000000..bf0bf1ea7d0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.qlref
@@ -0,0 +1 @@
+Likely Bugs/Memory Management/StrncpyFlippedArgs.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.expected
new file mode 100644
index 00000000000..58e3dda0964
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.expected
@@ -0,0 +1,3 @@
+edges
+nodes
+#select
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.qlref
new file mode 100644
index 00000000000..767f2ea4db9
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/UnboundedWrite.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-120/UnboundedWrite.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
new file mode 100644
index 00000000000..364ef56b331
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
@@ -0,0 +1,673 @@
+//semmle-extractor-options: --edg --target --edg win64
+
+// A sample of tests from the samate framework for rule CWE-119.
+
+// library types, functions etc
+typedef unsigned long size_t;
+void *malloc(size_t size);
+void *alloca(size_t size);
+void free(void *ptr);
+#define ALLOCA alloca
+
+void *memcpy(void *s1, const void *s2, size_t n);
+void *memset(void *s, int c, size_t n);
+char *strcpy(char *s1, const char *s2);
+size_t strlen(const char *s);
+
+void exit(int status);
+
+typedef unsigned int DWORD;
+DWORD GetCurrentDirectoryA(DWORD bufferLength, char *buffer);
+bool PathAppendA(char *path, const char *more);
+#define MAX_PATH 4096
+
+void printLine(const char *str);
+void printSizeTLine(size_t val);
+void printIntLine(int val);
+
+// ----------
+
+#define SRC_STR "0123456789abcde0123"
+
+typedef struct _charVoid
+{
+ char charFirst[16];
+ void * voidSecond;
+ void * voidThird;
+} charVoid;
+
+void CWE121_Stack_Based_Buffer_Overflow__char_type_overrun_memcpy_01_bad()
+{
+ {
+ charVoid structCharVoid;
+ structCharVoid.voidSecond = (void *)SRC_STR;
+ /* Print the initial block pointed to by structCharVoid.voidSecond */
+ printLine((char *)structCharVoid.voidSecond);
+ /* FLAW: Use the sizeof(structCharVoid) which will overwrite the pointer voidSecond */
+ memcpy(structCharVoid.charFirst, SRC_STR, sizeof(structCharVoid));
+ structCharVoid.charFirst[(sizeof(structCharVoid.charFirst)/sizeof(char))-1] = '\0'; /* null terminate the string */
+ printLine((char *)structCharVoid.charFirst);
+ printLine((char *)structCharVoid.voidSecond);
+ }
+}
+
+void CWE122_Heap_Based_Buffer_Overflow__char_type_overrun_memcpy_01_bad()
+{
+ {
+ charVoid * structCharVoid = (charVoid *)malloc(sizeof(charVoid));
+ structCharVoid->voidSecond = (void *)SRC_STR;
+ /* Print the initial block pointed to by structCharVoid->voidSecond */
+ printLine((char *)structCharVoid->voidSecond);
+ /* FLAW: Use the sizeof(*structCharVoid) which will overwrite the pointer y */
+ memcpy(structCharVoid->charFirst, SRC_STR, sizeof(*structCharVoid));
+ structCharVoid->charFirst[(sizeof(structCharVoid->charFirst)/sizeof(char))-1] = '\0'; /* null terminate the string */
+ printLine((char *)structCharVoid->charFirst);
+ printLine((char *)structCharVoid->voidSecond);
+ free(structCharVoid);
+ }
+}
+
+void CWE124_Buffer_Underwrite__char_alloca_cpy_01_bad()
+{
+ char * data;
+ char * dataBuffer = (char *)ALLOCA(100*sizeof(char));
+ memset(dataBuffer, 'A', 100-1);
+ dataBuffer[100-1] = '\0';
+ /* FLAW: Set data pointer to before the allocated memory buffer */
+ data = dataBuffer - 8;
+ {
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possibly copying data to memory before the destination buffer */
+ strcpy(data, source); // [NOT DETECTED]
+ printLine(data);
+ }
+}
+
+void CWE126_Buffer_Overread__char_alloca_loop_01_bad()
+{
+ char * data;
+ char * dataBadBuffer = (char *)ALLOCA(50*sizeof(char));
+ char * dataGoodBuffer = (char *)ALLOCA(100*sizeof(char));
+ memset(dataBadBuffer, 'A', 50-1); /* fill with 'A's */
+ dataBadBuffer[50-1] = '\0'; /* null terminate */
+ memset(dataGoodBuffer, 'A', 100-1); /* fill with 'A's */
+ dataGoodBuffer[100-1] = '\0'; /* null terminate */
+ /* FLAW: Set data pointer to a small buffer */
+ data = dataBadBuffer;
+ {
+ size_t i, destLen;
+ char dest[100];
+ memset(dest, 'C', 100-1);
+ dest[100-1] = '\0'; /* null terminate */
+ destLen = strlen(dest);
+ /* POTENTIAL FLAW: using length of the dest where data
+ * could be smaller than dest causing buffer overread */
+ for (i = 0; i < destLen; i++)
+ {
+ dest[i] = data[i]; // [NOT DETECTED]
+ }
+ dest[100-1] = '\0';
+ printLine(dest);
+ }
+}
+
+void CWE127_Buffer_Underread__char_alloca_cpy_01_bad()
+{
+ char * data;
+ char * dataBuffer = (char *)ALLOCA(100*sizeof(char));
+ memset(dataBuffer, 'A', 100-1);
+ dataBuffer[100-1] = '\0';
+ /* FLAW: Set data pointer to before the allocated memory buffer */
+ data = dataBuffer - 8;
+ {
+ char dest[100*2];
+ memset(dest, 'C', 100*2-1); /* fill with 'C's */
+ dest[100*2-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possibly copy from a memory location located before the source buffer */
+ strcpy(dest, data); // [NOT DETECTED]
+ printLine(dest);
+ }
+}
+
+#define BAD_PATH_SIZE (MAX_PATH / 2) /* maintenance note: must be < MAX_PATH in order for 'bad' to be 'bad' */
+
+void CWE785_Path_Manipulation_Function_Without_Max_Sized_Buffer__w32_01_bad()
+{
+ {
+ char path[BAD_PATH_SIZE];
+ DWORD length;
+ length = GetCurrentDirectoryA(BAD_PATH_SIZE, path);
+ if (length == 0 || length >= BAD_PATH_SIZE) /* failure conditions for this API call */
+ {
+ exit(1);
+ }
+ /* FLAW: PathAppend assumes the 'path' parameter is MAX_PATH */
+ /* INCIDENTAL: CWE 121 stack based buffer overflow, which is intrinsic to
+ * this example identified on the CWE webpage */
+ if (!PathAppendA(path, "AAAAAAAAAAAA")) // [NOT DETECTED]
+ {
+ exit(1);
+ }
+ printSizeTLine(strlen(path));
+ printIntLine(BAD_PATH_SIZE);
+ printLine(path);
+ }
+}
+
+#define NULL (0)
+
+void CWE122_Heap_Based_Buffer_Overflow__c_CWE805_char_memcpy_01_bad()
+{
+ char * data;
+ data = NULL;
+ /* FLAW: Allocate and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = (char *)malloc(50*sizeof(char));
+ data[0] = '\0'; /* null terminate */
+ {
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than data */
+ memcpy(data, source, 100*sizeof(char));
+ data[100-1] = '\0'; /* Ensure the destination buffer is null terminated */
+ printLine(data);
+ free(data);
+ }
+}
+
+void CWE121_Stack_Based_Buffer_Overflow__CWE805_char_declare_memcpy_01_bad()
+{
+ char * data;
+ char dataBadBuffer[50];
+ char dataGoodBuffer[100];
+ /* FLAW: Set a pointer to a "small" buffer. This buffer will be used in the sinks as a destination
+ * buffer in various memory copying functions using a "large" source buffer. */
+ data = dataBadBuffer;
+ data[0] = '\0'; /* null terminate */
+ {
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if the size of data is less than the length of source */
+ memcpy(data, source, 100*sizeof(char));
+ data[100-1] = '\0'; /* Ensure the destination buffer is null terminated */
+ printLine(data);
+ }
+}
+
+void CWE121_Stack_Based_Buffer_Overflow__CWE805_char_alloca_memcpy_01_bad()
+{
+ char * data;
+ char * dataBadBuffer = (char *)ALLOCA(50*sizeof(char));
+ char * dataGoodBuffer = (char *)ALLOCA(100*sizeof(char));
+ /* FLAW: Set a pointer to a "small" buffer. This buffer will be used in the sinks as a destination
+ * buffer in various memory copying functions using a "large" source buffer. */
+ data = dataBadBuffer;
+ data[0] = '\0'; /* null terminate */
+ {
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if the size of data is less than the length of source */
+ memcpy(data, source, 100*sizeof(char));
+ data[100-1] = '\0'; /* Ensure the destination buffer is null terminated */
+ printLine(data);
+ }
+}
+
+void CWE121_Stack_Based_Buffer_Overflow__CWE805_char_alloca_loop_01_bad()
+{
+ char * data;
+ char * dataBadBuffer = (char *)ALLOCA(50*sizeof(char));
+ char * dataGoodBuffer = (char *)ALLOCA(100*sizeof(char));
+ /* FLAW: Set a pointer to a "small" buffer. This buffer will be used in the sinks as a destination
+ * buffer in various memory copying functions using a "large" source buffer. */
+ data = dataBadBuffer;
+ data[0] = '\0'; /* null terminate */
+ {
+ size_t i;
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if the size of data is less than the length of source */
+ for (i = 0; i < 100; i++)
+ {
+ data[i] = source[i];
+ }
+ data[100-1] = '\0'; /* Ensure the destination buffer is null terminated */
+ printLine(data);
+ }
+}
+
+void CWE121_Stack_Based_Buffer_Overflow__CWE805_char_declare_loop_01_bad()
+{
+ char * data;
+ char dataBadBuffer[50];
+ char dataGoodBuffer[100];
+ /* FLAW: Set a pointer to a "small" buffer. This buffer will be used in the sinks as a destination
+ * buffer in various memory copying functions using a "large" source buffer. */
+ data = dataBadBuffer;
+ data[0] = '\0'; /* null terminate */
+ {
+ size_t i;
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if the size of data is less than the length of source */
+ for (i = 0; i < 100; i++)
+ {
+ data[i] = source[i];
+ }
+ data[100-1] = '\0'; /* Ensure the destination buffer is null terminated */
+ printLine(data);
+ }
+}
+
+wchar_t *wcsncpy(wchar_t *destination, const wchar_t *source, size_t num);
+size_t wcslen(const wchar_t *str);
+char *strcat(char *destination, const char *source);
+char *strncat(char *destination, const char *source, size_t num);
+
+void *memmove(void *destination, const void *source, size_t num);
+
+void printWLine(const wchar_t *line);
+
+/* MAINTENANCE NOTE: The length of this string should equal the 10 */
+#define SRC_STRING L"AAAAAAAAAA"
+
+namespace CWE122_Heap_Based_Buffer_Overflow__cpp_CWE193_wchar_t_ncpy_01
+{
+ void bad()
+ {
+ wchar_t * data;
+ data = NULL;
+ /* FLAW: Did not leave space for a null terminator */
+ data = new wchar_t[10];
+ {
+ wchar_t source[10+1] = SRC_STRING;
+ /* Copy length + 1 to include NUL terminator from source */
+ /* POTENTIAL FLAW: data may not have enough space to hold source */
+ wcsncpy(data, source, wcslen(source) + 1);
+ printWLine(data);
+ delete [] data;
+ }
+ }
+
+ static void goodG2B()
+ {
+ wchar_t * data;
+ data = NULL;
+ /* FIX: Allocate space for a null terminator */
+ data = new wchar_t[10+1];
+ {
+ wchar_t source[10+1] = SRC_STRING;
+ /* Copy length + 1 to include NUL terminator from source */
+ /* POTENTIAL FLAW: data may not have enough space to hold source */
+ wcsncpy(data, source, wcslen(source) + 1); // [FALSE POSITIVE RESULT] (debatable)
+ printWLine(data);
+ delete [] data;
+ }
+ }
+} /* close namespace */
+
+namespace CWE122_Heap_Based_Buffer_Overflow__cpp_CWE193_wchar_t_memmove_31
+{
+ void bad()
+ {
+ wchar_t * data;
+ data = NULL;
+ /* FLAW: Did not leave space for a null terminator */
+ data = new wchar_t[10];
+ {
+ wchar_t * dataCopy = data;
+ wchar_t * data = dataCopy;
+ {
+ wchar_t source[10+1] = SRC_STRING;
+ /* Copy length + 1 to include NUL terminator from source */
+ /* POTENTIAL FLAW: data may not have enough space to hold source */
+ memmove(data, source, (wcslen(source) + 1) * sizeof(wchar_t)); // [NOT DETECTED]
+ printWLine(data);
+ delete [] data;
+ }
+ }
+ }
+} /* close namespace */
+
+namespace CWE122_Heap_Based_Buffer_Overflow__cpp_CWE805_char_ncat_01
+{
+ void bad()
+ {
+ char * data;
+ data = NULL;
+ /* FLAW: Allocate using new[] and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = new char[50];
+ data[0] = '\0'; /* null terminate */
+ {
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than sizeof(data)-strlen(data) */
+ strncat(data, source, 100);
+ printLine(data);
+ delete [] data;
+ }
+ }
+} /* close namespace */
+
+void CWE122_Heap_Based_Buffer_Overflow__c_dest_char_cat_01_bad()
+{
+ char * data;
+ data = NULL;
+ /* FLAW: Allocate and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = (char *)malloc(50*sizeof(char));
+ data[0] = '\0'; /* null terminate */
+ {
+ char source[100];
+ memset(source, 'C', 100-1); /* fill with 'C's */
+ source[100-1] = '\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than sizeof(data)-strlen(data) */
+ strcat(data, source); // [NOT DETECTED]
+ printLine(data);
+ free(data);
+ }
+}
+
+void CWE121_Stack_Based_Buffer_Overflow__CWE131_memcpy_01_bad()
+{
+ int * data;
+ data = NULL;
+ /* FLAW: Allocate memory without using sizeof(int) */
+ data = (int *)ALLOCA(10);
+ {
+ int source[10] = {0};
+ /* POTENTIAL FLAW: Possible buffer overflow if data was not allocated correctly in the source */
+ memcpy(data, source, 10*sizeof(int));
+ printIntLine(data[0]);
+ }
+}
+
+typedef long long int64_t;
+wchar_t *wmemset(wchar_t *dest, wchar_t c, size_t count);
+void* calloc(size_t num, size_t size);
+
+void printLongLongLine(int64_t longLongIntNumber);
+void printDoubleLine(double doubleNumber);
+
+void CWE122_Heap_Based_Buffer_Overflow__cpp_CWE805_int64_t_loop_01_bad()
+{
+ int64_t * data;
+ data = NULL;
+ /* FLAW: Allocate using new[] and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = new int64_t[50];
+ {
+ int64_t source[100] = {0}; /* fill with 0's */
+ {
+ size_t i;
+ /* POTENTIAL FLAW: Possible buffer overflow if data < 100 */
+ for (i = 0; i < 100; i++)
+ {
+ data[i] = source[i]; // [NOT DETECTED]
+ }
+ printLongLongLine(data[0]);
+ delete [] data;
+ }
+ }
+}
+
+void CWE122_Heap_Based_Buffer_Overflow__cpp_CWE805_wchar_t_loop_01_bad()
+{
+ wchar_t * data;
+ data = NULL;
+ /* FLAW: Allocate using new[] and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = new wchar_t[50];
+ data[0] = L'\0'; /* null terminate */
+ {
+ size_t i;
+ wchar_t source[100];
+ wmemset(source, L'C', 100-1); /* fill with L'C's */
+ source[100-1] = L'\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than data */
+ for (i = 0; i < 100; i++)
+ {
+ data[i] = source[i];
+ }
+ data[100-1] = L'\0'; /* Ensure the destination buffer is null terminated */
+ printWLine(data);
+ delete [] data;
+ }
+}
+
+void CWE122_Heap_Based_Buffer_Overflow__cpp_CWE805_wchar_t_ncpy_01_bad()
+{
+ wchar_t * data;
+ data = NULL;
+ /* FLAW: Allocate using new[] and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = new wchar_t[50];
+ data[0] = L'\0'; /* null terminate */
+ {
+ wchar_t source[100];
+ wmemset(source, L'C', 100-1); /* fill with L'C's */
+ source[100-1] = L'\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than data */
+ wcsncpy(data, source, 100-1);
+ data[100-1] = L'\0'; /* Ensure the destination buffer is null terminated */
+ printWLine(data);
+ delete [] data;
+ }
+}
+
+#ifdef _WIN32
+int _snwprintf(wchar_t *buffer, size_t count, const wchar_t *format, ...);
+#define SNPRINTF _snwprintf
+#else
+int snprintf(char *s, size_t n, const char *format, ...);
+int swprintf(wchar_t *wcs, size_t maxlen, const wchar_t *format, ...);
+//#define SNPRINTF snprintf --- original code; using snprintf appears to be a mistake in samate?
+#define SNPRINTF swprintf
+#endif
+
+void CWE122_Heap_Based_Buffer_Overflow__cpp_CWE805_wchar_t_snprintf_01_bad()
+{
+ wchar_t * data;
+ data = NULL;
+ /* FLAW: Allocate using new[] and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = new wchar_t[50];
+ data[0] = L'\0'; /* null terminate */
+ {
+ wchar_t source[100];
+ wmemset(source, L'C', 100-1); /* fill with L'C's */
+ source[100-1] = L'\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than data */
+ SNPRINTF(data, 100, L"%s", source);
+ printWLine(data);
+ delete [] data;
+ }
+}
+
+/* classes used in some test cases as a custom type */
+class TwoIntsClass
+{
+ public: // Needed to access variables from label files
+ int intOne;
+ int intTwo;
+};
+
+class OneIntClass
+{
+ public: // Needed to access variables from label files
+ int intOne;
+};
+
+void *operator new(size_t size, void *ptr) throw(); // placement new (from #include )
+
+void CWE122_Heap_Based_Buffer_Overflow__placement_new_01_bad()
+{
+ char * data;
+ char * dataBadBuffer = (char *)malloc(sizeof(OneIntClass));
+ char * dataGoodBuffer = (char *)malloc(sizeof(TwoIntsClass));
+ /* POTENTIAL FLAW: Initialize data to a buffer small than the sizeof(TwoIntsClass) */
+ data = dataBadBuffer;
+ {
+ /* The Visual C++ compiler generates a warning if you initialize the class with ().
+ * This will cause the compile to default-initialize the object.
+ * See http://msdn.microsoft.com/en-us/library/wewb47ee%28v=VS.100%29.aspx
+ */
+ /* POTENTIAL FLAW: data may not be large enough to hold a TwoIntsClass */
+ TwoIntsClass * classTwo = new(data) TwoIntsClass; // [NOT DETECTED]
+ /* Initialize and make use of the class */
+ classTwo->intOne = 5;
+ classTwo->intTwo = 10; /* POTENTIAL FLAW: If sizeof(data) < sizeof(TwoIntsClass) then this line will be a buffer overflow */
+ printIntLine(classTwo->intOne);
+ /* skip printing classTwo->intTwo since that could be a buffer overread */
+ free(data);
+ }
+}
+
+void CWE122_Heap_Based_Buffer_Overflow__sizeof_double_01_bad()
+{
+ double * data;
+ /* Initialize data */
+ data = NULL;
+ /* INCIDENTAL: CWE-467 (Use of sizeof() on a pointer type) */
+ /* FLAW: Using sizeof the pointer and not the data type in malloc() */
+ data = (double *)malloc(sizeof(data)); // [NOT DETECTED]
+ *data = 1.7E300;
+ /* POTENTIAL FLAW: Attempt to use data, which may not have enough memory allocated */
+ printDoubleLine(*data);
+ free(data);
+}
+
+int rand(void);
+#define RAND32() ((rand()<<30) ^ (rand()<<15) ^ rand())
+
+void CWE122_Heap_Based_Buffer_Overflow__c_CWE129_rand_01_bad()
+{
+ int data;
+ /* Initialize data */
+ data = -1;
+ /* POTENTIAL FLAW: Set data to a random value */
+ data = RAND32();
+ {
+ int i;
+ int * buffer = (int *)malloc(10 * sizeof(int));
+ /* initialize buffer */
+ for (i = 0; i < 10; i++)
+ {
+ buffer[i] = 0;
+ }
+ /* POTENTIAL FLAW: Attempt to write to an index of the array that is above the upper bound
+ * This code does check to see if the array index is negative */
+ if (data >= 0)
+ {
+ buffer[data] = 1; // [NOT DETECTED]
+ /* Print the array values */
+ for(i = 0; i < 10; i++)
+ {
+ printIntLine(buffer[i]);
+ }
+ }
+ else
+ {
+ printLine("ERROR: Array index is negative.");
+ }
+ free(buffer);
+ }
+}
+
+typedef struct FILE;
+int fscanf(FILE *stream, const char *format, ...);
+FILE *stdin;
+
+void CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fscanf_01_bad()
+{
+ int data;
+ /* Initialize data */
+ data = -1;
+ /* POTENTIAL FLAW: Read data from the console using fscanf() */
+ fscanf(stdin, "%d", &data);
+ {
+ int i;
+ int * buffer = (int *)malloc(10 * sizeof(int));
+ /* initialize buffer */
+ for (i = 0; i < 10; i++)
+ {
+ buffer[i] = 0;
+ }
+ /* POTENTIAL FLAW: Attempt to write to an index of the array that is above the upper bound
+ * This code does check to see if the array index is negative */
+ if (data >= 0)
+ {
+ buffer[data] = 1; // [NOT DETECTED]
+ /* Print the array values */
+ for(i = 0; i < 10; i++)
+ {
+ printIntLine(buffer[i]);
+ }
+ }
+ else
+ {
+ printLine("ERROR: Array index is negative.");
+ }
+ free(buffer);
+ }
+}
+
+void CWE122_Heap_Based_Buffer_Overflow__cpp_CWE805_wchar_t_snprintf_31_bad()
+{
+ wchar_t * data;
+ data = NULL;
+ /* FLAW: Allocate using new[] and point data to a small buffer that is smaller than the large buffer used in the sinks */
+ data = new wchar_t[50];
+ data[0] = L'\0'; /* null terminate */
+ {
+ wchar_t * dataCopy = data;
+ wchar_t * data = dataCopy;
+ {
+ wchar_t source[100];
+ wmemset(source, L'C', 100-1); /* fill with L'C's */
+ source[100-1] = L'\0'; /* null terminate */
+ /* POTENTIAL FLAW: Possible buffer overflow if source is larger than data */
+ SNPRINTF(data, 100, L"%s", source);
+ printWLine(data);
+ delete [] data;
+ }
+ }
+}
+
+int rand(void);
+
+int globalReturnsTrueOrFalse()
+{
+ return (rand() % 2);
+}
+
+#define SRC_STRING "AAAAAAAAAA"
+
+void CWE121_Stack_Based_Buffer_Overflow__CWE193_char_declare_cpy_12_bad()
+{
+ char * data;
+ char dataBadBuffer[10];
+ char dataGoodBuffer[10+1];
+ if(globalReturnsTrueOrFalse())
+ {
+ /* FLAW: Set a pointer to a buffer that does not leave room for a NULL terminator when performing
+ * string copies in the sinks */
+ data = dataBadBuffer;
+ data[0] = '\0'; /* null terminate */
+ }
+ else
+ {
+ /* FIX: Set a pointer to a buffer that leaves room for a NULL terminator when performing
+ * string copies in the sinks */
+ data = dataGoodBuffer;
+ data[0] = '\0'; /* null terminate */
+ }
+ {
+ char source[10+1] = SRC_STRING;
+ /* POTENTIAL FLAW: data may not have enough space to hold source */ // [NOT DETECTED]
+ strcpy(data, source);
+ printLine(data);
+ }
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
new file mode 100644
index 00000000000..911e9987472
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
@@ -0,0 +1,65 @@
+// Snippet from an external (samate) test case for rule CWE-122 / CWE-129
+// CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
+
+typedef unsigned long size_t;
+void *malloc(size_t size);
+void free(void *ptr);
+#define NULL (0)
+
+typedef struct {} FILE;
+FILE *stdin;
+char *fgets(char *s, int n, FILE *stream);
+
+int atoi(const char *nptr);
+
+void printLine(const char *str);
+void printIntLine(int val);
+
+// ---
+
+#define CHAR_ARRAY_SIZE (64)
+
+void CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01_bad()
+{
+ int data;
+ /* Initialize data */
+ data = -1;
+ {
+ char inputBuffer[CHAR_ARRAY_SIZE] = "";
+ /* POTENTIAL FLAW: Read data from the console using fgets() */
+ if (fgets(inputBuffer, CHAR_ARRAY_SIZE, stdin) != NULL)
+ {
+ /* Convert to int */
+ data = atoi(inputBuffer);
+ }
+ else
+ {
+ printLine("fgets() failed.");
+ }
+ }
+ {
+ int i;
+ int * buffer = (int *)malloc(10 * sizeof(int));
+ /* initialize buffer */
+ for (i = 0; i < 10; i++)
+ {
+ buffer[i] = 0;
+ }
+ /* POTENTIAL FLAW: Attempt to write to an index of the array that is above the upper bound
+ * This code does check to see if the array index is negative */
+ if (data >= 0)
+ {
+ buffer[data] = 1;
+ /* Print the array values */
+ for(i = 0; i < 10; i++)
+ {
+ printIntLine(buffer[i]);
+ }
+ }
+ else
+ {
+ printLine("ERROR: Array index is negative.");
+ }
+ free(buffer);
+ }
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected
new file mode 100644
index 00000000000..357b583436b
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected
@@ -0,0 +1 @@
+| CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c:51:20:51:23 | data | $@ flows to here and is used in an array indexing expression, potentially causing an invalid access. | CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c:29:19:29:29 | inputBuffer | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.qlref
new file mode 100644
index 00000000000..f1d46d8f8d6
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-129/ImproperArrayIndexValidation.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.c
new file mode 100644
index 00000000000..ccba3e54840
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.c
@@ -0,0 +1,126 @@
+// External test case from SAMATE's Juliet Test Suite for C/C++
+// (http://samate.nist.gov/SRD/testsuite.php)
+// Associated with CWE-134: Uncontrolled format string. http://cwe.mitre.org/data/definitions/134.html
+// This is a snippet with added declarations, not the entire test case.
+
+typedef unsigned long size_t;
+
+typedef void *va_list;
+#define va_start(ap, parmN)
+#define va_end(ap)
+#define va_arg(ap, type) ((type)0)
+
+int vsnprintf(char *s, size_t n, const char *format, va_list arg);
+
+size_t strlen(const char *s);
+
+#define SOCKET int
+#define INVALID_SOCKET (0)
+#define SOCKET_ERROR (1)
+#define AF_INET (2)
+#define SOCK_STREAM (3)
+#define IPPROTO_TCP (4)
+#define IP_ADDRESS (5)
+#define TCP_PORT (6)
+typedef int in_addr_t;
+struct in_addr {
+ in_addr_t s_addr;
+};
+struct sockaddr_in {
+ int sin_family;
+ int sin_port;
+ struct in_addr sin_addr;
+};
+in_addr_t inet_addr(const char *cp);
+
+#define IP_ADDRESS "0.0.0.0"
+
+void printLine(char *);
+
+static void badVaSink(char * data, ...)
+{
+ {
+ char dest[100] = "";
+ va_list args;
+ va_start(args, data);
+ /* POTENTIAL FLAW: Do not specify the format allowing a possible format string vulnerability */
+ vsnprintf(dest, 100-1, data, args);
+ va_end(args);
+ printLine(dest);
+ }
+}
+
+void CWE134_Uncontrolled_Format_String__char_connect_socket_w32_vsnprintf_01_bad()
+{
+ char * data;
+ char dataBuffer[100] = "";
+ data = dataBuffer;
+ {
+#ifdef _WIN32
+ WSADATA wsaData;
+ int wsaDataInit = 0;
+#endif
+ int recvResult;
+ struct sockaddr_in service;
+ char *replace;
+ SOCKET connectSocket = INVALID_SOCKET;
+ size_t dataLen = strlen(data);
+ do
+ {
+#ifdef _WIN32
+ if (WSAStartup(MAKEWORD(2,2), &wsaData) != NO_ERROR)
+ {
+ break;
+ }
+ wsaDataInit = 1;
+#endif
+ /* POTENTIAL FLAW: Read data using a connect socket */
+ connectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ if (connectSocket == INVALID_SOCKET)
+ {
+ break;
+ }
+ memset(&service, 0, sizeof(service));
+ service.sin_family = AF_INET;
+ service.sin_addr.s_addr = inet_addr(IP_ADDRESS);
+ service.sin_port = htons(TCP_PORT);
+ if (connect(connectSocket, (struct sockaddr*)&service, sizeof(service)) == SOCKET_ERROR)
+ {
+ break;
+ }
+ /* Abort on error or the connection was closed, make sure to recv one
+ * less char than is in the recv_buf in order to append a terminator */
+ /* Abort on error or the connection was closed */
+ recvResult = recv(connectSocket, (char *)(data + dataLen), sizeof(char) * (100 - dataLen - 1), 0);
+ if (recvResult == SOCKET_ERROR || recvResult == 0)
+ {
+ break;
+ }
+ /* Append null terminator */
+ data[dataLen + recvResult / sizeof(char)] = '\0';
+ /* Eliminate CRLF */
+ replace = strchr(data, '\r');
+ if (replace)
+ {
+ *replace = '\0';
+ }
+ replace = strchr(data, '\n');
+ if (replace)
+ {
+ *replace = '\0';
+ }
+ }
+ while (0);
+ if (connectSocket != INVALID_SOCKET)
+ {
+ CLOSE_SOCKET(connectSocket);
+ }
+#ifdef _WIN32
+ if (wsaDataInit)
+ {
+ WSACleanup();
+ }
+#endif
+ }
+ badVaSink(data, data);
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.expected
new file mode 100644
index 00000000000..46fc6b5a61d
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.expected
@@ -0,0 +1,17 @@
+edges
+| test.c:93:46:93:69 | recv output argument | test.c:124:15:124:18 | data |
+| test.c:93:46:93:69 | recv output argument | test.c:124:15:124:18 | data |
+| test.c:93:46:93:69 | recv output argument | test.c:124:15:124:18 | data indirection |
+| test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data |
+| test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data |
+| test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data indirection |
+nodes
+| test.c:93:46:93:69 | recv output argument | semmle.label | recv output argument |
+| test.c:93:55:93:68 | ... + ... | semmle.label | ... + ... |
+| test.c:124:15:124:18 | data | semmle.label | data |
+| test.c:124:15:124:18 | data | semmle.label | data |
+| test.c:124:15:124:18 | data | semmle.label | data |
+| test.c:124:15:124:18 | data indirection | semmle.label | data indirection |
+| test.c:124:15:124:18 | data indirection | semmle.label | data indirection |
+#select
+| test.c:124:15:124:18 | data | test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data | The value of this argument may come from $@ and is being used as a formatting argument to badVaSink(data), which calls vsnprintf(format) | test.c:93:55:93:68 | ... + ... | recv |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.qlref
new file mode 100644
index 00000000000..079e0c8a7c0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-134/UncontrolledFormatString.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
new file mode 100644
index 00000000000..af6affbd1ac
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
@@ -0,0 +1,161 @@
+// External test case from SAMATE's Juliet Test Suite for C/C++
+// (http://samate.nist.gov/SRD/testsuite.php)
+// Associated with CWE-134: Uncontrolled format string. http://cwe.mitre.org/data/definitions/134.html
+// Examples amended to have all function declarations inlined.
+
+/* TEMPLATE GENERATED TESTCASE FILE
+Filename: CWE134_Uncontrolled_Format_String__char_console_fprintf_01.c
+Label Definition File: CWE134_Uncontrolled_Format_String.label.xml
+Template File: sources-sinks-01.tmpl.c
+*/
+/*
+ * @description
+ * CWE: 134 Uncontrolled Format String
+ * BadSource: console Read input from the console
+ * GoodSource: Copy a fixed string into data
+ * Sinks: fprintf
+ * GoodSink: fprintf with "%s" as the second argument and data as the third
+ * BadSink : fprintf with data as the second argument
+ * Flow Variant: 01 Baseline
+ *
+ * */
+
+// Replaced with inlined functions
+//#include "std_testcase.h"
+//
+//#ifndef _WIN32
+//# include
+//#endif
+#define NULL 0
+typedef unsigned long size_t;
+typedef struct {} FILE;
+extern FILE * stdin;
+extern FILE * stdout;
+size_t strlen(const char *s);
+char *fgets(char *s, int n, FILE *stream);
+int fprintf(FILE *stream, const char *format, ...);
+char *strcpy(char *s1, const char *s2);
+void srand(unsigned int seed);
+
+void printLine(char *);
+
+#ifndef OMITBAD
+
+void CWE134_Uncontrolled_Format_String__char_console_fprintf_01_bad()
+{
+ char * data;
+ char data_buf[100] = "";
+ data = data_buf;
+ {
+ /* Read input from the console */
+ size_t data_len = strlen(data);
+ /* if there is room in data, read into it from the console */
+ /* POTENTIAL FLAW: Read data from the console */
+ if(100-data_len > 1)
+ {
+ if (fgets(data+data_len, (int)(100-data_len), stdin) != NULL)
+ {
+ /* The next 3 lines remove the carriage return from the string that is
+ * inserted by fgets() */
+ data_len = strlen(data);
+ if (data_len > 0 && data[data_len-1] == '\n')
+ {
+ data[data_len-1] = '\0';
+ }
+ }
+ else
+ {
+ printLine("fgets() failed");
+ /* Restore NUL terminator if fgets fails */
+ data[data_len] = '\0';
+ }
+ }
+ }
+ /* POTENTIAL FLAW: Do not specify the format allowing a possible format string vulnerability */
+ fprintf(stdout, data);
+}
+
+#endif /* OMITBAD */
+
+#ifndef OMITGOOD
+
+/* goodG2B uses the GoodSource with the BadSink */
+static void goodG2B()
+{
+ char * data;
+ char data_buf[100] = "";
+ data = data_buf;
+ /* FIX: Use a fixed string that does not contain a format specifier */
+ strcpy(data, "fixedstringtest");
+ /* POTENTIAL FLAW: Do not specify the format allowing a possible format string vulnerability */
+ fprintf(stdout, data);
+}
+
+/* goodB2G uses the BadSource with the GoodSink */
+static void goodB2G()
+{
+ char * data;
+ char data_buf[100] = "";
+ data = data_buf;
+ {
+ /* Read input from the console */
+ size_t data_len = strlen(data);
+ /* if there is room in data, read into it from the console */
+ /* POTENTIAL FLAW: Read data from the console */
+ if(100-data_len > 1)
+ {
+ if (fgets(data+data_len, (int)(100-data_len), stdin) != NULL)
+ {
+ /* The next 3 lines remove the carriage return from the string that is
+ * inserted by fgets() */
+ data_len = strlen(data);
+ if (data_len > 0 && data[data_len-1] == '\n')
+ {
+ data[data_len-1] = '\0';
+ }
+ }
+ else
+ {
+ printLine("fgets() failed");
+ /* Restore NUL terminator if fgets fails */
+ data[data_len] = '\0';
+ }
+ }
+ }
+ /* FIX: Specify the format disallowing a format string vulnerability */
+ fprintf(stdout, "%s\n", data);
+}
+
+void CWE134_Uncontrolled_Format_String__char_console_fprintf_01_good()
+{
+ goodG2B();
+ goodB2G();
+}
+
+#endif /* OMITGOOD */
+
+/* Below is the main(). It is only used when building this testcase on
+ its own for testing or for building a binary to use in testing binary
+ analysis tools. It is not used when compiling all the testcases as one
+ application, which is how source code analysis tools are tested. */
+
+#ifdef INCLUDEMAIN
+
+int main(int argc, char * argv[])
+{
+ /* seed randomness */
+ srand( (unsigned)time(NULL) );
+#ifndef OMITGOOD
+ printLine("Calling good()...");
+ CWE134_Uncontrolled_Format_String__char_console_fprintf_01_good();
+ printLine("Finished good()");
+#endif /* OMITGOOD */
+#ifndef OMITBAD
+ printLine("Calling bad()...");
+ CWE134_Uncontrolled_Format_String__char_console_fprintf_01_bad();
+ printLine("Finished bad()");
+#endif /* OMITBAD */
+ return 0;
+}
+
+#endif
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected
new file mode 100644
index 00000000000..5e6af965119
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected
@@ -0,0 +1,17 @@
+edges
+| test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | (const char *)... |
+| test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | data |
+| test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | data indirection |
+| test.c:56:23:56:35 | fgets output argument | test.c:75:21:75:24 | (const char *)... |
+| test.c:56:23:56:35 | fgets output argument | test.c:75:21:75:24 | data |
+| test.c:56:23:56:35 | fgets output argument | test.c:75:21:75:24 | data indirection |
+nodes
+| test.c:56:23:56:35 | ... + ... | semmle.label | ... + ... |
+| test.c:56:23:56:35 | fgets output argument | semmle.label | fgets output argument |
+| test.c:75:21:75:24 | (const char *)... | semmle.label | (const char *)... |
+| test.c:75:21:75:24 | (const char *)... | semmle.label | (const char *)... |
+| test.c:75:21:75:24 | data | semmle.label | data |
+| test.c:75:21:75:24 | data indirection | semmle.label | data indirection |
+| test.c:75:21:75:24 | data indirection | semmle.label | data indirection |
+#select
+| test.c:75:21:75:24 | data | test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | data | The value of this argument may come from $@ and is being used as a formatting argument to fprintf(format) | test.c:56:23:56:35 | ... + ... | fgets |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref
new file mode 100644
index 00000000000..079e0c8a7c0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-134/UncontrolledFormatString.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
new file mode 100644
index 00000000000..753183ca31a
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
@@ -0,0 +1,136 @@
+// External test case from SAMATE's Juliet Test Suite for C/C++
+// (http://samate.nist.gov/SRD/testsuite.php)
+// Associated with CWE-134: Uncontrolled format string. http://cwe.mitre.org/data/definitions/134.html
+// Examples amended to have all function declarations inlined.
+
+/* TEMPLATE GENERATED TESTCASE FILE
+Filename: CWE134_Uncontrolled_Format_String__char_environment_fprintf_01.c
+Label Definition File: CWE134_Uncontrolled_Format_String.label.xml
+Template File: sources-sinks-01.tmpl.c
+*/
+/*
+ * @description
+ * CWE: 134 Uncontrolled Format String
+ * BadSource: environment Read input from an environment variable
+ * GoodSource: Copy a fixed string into data
+ * Sinks: fprintf
+ * GoodSink: fprintf with "%s" as the second argument and data as the third
+ * BadSink : fprintf with data as the second argument
+ * Flow Variant: 01 Baseline
+ *
+ * */
+
+// Replaced with inlined functions
+//#include "std_testcase.h"
+//
+//#ifndef _WIN32
+//# include
+//#endif
+#define NULL 0
+typedef struct {} FILE;
+typedef unsigned long size_t;
+extern FILE * stdout;
+void srand(unsigned int seed);
+size_t strlen(const char *s);
+char *getenv(const char *name);
+char *strcpy(char *s1, const char *s2);
+char *strncat(char *s1, const char *s2, size_t n);
+int fprintf(FILE *stream, const char *format, ...);
+
+void printLine(char *);
+
+#define ENV_VARIABLE "ADD"
+#define GETENV getenv
+
+#ifndef OMITBAD
+
+void CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_bad()
+{
+ char * data;
+ char data_buf[100] = "";
+ data = data_buf;
+ {
+ /* Append input from an environment variable to data */
+ size_t data_len = strlen(data);
+ char * environment = GETENV(ENV_VARIABLE);
+ /* If there is data in the environment variable */
+ if (environment != NULL)
+ {
+ /* POTENTIAL FLAW: Read data from an environment variable */
+ strncat(data+data_len, environment, 100-data_len-1);
+ }
+ }
+ /* POTENTIAL FLAW: Do not specify the format allowing a possible format string vulnerability */
+ fprintf(stdout, data);
+}
+
+#endif /* OMITBAD */
+
+#ifndef OMITGOOD
+
+/* goodG2B uses the GoodSource with the BadSink */
+static void goodG2B()
+{
+ char * data;
+ char data_buf[100] = "";
+ data = data_buf;
+ /* FIX: Use a fixed string that does not contain a format specifier */
+ strcpy(data, "fixedstringtest");
+ /* POTENTIAL FLAW: Do not specify the format allowing a possible format string vulnerability */
+ fprintf(stdout, data);
+}
+
+/* goodB2G uses the BadSource with the GoodSink */
+static void goodB2G()
+{
+ char * data;
+ char data_buf[100] = "";
+ data = data_buf;
+ {
+ /* Append input from an environment variable to data */
+ size_t data_len = strlen(data);
+ char * environment = GETENV(ENV_VARIABLE);
+ /* If there is data in the environment variable */
+ if (environment != NULL)
+ {
+ /* POTENTIAL FLAW: Read data from an environment variable */
+ strncat(data+data_len, environment, 100-data_len-1);
+ }
+ }
+ /* FIX: Specify the format disallowing a format string vulnerability */
+ fprintf(stdout, "%s\n", data);
+}
+
+void CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_good()
+{
+ goodG2B();
+ goodB2G();
+}
+
+#endif /* OMITGOOD */
+
+/* Below is the main(). It is only used when building this testcase on
+ its own for testing or for building a binary to use in testing binary
+ analysis tools. It is not used when compiling all the testcases as one
+ application, which is how source code analysis tools are tested. */
+
+#ifdef INCLUDEMAIN
+
+int main(int argc, char * argv[])
+{
+ /* seed randomness */
+ srand( (unsigned)time(NULL) );
+#ifndef OMITGOOD
+ printLine("Calling good()...");
+ CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_good();
+ printLine("Finished good()");
+#endif /* OMITGOOD */
+#ifndef OMITBAD
+ printLine("Calling bad()...");
+ CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_bad();
+ printLine("Finished bad()");
+#endif /* OMITBAD */
+ return 0;
+}
+
+#endif
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected
new file mode 100644
index 00000000000..122dff5b3f0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected
@@ -0,0 +1,17 @@
+edges
+| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | (const char *)... |
+| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | (const char *)... |
+| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data |
+| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data |
+| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data indirection |
+| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data indirection |
+nodes
+| test.c:59:30:59:35 | call to getenv | semmle.label | call to getenv |
+| test.c:59:30:59:35 | call to getenv | semmle.label | call to getenv |
+| test.c:68:21:68:24 | (const char *)... | semmle.label | (const char *)... |
+| test.c:68:21:68:24 | (const char *)... | semmle.label | (const char *)... |
+| test.c:68:21:68:24 | data | semmle.label | data |
+| test.c:68:21:68:24 | data indirection | semmle.label | data indirection |
+| test.c:68:21:68:24 | data indirection | semmle.label | data indirection |
+#select
+| test.c:68:21:68:24 | data | test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data | The value of this argument may come from $@ and is being used as a formatting argument to fprintf(format) | test.c:59:30:59:35 | call to getenv | getenv |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref
new file mode 100644
index 00000000000..079e0c8a7c0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-134/UncontrolledFormatString.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected
new file mode 100644
index 00000000000..858be731077
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected
@@ -0,0 +1,13 @@
+edges
+| examples.cpp:62:26:62:30 | & ... | examples.cpp:65:11:65:14 | data |
+| examples.cpp:62:26:62:30 | & ... | examples.cpp:65:11:65:14 | data |
+| examples.cpp:62:26:62:30 | fscanf output argument | examples.cpp:65:11:65:14 | data |
+| examples.cpp:62:26:62:30 | fscanf output argument | examples.cpp:65:11:65:14 | data |
+nodes
+| examples.cpp:62:26:62:30 | & ... | semmle.label | & ... |
+| examples.cpp:62:26:62:30 | fscanf output argument | semmle.label | fscanf output argument |
+| examples.cpp:65:11:65:14 | data | semmle.label | data |
+| examples.cpp:65:11:65:14 | data | semmle.label | data |
+| examples.cpp:65:11:65:14 | data | semmle.label | data |
+#select
+| examples.cpp:65:11:65:14 | data | examples.cpp:62:26:62:30 | & ... | examples.cpp:65:11:65:14 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:62:26:62:30 | & ... | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.qlref
new file mode 100644
index 00000000000..3939653db1c
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-190/ArithmeticTainted.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected
new file mode 100644
index 00000000000..14f313e62cf
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected
@@ -0,0 +1,77 @@
+edges
+| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
+| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+nodes
+| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:24:31:24:34 | data | semmle.label | data |
+| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:37:9:37:12 | data | semmle.label | data |
+#select
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.qlref
new file mode 100644
index 00000000000..1fcafc3ca1c
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-190/ArithmeticUncontrolled.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.qlref
new file mode 100644
index 00000000000..ab2c35ce59b
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticWithExtremeValues.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-190/ArithmeticWithExtremeValues.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected
new file mode 100644
index 00000000000..9a9a0eba1ab
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected
@@ -0,0 +1 @@
+| examples.cpp:65:9:65:14 | -- ... | $@ flows to here and is used in an expression which might overflow negatively. | examples.cpp:62:26:62:30 | & ... | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.qlref
new file mode 100644
index 00000000000..df42008c632
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-190/IntegerOverflowTainted.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp
new file mode 100644
index 00000000000..7a824748861
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp
@@ -0,0 +1,70 @@
+// External test cases for rule CWE-190.
+
+///// Library functions //////
+
+typedef struct {} FILE;
+extern FILE *stdin;
+int fscanf(FILE *stream, const char *format, ...);
+int rand(void);
+
+#define URAND31() (((unsigned)rand()<<30) ^ ((unsigned)rand()<<15) ^ rand())
+#define RAND32() ((int)(rand() & 1 ? URAND31() : -URAND31() - 1))
+
+void printUnsignedLine(unsigned unsignedNumber);
+
+//// Test code /////
+
+void CWE191_Integer_Underflow__unsigned_int_rand_sub_01_bad()
+{
+ unsigned int data;
+ data = 0;
+ /* POTENTIAL FLAW: Use a random value */
+ data = (unsigned int)RAND32();
+ {
+ /* POTENTIAL FLAW: Subtracting 1 from data could cause an underflow */
+ unsigned int result = data - 1;
+ printUnsignedLine(result);
+ }
+}
+
+void CWE191_Integer_Underflow__unsigned_int_rand_postdec_01_bad()
+{
+ unsigned int data;
+ data = 0;
+ /* POTENTIAL FLAW: Use a random value */
+ data = (unsigned int)RAND32();
+ {
+ /* POTENTIAL FLAW: Decrementing data could cause an underflow */
+ data--;
+ unsigned int result = data;
+ printUnsignedLine(result);
+ }
+}
+
+void CWE191_Integer_Underflow__unsigned_int_min_postdec_01_bad()
+{
+ unsigned int data;
+ data = 0;
+ /* POTENTIAL FLAW: Use the minimum size of the data type */
+ data = 0;
+ {
+ /* POTENTIAL FLAW: Decrementing data could cause an underflow [NOT DETECTED] */
+ data--;
+ unsigned int result = data;
+ printUnsignedLine(result);
+ }
+}
+
+void CWE191_Integer_Underflow__unsigned_int_fscanf_predec_01_bad()
+{
+ unsigned int data;
+ data = 0;
+ /* POTENTIAL FLAW: Use a value input from the console */
+ fscanf (stdin, "%u", &data);
+ {
+ /* POTENTIAL FLAW: Decrementing data could cause an underflow */
+ --data;
+ unsigned int result = data;
+ printUnsignedLine(result);
+ }
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.expected
new file mode 100644
index 00000000000..f71dbfde719
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.expected
@@ -0,0 +1 @@
+| tests.cpp:39:31:39:34 | data | $@ flows to here and is used in an expression which might overflow. | tests.cpp:58:27:58:31 | & ... | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.qlref
new file mode 100644
index 00000000000..72ed7d53685
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-190/IntegerOverflowTainted.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
new file mode 100644
index 00000000000..f183df39eab
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
@@ -0,0 +1,74 @@
+//semmle-extractor-options: --edg --target --edg win64
+
+// A selection of tests from the samate framework for rule CWE-197.
+
+// library types, functions etc
+typedef struct {} FILE;
+int fscanf(FILE *stream, const char *format, ...);
+#define CHAR_MAX (127)
+FILE *stdin;
+
+void printHexCharLine(char charHex);
+
+// ----------
+
+class CWE197_Numeric_Truncation_Error__short_fscanf_82_base
+{
+public:
+ /* pure virtual function */
+ virtual void action(short data) = 0;
+};
+
+class CWE197_Numeric_Truncation_Error__short_fscanf_82_bad : public CWE197_Numeric_Truncation_Error__short_fscanf_82_base
+{
+public:
+ void action(short data);
+};
+
+class CWE197_Numeric_Truncation_Error__short_fscanf_82_goodG2B : public CWE197_Numeric_Truncation_Error__short_fscanf_82_base
+{
+public:
+ void action(short data);
+};
+
+void CWE197_Numeric_Truncation_Error__short_fscanf_82_bad::action(short data)
+{
+ {
+ /* POTENTIAL FLAW: Convert data to a char, possibly causing a truncation error */
+ char charData = (char)data;
+ printHexCharLine(charData);
+ }
+}
+
+void CWE197_Numeric_Truncation_Error__short_fscanf_82_goodG2B::action(short data)
+{
+ {
+ char charData = (char)data;
+ printHexCharLine(charData);
+ }
+}
+
+void bad()
+{
+ short data;
+ /* Initialize data */
+ data = -1;
+ /* FLAW: Use a number input from the console using fscanf() */
+ fscanf (stdin, "%hd", &data);
+ CWE197_Numeric_Truncation_Error__short_fscanf_82_base* baseObject = new CWE197_Numeric_Truncation_Error__short_fscanf_82_bad;
+ baseObject->action(data);
+ delete baseObject;
+}
+
+/* goodG2B uses the GoodSource with the BadSink */
+static void goodG2B()
+{
+ short data;
+ /* Initialize data */
+ data = -1;
+ /* FIX: Use a positive integer less than CHAR_MAX*/
+ data = CHAR_MAX-5;
+ CWE197_Numeric_Truncation_Error__short_fscanf_82_base* baseObject = new CWE197_Numeric_Truncation_Error__short_fscanf_82_goodG2B;
+ baseObject->action(data);
+ delete baseObject;
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
new file mode 100644
index 00000000000..77a8ad6c533
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
@@ -0,0 +1 @@
+| tests.c:76:9:76:15 | call to fprintf | This operation exposes system data from $@. | tests.c:60:13:60:22 | call to LogonUserA | call to LogonUserA |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.qlref
new file mode 100644
index 00000000000..0c88835bf1f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-497/ExposedSystemData.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
new file mode 100644
index 00000000000..3f9eeda87db
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
@@ -0,0 +1,7 @@
+| tests.c:35:9:35:14 | call to printf | tests.c:35:16:35:21 | %s\n |
+| tests.c:35:9:35:14 | call to printf | tests.c:35:24:35:27 | line |
+| tests.c:49:13:49:21 | call to printLine | tests.c:49:23:49:38 | fgets() failed |
+| tests.c:68:13:68:21 | call to printLine | tests.c:68:23:68:52 | User logged in successfully. |
+| tests.c:73:13:73:21 | call to printLine | tests.c:73:23:73:40 | Unable to login. |
+| tests.c:76:9:76:15 | call to fprintf | tests.c:76:25:76:67 | User attempted access with password: %s\n |
+| tests.c:76:9:76:15 | call to fprintf | tests.c:76:70:76:77 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.ql b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.ql
new file mode 100644
index 00000000000..d9e67a944e9
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.ql
@@ -0,0 +1,4 @@
+import semmle.code.cpp.security.OutputWrite
+
+from OutputWrite ow
+select ow, ow.getASource()
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c
new file mode 100644
index 00000000000..03248393f88
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c
@@ -0,0 +1,72 @@
+// Semmle test cases for rule CWE-497
+
+// library functions etc
+typedef struct {} FILE;
+
+// define stdout, stderr in a similar style to MinGW
+FILE std_files[2];
+#define stdin (&std_files[0])
+#define stderr (&std_files[1])
+
+typedef unsigned long size_t;
+size_t strlen(const char *s);
+int printf(const char *format, ...);
+int fprintf(FILE *stream, const char *format, ...);
+char *fgets(char *s, int n, FILE *stream);
+
+typedef struct {} *HANDLE;
+int LogonUserA(const char *lpszUserName, const char *lpszDomain, const char *lpszPassword, int dwLogonType, int dwLogonProvider, HANDLE *phToken);
+void CloseHandle(HANDLE h);
+
+#define NULL (0)
+#define LOGON32_LOGON_NETWORK (1)
+#define LOGON32_PROVIDER_DEFAULT (2)
+
+void printLine(const char * line)
+{
+ if(line != NULL)
+ {
+ printf("%s\n", line);
+ }
+}
+
+void CWE535_Info_Exposure_Shell_Error__w32_char_01_bad()
+{
+ {
+ char password[100] = "";
+ size_t passwordLen = 0;
+ HANDLE pHandle;
+ char * username = "User";
+ char * domain = "Domain";
+ if (fgets(password, 100, stdin) == NULL)
+ {
+ printLine("fgets() failed");
+ /* Restore NUL terminator if fgets fails */
+ password[0] = '\0';
+ }
+ /* Remove the carriage return from the string that is inserted by fgets() */
+ passwordLen = strlen(password);
+ if (passwordLen > 0)
+ {
+ password[passwordLen-1] = '\0';
+ }
+ /* Use the password in LogonUser() to establish that it is "sensitive" */
+ if (LogonUserA(
+ username,
+ domain,
+ password,
+ LOGON32_LOGON_NETWORK,
+ LOGON32_PROVIDER_DEFAULT,
+ &pHandle) != 0)
+ {
+ printLine("User logged in successfully.");
+ CloseHandle(pHandle);
+ }
+ else
+ {
+ printLine("Unable to login.");
+ }
+ /* FLAW: Write sensitive data to stderr */
+ fprintf(stderr, "User attempted access with password: %s\n", password);
+ }
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected
new file mode 100644
index 00000000000..311f82ad99d
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected
@@ -0,0 +1 @@
+| test.cpp:129:17:129:17 | call to operator>> | Use of 'cin' without specifying the length of the input may be dangerous. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.qlref
new file mode 100644
index 00000000000..5a35bf81fd9
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-676/DangerousUseOfCin.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
new file mode 100644
index 00000000000..708ba5f05f9
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
@@ -0,0 +1,69 @@
+// Samate test case for rule DangerousUseOfCin.ql / CWE-676
+
+// --- library types, functions etc ---
+
+namespace std
+{
+ // --- std::string ---
+
+ // std::char_traits
+ template class char_traits;
+
+ // --- std::istream ---
+
+ // std::basic_istream
+ template >
+ class basic_istream /*: virtual public basic_ios - not needed for this test */ {
+ };
+
+ // operator>> std::basic_istream -> char*
+ template basic_istream& operator>>(basic_istream&, charT*);
+
+ // std::istream
+ typedef basic_istream istream;
+
+ // --- std::cin ---
+
+ extern istream cin;
+}
+
+void printLine(const char *str);
+
+// --- test cases ---
+
+using namespace std;
+
+#define CHAR_BUFFER_SIZE 10
+
+void CWE676_Use_of_Potentially_Dangerous_Function__basic_17_bad()
+{
+ int j;
+ for(j = 0; j < 1; j++)
+ {
+ {
+ char charBuffer[CHAR_BUFFER_SIZE];
+ /* FLAW: using cin in an inherently dangerous fashion */
+ /* INCIDENTAL CWE120 Buffer Overflow since cin extraction is unbounded. */
+ cin >> charBuffer; // BAD
+ charBuffer[CHAR_BUFFER_SIZE-1] = '\0';
+ printLine(charBuffer);
+ }
+ }
+}
+
+/* good1() changes the conditions on the for statements */
+static void CWE676_Use_of_Potentially_Dangerous_Function__basic_17_good1()
+{
+ int k;
+ for(k = 0; k < 1; k++)
+ {
+ {
+ char charBuffer[CHAR_BUFFER_SIZE];
+ /* FIX: Use cin after specifying the length of the input */
+ cin.width(CHAR_BUFFER_SIZE);
+ cin >> charBuffer; // GOOD
+ charBuffer[CHAR_BUFFER_SIZE-1] = '\0';
+ printLine(charBuffer);
+ }
+ }
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.qlref
new file mode 100644
index 00000000000..fd711c007f0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileMayNotBeClosed.qlref
@@ -0,0 +1 @@
+Critical/FileMayNotBeClosed.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
new file mode 100644
index 00000000000..31a7fe984bd
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
@@ -0,0 +1,3 @@
+| tests.cpp:248:12:248:16 | call to fopen | The file is never closed |
+| tests.cpp:280:12:280:15 | call to open | The file is never closed |
+| tests.cpp:306:12:306:21 | call to CreateFile | The file is never closed |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.qlref
new file mode 100644
index 00000000000..825ac26f500
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.qlref
@@ -0,0 +1 @@
+Critical/FileNeverClosed.ql
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
new file mode 100644
index 00000000000..006eeb5aa4c
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
@@ -0,0 +1,2 @@
+| tests.cpp:226:31:226:36 | call to malloc | The memory allocated here may not be released at $@. | tests.cpp:240:1:240:1 | return ... | this exit point |
+| tests.cpp:353:5:353:68 | ... = ... | The memory allocated here may not be released at $@. | tests.cpp:361:1:361:1 | return ... | this exit point |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.qlref
new file mode 100644
index 00000000000..33da8e296e2
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.qlref
@@ -0,0 +1 @@
+Critical/MemoryMayNotBeFreed.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
new file mode 100644
index 00000000000..9c72dced70d
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
@@ -0,0 +1,2 @@
+| tests.cpp:99:20:99:26 | new | This memory is never freed |
+| tests.cpp:164:24:164:29 | call to malloc | This memory is never freed |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.qlref
new file mode 100644
index 00000000000..2d1336a55eb
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.qlref
@@ -0,0 +1 @@
+Critical/MemoryNeverFreed.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp
new file mode 100644
index 00000000000..01e63310396
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp
@@ -0,0 +1,333 @@
+// Sample of samate tests for CWE-772.
+
+// --- library types, functions etc ---
+
+#define NULL (0)
+typedef unsigned long size_t;
+
+void *malloc(size_t size);
+void *realloc(void *ptr, size_t size);
+void *alloca(size_t size);
+#define ALLOCA alloca
+void free(void *ptr);
+
+typedef struct {} FILE;
+FILE *fopen(const char *filename, const char *mode);
+int fclose(FILE *stream);
+
+char *strcpy(char *s1, const char *s2);
+
+void printLine(const char *str);
+void printIntLine(int val);
+
+// --- open ---
+
+typedef unsigned int mode_t;
+int open(const char *path, int oflags, mode_t mode);
+#define OPEN open
+int close(int fd);
+#define CLOSE close
+
+#define O_RDWR (1)
+#define O_CREAT (2)
+#define S_IREAD (3)
+#define S_IWRITE (4)
+
+// --- Windows ---
+
+typedef unsigned int HANDLE;
+#define INVALID_HANDLE_VALUE (-1)
+typedef const char *LPCTSTR;
+typedef unsigned long DWORD;
+typedef struct _SECURITY_ATTRIBUTES {} *LPSECURITY_ATTRIBUTES;
+typedef bool BOOL;
+HANDLE CreateFile(
+ LPCTSTR lpFileName,
+ DWORD dwDesiredAccess,
+ DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
+ DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes,
+ HANDLE hTemplateFile);
+BOOL CloseHandle(HANDLE hObject);
+
+#define GENERIC_READ (1)
+#define GENERIC_WRITE (2)
+#define OPEN_ALWAYS (3)
+#define FILE_ATTRIBUTE_NORMAL (4)
+
+// --- test cases ---
+
+namespace CWE401_Memory_Leak__new_int_17
+{
+ void bad()
+ {
+ int i,j;
+ int * data;
+ data = NULL;
+ for(i = 0; i < 1; i++)
+ {
+ /* POTENTIAL FLAW: Allocate memory on the heap */
+ data = new int; // BAD
+ /* Initialize and make use of data */
+ *data = 5;
+ printIntLine(*data);
+ }
+ for(j = 0; j < 1; j++)
+ {
+ /* POTENTIAL FLAW: No deallocation */
+ ; /* empty statement needed for some flow variants */
+ }
+ }
+
+ /* goodB2G() - use badsource and goodsink in the for statements */
+ static void goodB2G()
+ {
+ int i,k;
+ int * data;
+ data = NULL;
+ for(i = 0; i < 1; i++)
+ {
+ /* POTENTIAL FLAW: Allocate memory on the heap */
+ data = new int; // GOOD
+ /* Initialize and make use of data */
+ *data = 5;
+ printIntLine(*data);
+ }
+ for(k = 0; k < 1; k++)
+ {
+ /* FIX: Deallocate memory */
+ delete data;
+ }
+ }
+
+ /* goodG2B() - use goodsource and badsink in the for statements */
+ static void goodG2B()
+ {
+ int h,j;
+ int * data;
+ data = NULL;
+ for(h = 0; h < 1; h++)
+ {
+ /* FIX: Use memory allocated on the stack */
+ int dataGoodBuffer; // GOOD
+ data = &dataGoodBuffer;
+ /* Initialize and make use of data */
+ *data = 5;
+ printIntLine(*data);
+ }
+ for(j = 0; j < 1; j++)
+ {
+ /* POTENTIAL FLAW: No deallocation */
+ ; /* empty statement needed for some flow variants */
+ }
+ }
+} /* close namespace */
+
+void CWE401_Memory_Leak__char_malloc_32_bad()
+{
+ char * data;
+ char * *dataPtr1 = &data;
+ char * *dataPtr2 = &data;
+ data = NULL;
+ {
+ char * data = *dataPtr1;
+ /* POTENTIAL FLAW: Allocate memory on the heap */
+ data = (char *)malloc(100*sizeof(char)); // BAD
+ /* Initialize and make use of data */
+ strcpy(data, "A String");
+ printLine(data);
+ *dataPtr1 = data;
+ }
+ {
+ char * data = *dataPtr2;
+ /* POTENTIAL FLAW: No deallocation */
+ ; /* empty statement needed for some flow variants */
+ }
+}
+
+/* goodG2B() uses the GoodSource with the BadSink */
+static void CWE401_Memory_Leak__char_malloc_32_goodG2B()
+{
+ char * data;
+ char * *dataPtr1 = &data;
+ char * *dataPtr2 = &data;
+ data = NULL;
+ {
+ char * data = *dataPtr1;
+ /* FIX: Use memory allocated on the stack with ALLOCA */
+ data = (char *)ALLOCA(100*sizeof(char)); // GOOD
+ /* Initialize and make use of data */
+ strcpy(data, "A String");
+ printLine(data);
+ *dataPtr1 = data;
+ }
+ {
+ char * data = *dataPtr2;
+ /* POTENTIAL FLAW: No deallocation */
+ ; /* empty statement needed for some flow variants */
+ }
+}
+
+/* goodB2G() uses the BadSource with the GoodSink */
+static void CWE401_Memory_Leak__char_malloc_32_goodB2G()
+{
+ char * data;
+ char * *dataPtr1 = &data;
+ char * *dataPtr2 = &data;
+ data = NULL;
+ {
+ char * data = *dataPtr1;
+ /* POTENTIAL FLAW: Allocate memory on the heap */
+ data = (char *)malloc(100*sizeof(char)); // GOOD
+ /* Initialize and make use of data */
+ strcpy(data, "A String");
+ printLine(data);
+ *dataPtr1 = data;
+ }
+ {
+ char * data = *dataPtr2;
+ /* FIX: Deallocate memory */
+ free(data);
+ }
+}
+
+void CWE401_Memory_Leak__malloc_realloc_char_01_bad()
+{
+ {
+ char * data = (char *)malloc(100*sizeof(char)); // BAD
+ /* Initialize and make use of data */
+ strcpy(data, "A String");
+ printLine(data);
+ /* FLAW: If realloc() fails, the initial memory block will not be freed() */
+ data = (char *)realloc(data, (130000)*sizeof(char));
+ if (data != NULL)
+ {
+ /* Reinitialize and make use of data */
+ strcpy(data, "New String");
+ printLine(data);
+ free(data);
+ }
+ }
+}
+
+void CWE775_Missing_Release_of_File_Descriptor_or_Handle__fopen_no_close_17_bad()
+{
+ int j;
+ FILE * data;
+ data = NULL;
+ /* POTENTIAL FLAW: Open a file without closing it */
+ data = fopen("BadSource_fopen.txt", "w+"); // BAD
+ for(j = 0; j < 1; j++)
+ {
+ /* FLAW: No attempt to close the file */
+ ; /* empty statement needed for some flow variants */
+ }
+}
+
+/* goodB2G() - use the goodsink in the for statement */
+static void CWE775_Missing_Release_of_File_Descriptor_or_Handle__fopen_no_close_17_goodB2G()
+{
+ int k;
+ FILE * data;
+ data = NULL;
+ /* POTENTIAL FLAW: Open a file without closing it */
+ data = fopen("BadSource_fopen.txt", "w+"); // GOOD
+ for(k = 0; k < 1; k++)
+ {
+ /* FIX: If the file is still opened, close it */
+ if (data != NULL)
+ {
+ fclose(data);
+ }
+ }
+}
+
+void CWE775_Missing_Release_of_File_Descriptor_or_Handle__open_no_close_01_bad()
+{
+ int data;
+ /* Initialize data */
+ data = -1;
+ /* POTENTIAL FLAW: Open a file without closing it */
+ data = OPEN("BadSource_open.txt", O_RDWR|O_CREAT, S_IREAD|S_IWRITE); // BAD
+ /* FLAW: No attempt to close the file */
+ ; /* empty statement needed for some flow variants */
+}
+
+/* goodB2G() uses the BadSource with the GoodSink */
+static void CWE775_Missing_Release_of_File_Descriptor_or_Handle__open_no_close_01_goodB2G()
+{
+ int data;
+ /* Initialize data */
+ data = -1;
+ /* POTENTIAL FLAW: Open a file without closing it */
+ data = OPEN("BadSource_open.txt", O_RDWR|O_CREAT, S_IREAD|S_IWRITE); // GOOD
+ /* FIX: If the file is still opened, close it */
+ if (data != -1)
+ {
+ CLOSE(data);
+ }
+}
+
+void CWE775_Missing_Release_of_File_Descriptor_or_Handle__w32CreateFile_no_close_01_bad()
+{
+ HANDLE data;
+ /* Initialize data */
+ data = INVALID_HANDLE_VALUE;
+ /* POTENTIAL FLAW: Open a file without closing it */
+ data = CreateFile("BadSource_w32CreateFile.txt", // BAD
+ (GENERIC_WRITE|GENERIC_READ),
+ 0,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ /* FLAW: No attempt to close the file */
+ ; /* empty statement needed for some flow variants */
+}
+
+/* goodB2G() uses the BadSource with the GoodSink */
+static void CWE775_Missing_Release_of_File_Descriptor_or_Handle__w32CreateFile_no_close_01_goodB2G()
+{
+ HANDLE data;
+ /* Initialize data */
+ data = INVALID_HANDLE_VALUE;
+ /* POTENTIAL FLAW: Open a file without closing it */
+ data = CreateFile("BadSource_w32CreateFile.txt", // GOOD
+ (GENERIC_WRITE|GENERIC_READ),
+ 0,
+ NULL,
+ OPEN_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ /* FIX: If the file is still opened, close it */
+ if (data != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(data);
+ }
+}
+
+void exit(int status);
+
+typedef struct _twoIntsStruct
+{
+ int intOne;
+ int intTwo;
+} twoIntsStruct;
+
+void printStructLine(const twoIntsStruct * structTwoIntsStruct);
+
+void CWE401_Memory_Leak__twoIntsStruct_realloc_01_bad()
+{
+ twoIntsStruct * data;
+ data = NULL;
+ /* POTENTIAL FLAW: Allocate memory on the heap */
+ data = (twoIntsStruct *)realloc(data, 100*sizeof(twoIntsStruct));
+ if (data == NULL) {exit(-1);}
+ /* Initialize and make use of data */
+ data[0].intOne = 0;
+ data[0].intTwo = 0;
+ printStructLine(&data[0]);
+ /* POTENTIAL FLAW: No deallocation */
+ ; /* empty statement needed for some flow variants */
+}
From fdb4a2acdb41ab38f254a3d8e6a933fa9b7f1156 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 1 Sep 2021 15:39:19 +0100
Subject: [PATCH 105/459] C++: Clean up header comments.
---
..._Path_Traversal__char_console_fopen_11.cpp | 2 +-
.../Security/CWE/CWE-078/SAMATE/tests.cpp | 2 +-
.../UncontrolledProcessOperation/test.cpp | 2 +-
.../Security/CWE/CWE-119/SAMATE/tests.cpp | 2 +-
...Based_Buffer_Overflow__c_CWE129_fgets_01.c | 2 +-
.../CWE-134/SAMATE/console_fprintf_01/test.c | 23 -------------------
.../SAMATE/environment_fprintf_01/test.c | 23 -------------------
.../Security/CWE/CWE-190/SAMATE/examples.cpp | 2 +-
.../Security/CWE/CWE-197/SAMATE/tests.cpp | 2 +-
.../Security/CWE/CWE-497/SAMATE/tests.c | 4 ++--
.../CWE-676/SAMATE/DangerousUseOfCin/test.cpp | 2 +-
.../Security/CWE/CWE-772/SAMATE/tests.cpp | 2 +-
12 files changed, 11 insertions(+), 57 deletions(-)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
index 216258fd707..de67a38fd3e 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
@@ -1,6 +1,6 @@
/**
* This test case is closely based on CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
- * from the SAMATE test suite.
+ * from the SAMATE Juliet test suite.
*/
#define NULL (0)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
index 4b9c1343f6c..3a78acdb3a0 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
@@ -1,6 +1,6 @@
//semmle-extractor-options: --edg --target --edg win64
-// A selection of tests from the samate framework for rule CWE-78.
+// A selection of tests from the SAMATE Juliet framework for rule CWE-78.
// library types, functions etc
#define NULL (0)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp
index cf1f98e1d53..299e0372d4a 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/test.cpp
@@ -1,4 +1,4 @@
-// Samate test cases for CWE-114
+// Some SAMATE Juliet test cases for CWE-114.
typedef unsigned long size_t;
typedef unsigned int BOOL;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
index 364ef56b331..11510f5ef3c 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
@@ -1,6 +1,6 @@
//semmle-extractor-options: --edg --target --edg win64
-// A sample of tests from the samate framework for rule CWE-119.
+// A sample of tests from the SAMATE Juliet framework for rule CWE-119.
// library types, functions etc
typedef unsigned long size_t;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
index 911e9987472..2092902b665 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
@@ -1,4 +1,4 @@
-// Snippet from an external (samate) test case for rule CWE-122 / CWE-129
+// Snippet from a SAMATE Juliet test case for rule CWE-122 / CWE-129
// CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c
typedef unsigned long size_t;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
index af6affbd1ac..d3d06c47087 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
@@ -3,29 +3,6 @@
// Associated with CWE-134: Uncontrolled format string. http://cwe.mitre.org/data/definitions/134.html
// Examples amended to have all function declarations inlined.
-/* TEMPLATE GENERATED TESTCASE FILE
-Filename: CWE134_Uncontrolled_Format_String__char_console_fprintf_01.c
-Label Definition File: CWE134_Uncontrolled_Format_String.label.xml
-Template File: sources-sinks-01.tmpl.c
-*/
-/*
- * @description
- * CWE: 134 Uncontrolled Format String
- * BadSource: console Read input from the console
- * GoodSource: Copy a fixed string into data
- * Sinks: fprintf
- * GoodSink: fprintf with "%s" as the second argument and data as the third
- * BadSink : fprintf with data as the second argument
- * Flow Variant: 01 Baseline
- *
- * */
-
-// Replaced with inlined functions
-//#include "std_testcase.h"
-//
-//#ifndef _WIN32
-//# include
-//#endif
#define NULL 0
typedef unsigned long size_t;
typedef struct {} FILE;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
index 753183ca31a..2d92469b0e5 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
@@ -3,29 +3,6 @@
// Associated with CWE-134: Uncontrolled format string. http://cwe.mitre.org/data/definitions/134.html
// Examples amended to have all function declarations inlined.
-/* TEMPLATE GENERATED TESTCASE FILE
-Filename: CWE134_Uncontrolled_Format_String__char_environment_fprintf_01.c
-Label Definition File: CWE134_Uncontrolled_Format_String.label.xml
-Template File: sources-sinks-01.tmpl.c
-*/
-/*
- * @description
- * CWE: 134 Uncontrolled Format String
- * BadSource: environment Read input from an environment variable
- * GoodSource: Copy a fixed string into data
- * Sinks: fprintf
- * GoodSink: fprintf with "%s" as the second argument and data as the third
- * BadSink : fprintf with data as the second argument
- * Flow Variant: 01 Baseline
- *
- * */
-
-// Replaced with inlined functions
-//#include "std_testcase.h"
-//
-//#ifndef _WIN32
-//# include
-//#endif
#define NULL 0
typedef struct {} FILE;
typedef unsigned long size_t;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp
index 7a824748861..b2cdbbe7133 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/examples.cpp
@@ -1,4 +1,4 @@
-// External test cases for rule CWE-190.
+// Some SAMATE Juliet test cases for rule CWE-190.
///// Library functions //////
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
index f183df39eab..79f9a79c97f 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
@@ -1,6 +1,6 @@
//semmle-extractor-options: --edg --target --edg win64
-// A selection of tests from the samate framework for rule CWE-197.
+// A selection of tests from the SAMATE Juliet framework for rule CWE-197.
// library types, functions etc
typedef struct {} FILE;
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c
index 03248393f88..4b1df2a96e1 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/tests.c
@@ -1,4 +1,4 @@
-// Semmle test cases for rule CWE-497
+// SAMATE Juliet test cases for rule CWE-497.
// library functions etc
typedef struct {} FILE;
@@ -69,4 +69,4 @@ void CWE535_Info_Exposure_Shell_Error__w32_char_01_bad()
/* FLAW: Write sensitive data to stderr */
fprintf(stderr, "User attempted access with password: %s\n", password);
}
-}
\ No newline at end of file
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
index 708ba5f05f9..7d01bd5929d 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
@@ -1,4 +1,4 @@
-// Samate test case for rule DangerousUseOfCin.ql / CWE-676
+// SAMATE Juliet test case for rule DangerousUseOfCin.ql / CWE-676.
// --- library types, functions etc ---
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp
index 01e63310396..e7b889deb08 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/tests.cpp
@@ -1,4 +1,4 @@
-// Sample of samate tests for CWE-772.
+// Sample of SAMATE Juliet tests for CWE-772.
// --- library types, functions etc ---
From d1ab2d2e8cdca4081cd3f1f45f3c851e17838e8e Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 1 Sep 2021 19:11:31 +0100
Subject: [PATCH 106/459] C++: Remove some irrelevant macro logic and main
functions.
---
..._Path_Traversal__char_console_fopen_11.cpp | 33 --------------
.../CWE-134/SAMATE/console_fprintf_01/test.c | 41 ------------------
.../SAMATE/environment_fprintf_01/test.c | 43 -------------------
3 files changed, 117 deletions(-)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
index de67a38fd3e..db4d0e7b8a2 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
@@ -7,7 +7,6 @@
typedef size_t time_t;
time_t time(time_t *timer);
-void srand(unsigned int seed);
typedef struct {} FILE;
extern FILE *stdin;
@@ -130,35 +129,3 @@ static void goodG2B2()
}
}
}
-
-void good()
-{
- goodG2B1();
- goodG2B2();
-}
-
-} /* close namespace */
-
-/* Below is the main(). It is only used when building this testcase on
- its own for testing or for building a binary to use in testing binary
- analysis tools. It is not used when compiling all the testcases as one
- application, which is how source code analysis tools are tested. */
-
-using namespace CWE23_Relative_Path_Traversal__char_console_fopen_11; /* so that we can use good and bad easily */
-
-int main(int argc, char * argv[])
-{
- /* seed randomness */
- srand( (unsigned)time(NULL) );
-#ifndef OMITGOOD
- printLine("Calling good()...");
- good();
- printLine("Finished good()");
-#endif /* OMITGOOD */
-#ifndef OMITBAD
- printLine("Calling bad()...");
- bad();
- printLine("Finished bad()");
-#endif /* OMITBAD */
- return 0;
-}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
index d3d06c47087..1af281b9f42 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
@@ -12,12 +12,9 @@ size_t strlen(const char *s);
char *fgets(char *s, int n, FILE *stream);
int fprintf(FILE *stream, const char *format, ...);
char *strcpy(char *s1, const char *s2);
-void srand(unsigned int seed);
void printLine(char *);
-#ifndef OMITBAD
-
void CWE134_Uncontrolled_Format_String__char_console_fprintf_01_bad()
{
char * data;
@@ -52,10 +49,6 @@ void CWE134_Uncontrolled_Format_String__char_console_fprintf_01_bad()
fprintf(stdout, data);
}
-#endif /* OMITBAD */
-
-#ifndef OMITGOOD
-
/* goodG2B uses the GoodSource with the BadSink */
static void goodG2B()
{
@@ -102,37 +95,3 @@ static void goodB2G()
/* FIX: Specify the format disallowing a format string vulnerability */
fprintf(stdout, "%s\n", data);
}
-
-void CWE134_Uncontrolled_Format_String__char_console_fprintf_01_good()
-{
- goodG2B();
- goodB2G();
-}
-
-#endif /* OMITGOOD */
-
-/* Below is the main(). It is only used when building this testcase on
- its own for testing or for building a binary to use in testing binary
- analysis tools. It is not used when compiling all the testcases as one
- application, which is how source code analysis tools are tested. */
-
-#ifdef INCLUDEMAIN
-
-int main(int argc, char * argv[])
-{
- /* seed randomness */
- srand( (unsigned)time(NULL) );
-#ifndef OMITGOOD
- printLine("Calling good()...");
- CWE134_Uncontrolled_Format_String__char_console_fprintf_01_good();
- printLine("Finished good()");
-#endif /* OMITGOOD */
-#ifndef OMITBAD
- printLine("Calling bad()...");
- CWE134_Uncontrolled_Format_String__char_console_fprintf_01_bad();
- printLine("Finished bad()");
-#endif /* OMITBAD */
- return 0;
-}
-
-#endif
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
index 2d92469b0e5..f759a9d8e61 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
@@ -7,20 +7,15 @@
typedef struct {} FILE;
typedef unsigned long size_t;
extern FILE * stdout;
-void srand(unsigned int seed);
size_t strlen(const char *s);
char *getenv(const char *name);
char *strcpy(char *s1, const char *s2);
char *strncat(char *s1, const char *s2, size_t n);
int fprintf(FILE *stream, const char *format, ...);
-void printLine(char *);
-
#define ENV_VARIABLE "ADD"
#define GETENV getenv
-#ifndef OMITBAD
-
void CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_bad()
{
char * data;
@@ -41,10 +36,6 @@ void CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_bad()
fprintf(stdout, data);
}
-#endif /* OMITBAD */
-
-#ifndef OMITGOOD
-
/* goodG2B uses the GoodSource with the BadSink */
static void goodG2B()
{
@@ -77,37 +68,3 @@ static void goodB2G()
/* FIX: Specify the format disallowing a format string vulnerability */
fprintf(stdout, "%s\n", data);
}
-
-void CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_good()
-{
- goodG2B();
- goodB2G();
-}
-
-#endif /* OMITGOOD */
-
-/* Below is the main(). It is only used when building this testcase on
- its own for testing or for building a binary to use in testing binary
- analysis tools. It is not used when compiling all the testcases as one
- application, which is how source code analysis tools are tested. */
-
-#ifdef INCLUDEMAIN
-
-int main(int argc, char * argv[])
-{
- /* seed randomness */
- srand( (unsigned)time(NULL) );
-#ifndef OMITGOOD
- printLine("Calling good()...");
- CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_good();
- printLine("Finished good()");
-#endif /* OMITGOOD */
-#ifndef OMITBAD
- printLine("Calling bad()...");
- CWE134_Uncontrolled_Format_String__char_environment_fprintf_01_bad();
- printLine("Finished bad()");
-#endif /* OMITBAD */
- return 0;
-}
-
-#endif
From f755659f5deb4ba6b92af545a64ff73188bcf1f4 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 1 Sep 2021 19:31:53 +0100
Subject: [PATCH 107/459] C++: More directory structure consistency / cleanup.
---
...ve_Path_Traversal__char_console_fopen_11.cpp | 0
.../{ => TaintedPath}/TaintedPath.expected | 0
.../SAMATE/{ => TaintedPath}/TaintedPath.qlref | 0
.../{ => ExecTainted}/ExecTainted.expected | 0
.../SAMATE/{ => ExecTainted}/ExecTainted.qlref | 0
.../CWE-078/SAMATE/{ => ExecTainted}/tests.cpp | 0
...pected => UncontrolledFormatString.expected} | 0
...est.qlref => UncontrolledFormatString.qlref} | 0
... char_connect_socket_w32_vsnprintf_01_bad.c} | 0
.../test.c => char_console_fprintf_01_bad.c} | 0
...test.c => char_environment_fprintf_01_bad.c} | 0
.../SAMATE/console_fprintf_01/test.expected | 17 -----------------
.../SAMATE/console_fprintf_01/test.qlref | 1 -
.../SAMATE/environment_fprintf_01/test.expected | 17 -----------------
.../SAMATE/environment_fprintf_01/test.qlref | 1 -
.../IntegerOverflowTainted.expected | 0
.../IntegerOverflowTainted.qlref | 0
.../{ => IntegerOverflowTainted}/tests.cpp | 0
18 files changed, 36 deletions(-)
rename cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/{ => TaintedPath}/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/{ => TaintedPath}/TaintedPath.expected (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/{ => TaintedPath}/TaintedPath.qlref (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/{ => ExecTainted}/ExecTainted.expected (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/{ => ExecTainted}/ExecTainted.qlref (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/{ => ExecTainted}/tests.cpp (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/{connect_socket_w32_vsnprintf_01/test.expected => UncontrolledFormatString.expected} (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/{connect_socket_w32_vsnprintf_01/test.qlref => UncontrolledFormatString.qlref} (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/{connect_socket_w32_vsnprintf_01/test.c => char_connect_socket_w32_vsnprintf_01_bad.c} (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/{console_fprintf_01/test.c => char_console_fprintf_01_bad.c} (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/{environment_fprintf_01/test.c => char_environment_fprintf_01_bad.c} (100%)
delete mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected
delete mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref
delete mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected
delete mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref
rename cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/{ => IntegerOverflowTainted}/IntegerOverflowTainted.expected (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/{ => IntegerOverflowTainted}/IntegerOverflowTainted.qlref (100%)
rename cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/{ => IntegerOverflowTainted}/tests.cpp (100%)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
rename to cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/TaintedPath.expected
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.expected
rename to cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/TaintedPath.expected
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/TaintedPath.qlref
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath.qlref
rename to cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/TaintedPath.qlref
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.expected
rename to cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.qlref
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted.qlref
rename to cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.qlref
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/tests.cpp
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/tests.cpp
rename to cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/tests.cpp
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.expected
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.expected
rename to cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.expected
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.qlref
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.qlref
rename to cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.qlref
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/char_connect_socket_w32_vsnprintf_01_bad.c
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/connect_socket_w32_vsnprintf_01/test.c
rename to cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/char_connect_socket_w32_vsnprintf_01_bad.c
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/char_console_fprintf_01_bad.c
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.c
rename to cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/char_console_fprintf_01_bad.c
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/char_environment_fprintf_01_bad.c
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.c
rename to cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/char_environment_fprintf_01_bad.c
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected
deleted file mode 100644
index 5e6af965119..00000000000
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.expected
+++ /dev/null
@@ -1,17 +0,0 @@
-edges
-| test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | (const char *)... |
-| test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | data |
-| test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | data indirection |
-| test.c:56:23:56:35 | fgets output argument | test.c:75:21:75:24 | (const char *)... |
-| test.c:56:23:56:35 | fgets output argument | test.c:75:21:75:24 | data |
-| test.c:56:23:56:35 | fgets output argument | test.c:75:21:75:24 | data indirection |
-nodes
-| test.c:56:23:56:35 | ... + ... | semmle.label | ... + ... |
-| test.c:56:23:56:35 | fgets output argument | semmle.label | fgets output argument |
-| test.c:75:21:75:24 | (const char *)... | semmle.label | (const char *)... |
-| test.c:75:21:75:24 | (const char *)... | semmle.label | (const char *)... |
-| test.c:75:21:75:24 | data | semmle.label | data |
-| test.c:75:21:75:24 | data indirection | semmle.label | data indirection |
-| test.c:75:21:75:24 | data indirection | semmle.label | data indirection |
-#select
-| test.c:75:21:75:24 | data | test.c:56:23:56:35 | ... + ... | test.c:75:21:75:24 | data | The value of this argument may come from $@ and is being used as a formatting argument to fprintf(format) | test.c:56:23:56:35 | ... + ... | fgets |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref
deleted file mode 100644
index 079e0c8a7c0..00000000000
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/console_fprintf_01/test.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-134/UncontrolledFormatString.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected
deleted file mode 100644
index 122dff5b3f0..00000000000
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.expected
+++ /dev/null
@@ -1,17 +0,0 @@
-edges
-| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | (const char *)... |
-| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | (const char *)... |
-| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data |
-| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data |
-| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data indirection |
-| test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data indirection |
-nodes
-| test.c:59:30:59:35 | call to getenv | semmle.label | call to getenv |
-| test.c:59:30:59:35 | call to getenv | semmle.label | call to getenv |
-| test.c:68:21:68:24 | (const char *)... | semmle.label | (const char *)... |
-| test.c:68:21:68:24 | (const char *)... | semmle.label | (const char *)... |
-| test.c:68:21:68:24 | data | semmle.label | data |
-| test.c:68:21:68:24 | data indirection | semmle.label | data indirection |
-| test.c:68:21:68:24 | data indirection | semmle.label | data indirection |
-#select
-| test.c:68:21:68:24 | data | test.c:59:30:59:35 | call to getenv | test.c:68:21:68:24 | data | The value of this argument may come from $@ and is being used as a formatting argument to fprintf(format) | test.c:59:30:59:35 | call to getenv | getenv |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref
deleted file mode 100644
index 079e0c8a7c0..00000000000
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/environment_fprintf_01/test.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Security/CWE/CWE-134/UncontrolledFormatString.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.expected
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.expected
rename to cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.expected
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.qlref
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted.qlref
rename to cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.qlref
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/tests.cpp
similarity index 100%
rename from cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/tests.cpp
rename to cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/tests.cpp
From d73604d1c5f6387c7d0531421c15cb8064d71c9d Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Sep 2021 11:35:30 +0100
Subject: [PATCH 108/459] C++: Fix a few glitches and accept line number
changes in expected files.
---
..._Path_Traversal__char_console_fopen_11.cpp | 3 +
.../SAMATE/ExecTainted/ExecTainted.expected | 2 +-
.../CWE/CWE-078/SAMATE/ExecTainted/tests.cpp | 2 +-
.../UncontrolledProcessOperation.expected | 54 +++++++++---------
.../CWE-119/SAMATE/BadlyBoundedWrite.expected | 8 +--
.../CWE-119/SAMATE/OverflowBuffer.expected | 38 ++++++-------
.../CWE-119/SAMATE/OverflowStatic.expected | 4 +-
.../SAMATE/StrncpyFlippedArgs.expected | 6 +-
.../Security/CWE/CWE-119/SAMATE/tests.cpp | 2 -
.../ImproperArrayIndexValidation.expected | 2 +-
.../SAMATE/UncontrolledFormatString.expected | 56 ++++++++++++++-----
.../IntegerOverflowTainted.expected | 2 +-
.../CWE-497/SAMATE/ExposedSystemData.expected | 2 +-
.../CWE/CWE-497/SAMATE/OutputWrite.expected | 14 ++---
.../DangerousUseOfCin.expected | 2 +-
.../CWE-676/SAMATE/DangerousUseOfCin/test.cpp | 18 +++++-
.../CWE-772/SAMATE/FileNeverClosed.expected | 6 +-
.../SAMATE/MemoryMayNotBeFreed.expected | 4 +-
.../CWE-772/SAMATE/MemoryNeverFreed.expected | 4 +-
19 files changed, 135 insertions(+), 94 deletions(-)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
index db4d0e7b8a2..876584c5117 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-022/SAMATE/TaintedPath/CWE23_Relative_Path_Traversal__char_console_fopen_11.cpp
@@ -5,6 +5,7 @@
#define NULL (0)
+typedef unsigned long size_t;
typedef size_t time_t;
time_t time(time_t *timer);
@@ -129,3 +130,5 @@ static void goodG2B2()
}
}
}
+
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected
index 7a7858134ab..6f011708465 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/ExecTainted.expected
@@ -1 +1 @@
-| tests.cpp:55:16:55:19 | data | This argument to an OS command is derived from $@ and then passed to system(string) | tests.cpp:35:34:35:39 | call to getenv | user input (getenv) |
+| tests.cpp:53:16:53:19 | data | This argument to an OS command is derived from $@ and then passed to system(string) | tests.cpp:33:34:33:39 | call to getenv | user input (getenv) |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/tests.cpp
index 3a78acdb3a0..80f8221d903 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/tests.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-078/SAMATE/ExecTainted/tests.cpp
@@ -6,7 +6,7 @@
#define NULL (0)
typedef unsigned long size_t;
size_t strlen(const char *s);
-char *strncat(char *s1, const char *s2, size_t n);n);
+char *strncat(char *s1, const char *s2, size_t n);
char *getenv(const char *name);
int system(const char *string);
void exit(int status);
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected
index bcaff619bd2..1aa02436ddd 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-114/SAMATE/UncontrolledProcessOperation/UncontrolledProcessOperation.expected
@@ -1,30 +1,30 @@
edges
-| test.cpp:35:73:35:76 | *data | test.cpp:41:32:41:35 | (LPCSTR)... |
-| test.cpp:35:73:35:76 | *data | test.cpp:41:32:41:35 | data |
-| test.cpp:35:73:35:76 | *data | test.cpp:41:32:41:35 | data indirection |
-| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | (LPCSTR)... |
-| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | data |
-| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | data |
-| test.cpp:35:73:35:76 | data | test.cpp:41:32:41:35 | data indirection |
-| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:17:71:22 | data |
-| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:17:71:22 | data |
-| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:24:71:27 | data indirection |
-| test.cpp:62:30:62:35 | call to getenv | test.cpp:71:24:71:27 | data indirection |
-| test.cpp:71:17:71:22 | data | test.cpp:35:73:35:76 | data |
-| test.cpp:71:24:71:27 | data indirection | test.cpp:35:73:35:76 | *data |
+| test.cpp:37:73:37:76 | *data | test.cpp:43:32:43:35 | (LPCSTR)... |
+| test.cpp:37:73:37:76 | *data | test.cpp:43:32:43:35 | data |
+| test.cpp:37:73:37:76 | *data | test.cpp:43:32:43:35 | data indirection |
+| test.cpp:37:73:37:76 | data | test.cpp:43:32:43:35 | (LPCSTR)... |
+| test.cpp:37:73:37:76 | data | test.cpp:43:32:43:35 | data |
+| test.cpp:37:73:37:76 | data | test.cpp:43:32:43:35 | data |
+| test.cpp:37:73:37:76 | data | test.cpp:43:32:43:35 | data indirection |
+| test.cpp:64:30:64:35 | call to getenv | test.cpp:73:17:73:22 | data |
+| test.cpp:64:30:64:35 | call to getenv | test.cpp:73:17:73:22 | data |
+| test.cpp:64:30:64:35 | call to getenv | test.cpp:73:24:73:27 | data indirection |
+| test.cpp:64:30:64:35 | call to getenv | test.cpp:73:24:73:27 | data indirection |
+| test.cpp:73:17:73:22 | data | test.cpp:37:73:37:76 | data |
+| test.cpp:73:24:73:27 | data indirection | test.cpp:37:73:37:76 | *data |
nodes
-| test.cpp:35:73:35:76 | *data | semmle.label | *data |
-| test.cpp:35:73:35:76 | data | semmle.label | data |
-| test.cpp:41:32:41:35 | (LPCSTR)... | semmle.label | (LPCSTR)... |
-| test.cpp:41:32:41:35 | (LPCSTR)... | semmle.label | (LPCSTR)... |
-| test.cpp:41:32:41:35 | data | semmle.label | data |
-| test.cpp:41:32:41:35 | data | semmle.label | data |
-| test.cpp:41:32:41:35 | data | semmle.label | data |
-| test.cpp:41:32:41:35 | data indirection | semmle.label | data indirection |
-| test.cpp:41:32:41:35 | data indirection | semmle.label | data indirection |
-| test.cpp:62:30:62:35 | call to getenv | semmle.label | call to getenv |
-| test.cpp:62:30:62:35 | call to getenv | semmle.label | call to getenv |
-| test.cpp:71:17:71:22 | data | semmle.label | data |
-| test.cpp:71:24:71:27 | data indirection | semmle.label | data indirection |
+| test.cpp:37:73:37:76 | *data | semmle.label | *data |
+| test.cpp:37:73:37:76 | data | semmle.label | data |
+| test.cpp:43:32:43:35 | (LPCSTR)... | semmle.label | (LPCSTR)... |
+| test.cpp:43:32:43:35 | (LPCSTR)... | semmle.label | (LPCSTR)... |
+| test.cpp:43:32:43:35 | data | semmle.label | data |
+| test.cpp:43:32:43:35 | data | semmle.label | data |
+| test.cpp:43:32:43:35 | data | semmle.label | data |
+| test.cpp:43:32:43:35 | data indirection | semmle.label | data indirection |
+| test.cpp:43:32:43:35 | data indirection | semmle.label | data indirection |
+| test.cpp:64:30:64:35 | call to getenv | semmle.label | call to getenv |
+| test.cpp:64:30:64:35 | call to getenv | semmle.label | call to getenv |
+| test.cpp:73:17:73:22 | data | semmle.label | data |
+| test.cpp:73:24:73:27 | data indirection | semmle.label | data indirection |
#select
-| test.cpp:41:32:41:35 | data | test.cpp:62:30:62:35 | call to getenv | test.cpp:41:32:41:35 | data | The value of this argument may come from $@ and is being passed to LoadLibraryA | test.cpp:62:30:62:35 | call to getenv | call to getenv |
+| test.cpp:43:32:43:35 | data | test.cpp:64:30:64:35 | call to getenv | test.cpp:43:32:43:35 | data | The value of this argument may come from $@ and is being passed to LoadLibraryA | test.cpp:64:30:64:35 | call to getenv | call to getenv |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected
index bd96138bb9f..bd60a176ca9 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/BadlyBoundedWrite.expected
@@ -1,4 +1,4 @@
-| tests.cpp:352:13:352:19 | call to strncat | This 'call to strncat' operation is limited to 100 bytes but the destination is only 50 bytes. |
-| tests.cpp:454:9:454:15 | call to wcsncpy | This 'call to wcsncpy' operation is limited to 198 bytes but the destination is only 100 bytes. |
-| tests.cpp:483:9:483:16 | call to swprintf | This 'call to swprintf' operation is limited to 200 bytes but the destination is only 100 bytes. |
-| tests.cpp:632:13:632:20 | call to swprintf | This 'call to swprintf' operation is limited to 200 bytes but the destination is only 100 bytes. |
+| tests.cpp:350:13:350:19 | call to strncat | This 'call to strncat' operation is limited to 100 bytes but the destination is only 50 bytes. |
+| tests.cpp:452:9:452:15 | call to wcsncpy | This 'call to wcsncpy' operation is limited to 396 bytes but the destination is only 200 bytes. |
+| tests.cpp:481:9:481:16 | call to swprintf | This 'call to swprintf' operation is limited to 400 bytes but the destination is only 200 bytes. |
+| tests.cpp:630:13:630:20 | call to swprintf | This 'call to swprintf' operation is limited to 400 bytes but the destination is only 200 bytes. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected
index 3621eb1d127..73f1b74db56 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowBuffer.expected
@@ -1,19 +1,19 @@
-| tests.cpp:47:9:47:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:34:10:34:18 | charFirst | destination buffer |
-| tests.cpp:62:9:62:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:34:10:34:18 | charFirst | destination buffer |
-| tests.cpp:173:9:173:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:166:20:166:25 | call to malloc | destination buffer |
-| tests.cpp:174:9:174:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:166:20:166:25 | call to malloc | array |
-| tests.cpp:194:9:194:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:183:10:183:22 | dataBadBuffer | destination buffer |
-| tests.cpp:194:9:194:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:187:12:187:24 | dataBadBuffer | destination buffer |
-| tests.cpp:195:9:195:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:183:10:183:22 | dataBadBuffer | array |
-| tests.cpp:195:9:195:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:187:12:187:24 | dataBadBuffer | array |
-| tests.cpp:214:9:214:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:203:36:203:41 | call to alloca | destination buffer |
-| tests.cpp:214:9:214:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:207:12:207:24 | dataBadBuffer | destination buffer |
-| tests.cpp:215:9:215:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:203:36:203:41 | call to alloca | array |
-| tests.cpp:215:9:215:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:207:12:207:24 | dataBadBuffer | array |
-| tests.cpp:239:9:239:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:223:36:223:41 | call to alloca | array |
-| tests.cpp:239:9:239:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:227:12:227:24 | dataBadBuffer | array |
-| tests.cpp:263:9:263:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:247:10:247:22 | dataBadBuffer | array |
-| tests.cpp:263:9:263:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:251:12:251:24 | dataBadBuffer | array |
-| tests.cpp:386:9:386:14 | call to memcpy | This 'memcpy' operation accesses 40 bytes but the $@ is only 10 bytes. | tests.cpp:382:19:382:24 | call to alloca | destination buffer |
-| tests.cpp:436:9:436:19 | access to array | This array indexing operation accesses byte offset 199 but the $@ is only 100 bytes. | tests.cpp:424:12:424:26 | new[] | array |
-| tests.cpp:455:9:455:19 | access to array | This array indexing operation accesses byte offset 199 but the $@ is only 100 bytes. | tests.cpp:447:12:447:26 | new[] | array |
+| tests.cpp:45:9:45:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:32:10:32:18 | charFirst | destination buffer |
+| tests.cpp:60:9:60:14 | call to memcpy | This 'memcpy' operation accesses 32 bytes but the $@ is only 16 bytes. | tests.cpp:32:10:32:18 | charFirst | destination buffer |
+| tests.cpp:171:9:171:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:164:20:164:25 | call to malloc | destination buffer |
+| tests.cpp:172:9:172:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:164:20:164:25 | call to malloc | array |
+| tests.cpp:192:9:192:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:181:10:181:22 | dataBadBuffer | destination buffer |
+| tests.cpp:192:9:192:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:185:12:185:24 | dataBadBuffer | destination buffer |
+| tests.cpp:193:9:193:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:181:10:181:22 | dataBadBuffer | array |
+| tests.cpp:193:9:193:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:185:12:185:24 | dataBadBuffer | array |
+| tests.cpp:212:9:212:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:201:36:201:41 | call to alloca | destination buffer |
+| tests.cpp:212:9:212:14 | call to memcpy | This 'memcpy' operation accesses 100 bytes but the $@ is only 50 bytes. | tests.cpp:205:12:205:24 | dataBadBuffer | destination buffer |
+| tests.cpp:213:9:213:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:201:36:201:41 | call to alloca | array |
+| tests.cpp:213:9:213:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:205:12:205:24 | dataBadBuffer | array |
+| tests.cpp:237:9:237:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:221:36:221:41 | call to alloca | array |
+| tests.cpp:237:9:237:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:225:12:225:24 | dataBadBuffer | array |
+| tests.cpp:261:9:261:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:245:10:245:22 | dataBadBuffer | array |
+| tests.cpp:261:9:261:19 | access to array | This array indexing operation accesses byte offset 99 but the $@ is only 50 bytes. | tests.cpp:249:12:249:24 | dataBadBuffer | array |
+| tests.cpp:384:9:384:14 | call to memcpy | This 'memcpy' operation accesses 40 bytes but the $@ is only 10 bytes. | tests.cpp:380:19:380:24 | call to alloca | destination buffer |
+| tests.cpp:434:9:434:19 | access to array | This array indexing operation accesses byte offset 399 but the $@ is only 200 bytes. | tests.cpp:422:12:422:26 | new[] | array |
+| tests.cpp:453:9:453:19 | access to array | This array indexing operation accesses byte offset 399 but the $@ is only 200 bytes. | tests.cpp:445:12:445:26 | new[] | array |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected
index e9cf380d0e6..ab9263b8544 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/OverflowStatic.expected
@@ -1,2 +1,2 @@
-| tests.cpp:47:51:47:72 | sizeof() | Potential buffer-overflow: 'charFirst' has size 16 not 32. |
-| tests.cpp:62:52:62:74 | sizeof() | Potential buffer-overflow: 'charFirst' has size 16 not 32. |
+| tests.cpp:45:51:45:72 | sizeof() | Potential buffer-overflow: 'charFirst' has size 16 not 32. |
+| tests.cpp:60:52:60:74 | sizeof() | Potential buffer-overflow: 'charFirst' has size 16 not 32. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected
index b376553baee..778adb97718 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/StrncpyFlippedArgs.expected
@@ -1,3 +1,3 @@
-| tests.cpp:292:13:292:19 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
-| tests.cpp:308:4:308:10 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
-| tests.cpp:454:9:454:15 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
+| tests.cpp:290:13:290:19 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
+| tests.cpp:306:4:306:10 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
+| tests.cpp:452:9:452:15 | call to wcsncpy | Potentially unsafe call to wcsncpy; third argument should be size of destination. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
index 11510f5ef3c..1c7f9ad60f5 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/SAMATE/tests.cpp
@@ -1,5 +1,3 @@
-//semmle-extractor-options: --edg --target --edg win64
-
// A sample of tests from the SAMATE Juliet framework for rule CWE-119.
// library types, functions etc
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected
index 357b583436b..008ff07b800 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-129/SAMATE/ImproperArrayIndexValidation/ImproperArrayIndexValidation.expected
@@ -1 +1 @@
-| CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c:51:20:51:23 | data | $@ flows to here and is used in an array indexing expression, potentially causing an invalid access. | CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c:29:19:29:29 | inputBuffer | User-provided value |
+| CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c:52:20:52:23 | data | $@ flows to here and is used in an array indexing expression, potentially causing an invalid access. | CWE122_Heap_Based_Buffer_Overflow__c_CWE129_fgets_01.c:30:19:30:29 | inputBuffer | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.expected
index 46fc6b5a61d..ba56fb478d6 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-134/SAMATE/UncontrolledFormatString.expected
@@ -1,17 +1,45 @@
edges
-| test.c:93:46:93:69 | recv output argument | test.c:124:15:124:18 | data |
-| test.c:93:46:93:69 | recv output argument | test.c:124:15:124:18 | data |
-| test.c:93:46:93:69 | recv output argument | test.c:124:15:124:18 | data indirection |
-| test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data |
-| test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data |
-| test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data indirection |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:46:94:69 | recv output argument | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:46:94:69 | recv output argument | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:46:94:69 | recv output argument | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data indirection |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:55:94:68 | ... + ... | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:55:94:68 | ... + ... | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:55:94:68 | ... + ... | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data indirection |
+| char_console_fprintf_01_bad.c:30:23:30:35 | ... + ... | char_console_fprintf_01_bad.c:49:21:49:24 | (const char *)... |
+| char_console_fprintf_01_bad.c:30:23:30:35 | ... + ... | char_console_fprintf_01_bad.c:49:21:49:24 | data |
+| char_console_fprintf_01_bad.c:30:23:30:35 | ... + ... | char_console_fprintf_01_bad.c:49:21:49:24 | data indirection |
+| char_console_fprintf_01_bad.c:30:23:30:35 | fgets output argument | char_console_fprintf_01_bad.c:49:21:49:24 | (const char *)... |
+| char_console_fprintf_01_bad.c:30:23:30:35 | fgets output argument | char_console_fprintf_01_bad.c:49:21:49:24 | data |
+| char_console_fprintf_01_bad.c:30:23:30:35 | fgets output argument | char_console_fprintf_01_bad.c:49:21:49:24 | data indirection |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | (const char *)... |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | (const char *)... |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | data |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | data |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | data indirection |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | data indirection |
nodes
-| test.c:93:46:93:69 | recv output argument | semmle.label | recv output argument |
-| test.c:93:55:93:68 | ... + ... | semmle.label | ... + ... |
-| test.c:124:15:124:18 | data | semmle.label | data |
-| test.c:124:15:124:18 | data | semmle.label | data |
-| test.c:124:15:124:18 | data | semmle.label | data |
-| test.c:124:15:124:18 | data indirection | semmle.label | data indirection |
-| test.c:124:15:124:18 | data indirection | semmle.label | data indirection |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:46:94:69 | recv output argument | semmle.label | recv output argument |
+| char_connect_socket_w32_vsnprintf_01_bad.c:94:55:94:68 | ... + ... | semmle.label | ... + ... |
+| char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data | semmle.label | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data | semmle.label | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data | semmle.label | data |
+| char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data indirection | semmle.label | data indirection |
+| char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data indirection | semmle.label | data indirection |
+| char_console_fprintf_01_bad.c:30:23:30:35 | ... + ... | semmle.label | ... + ... |
+| char_console_fprintf_01_bad.c:30:23:30:35 | fgets output argument | semmle.label | fgets output argument |
+| char_console_fprintf_01_bad.c:49:21:49:24 | (const char *)... | semmle.label | (const char *)... |
+| char_console_fprintf_01_bad.c:49:21:49:24 | (const char *)... | semmle.label | (const char *)... |
+| char_console_fprintf_01_bad.c:49:21:49:24 | data | semmle.label | data |
+| char_console_fprintf_01_bad.c:49:21:49:24 | data indirection | semmle.label | data indirection |
+| char_console_fprintf_01_bad.c:49:21:49:24 | data indirection | semmle.label | data indirection |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | semmle.label | call to getenv |
+| char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | semmle.label | call to getenv |
+| char_environment_fprintf_01_bad.c:36:21:36:24 | (const char *)... | semmle.label | (const char *)... |
+| char_environment_fprintf_01_bad.c:36:21:36:24 | (const char *)... | semmle.label | (const char *)... |
+| char_environment_fprintf_01_bad.c:36:21:36:24 | data | semmle.label | data |
+| char_environment_fprintf_01_bad.c:36:21:36:24 | data indirection | semmle.label | data indirection |
+| char_environment_fprintf_01_bad.c:36:21:36:24 | data indirection | semmle.label | data indirection |
#select
-| test.c:124:15:124:18 | data | test.c:93:55:93:68 | ... + ... | test.c:124:15:124:18 | data | The value of this argument may come from $@ and is being used as a formatting argument to badVaSink(data), which calls vsnprintf(format) | test.c:93:55:93:68 | ... + ... | recv |
+| char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data | char_connect_socket_w32_vsnprintf_01_bad.c:94:55:94:68 | ... + ... | char_connect_socket_w32_vsnprintf_01_bad.c:125:15:125:18 | data | The value of this argument may come from $@ and is being used as a formatting argument to badVaSink(data), which calls vsnprintf(format) | char_connect_socket_w32_vsnprintf_01_bad.c:94:55:94:68 | ... + ... | recv |
+| char_console_fprintf_01_bad.c:49:21:49:24 | data | char_console_fprintf_01_bad.c:30:23:30:35 | ... + ... | char_console_fprintf_01_bad.c:49:21:49:24 | data | The value of this argument may come from $@ and is being used as a formatting argument to fprintf(format) | char_console_fprintf_01_bad.c:30:23:30:35 | ... + ... | fgets |
+| char_environment_fprintf_01_bad.c:36:21:36:24 | data | char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | char_environment_fprintf_01_bad.c:36:21:36:24 | data | The value of this argument may come from $@ and is being used as a formatting argument to fprintf(format) | char_environment_fprintf_01_bad.c:27:30:27:35 | call to getenv | getenv |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.expected
index f71dbfde719..88b0b206cda 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-197/SAMATE/IntegerOverflowTainted/IntegerOverflowTainted.expected
@@ -1 +1 @@
-| tests.cpp:39:31:39:34 | data | $@ flows to here and is used in an expression which might overflow. | tests.cpp:58:27:58:31 | & ... | User-provided value |
+| tests.cpp:38:31:38:34 | data | $@ flows to here and is used in an expression which might overflow. | tests.cpp:57:27:57:31 | & ... | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
index 77a8ad6c533..b16a6cf1beb 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
@@ -1 +1 @@
-| tests.c:76:9:76:15 | call to fprintf | This operation exposes system data from $@. | tests.c:60:13:60:22 | call to LogonUserA | call to LogonUserA |
+| tests.c:71:9:71:15 | call to fprintf | This operation exposes system data from $@. | tests.c:55:13:55:22 | call to LogonUserA | call to LogonUserA |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
index 3f9eeda87db..9f2f7d6ee3f 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
@@ -1,7 +1,7 @@
-| tests.c:35:9:35:14 | call to printf | tests.c:35:16:35:21 | %s\n |
-| tests.c:35:9:35:14 | call to printf | tests.c:35:24:35:27 | line |
-| tests.c:49:13:49:21 | call to printLine | tests.c:49:23:49:38 | fgets() failed |
-| tests.c:68:13:68:21 | call to printLine | tests.c:68:23:68:52 | User logged in successfully. |
-| tests.c:73:13:73:21 | call to printLine | tests.c:73:23:73:40 | Unable to login. |
-| tests.c:76:9:76:15 | call to fprintf | tests.c:76:25:76:67 | User attempted access with password: %s\n |
-| tests.c:76:9:76:15 | call to fprintf | tests.c:76:70:76:77 | password |
+| tests.c:30:9:30:14 | call to printf | tests.c:30:16:30:21 | %s\n |
+| tests.c:30:9:30:14 | call to printf | tests.c:30:24:30:27 | line |
+| tests.c:44:13:44:21 | call to printLine | tests.c:44:23:44:38 | fgets() failed |
+| tests.c:63:13:63:21 | call to printLine | tests.c:63:23:63:52 | User logged in successfully. |
+| tests.c:68:13:68:21 | call to printLine | tests.c:68:23:68:40 | Unable to login. |
+| tests.c:71:9:71:15 | call to fprintf | tests.c:71:25:71:67 | User attempted access with password: %s\n |
+| tests.c:71:9:71:15 | call to fprintf | tests.c:71:70:71:77 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected
index 311f82ad99d..f5c0b85e28f 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/DangerousUseOfCin.expected
@@ -1 +1 @@
-| test.cpp:129:17:129:17 | call to operator>> | Use of 'cin' without specifying the length of the input may be dangerous. |
+| test.cpp:59:17:59:17 | call to operator>> | Use of 'cin' without specifying the length of the input may be dangerous. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
index 7d01bd5929d..704c2a87b3f 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-676/SAMATE/DangerousUseOfCin/test.cpp
@@ -2,18 +2,30 @@
// --- library types, functions etc ---
+typedef unsigned long size_t;
+
namespace std
{
- // --- std::string ---
+ // --- std::istream ---
// std::char_traits
template class char_traits;
- // --- std::istream ---
+ typedef size_t streamsize;
+
+ class ios_base {
+ public:
+ streamsize width(streamsize wide);
+ };
+
+ template >
+ class basic_ios : public ios_base {
+ public:
+ };
// std::basic_istream
template >
- class basic_istream /*: virtual public basic_ios - not needed for this test */ {
+ class basic_istream : virtual public basic_ios {
};
// operator>> std::basic_istream -> char*
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
index 31a7fe984bd..a0c7281a6f4 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
@@ -1,3 +1,3 @@
-| tests.cpp:248:12:248:16 | call to fopen | The file is never closed |
-| tests.cpp:280:12:280:15 | call to open | The file is never closed |
-| tests.cpp:306:12:306:21 | call to CreateFile | The file is never closed |
+| tests.cpp:223:12:223:16 | call to fopen | The file is never closed |
+| tests.cpp:255:12:255:15 | call to open | The file is never closed |
+| tests.cpp:281:12:281:21 | call to CreateFile | The file is never closed |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
index 006eeb5aa4c..b3d74dbf808 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
@@ -1,2 +1,2 @@
-| tests.cpp:226:31:226:36 | call to malloc | The memory allocated here may not be released at $@. | tests.cpp:240:1:240:1 | return ... | this exit point |
-| tests.cpp:353:5:353:68 | ... = ... | The memory allocated here may not be released at $@. | tests.cpp:361:1:361:1 | return ... | this exit point |
+| tests.cpp:201:31:201:36 | call to malloc | The memory allocated here may not be released at $@. | tests.cpp:215:1:215:1 | return ... | this exit point |
+| tests.cpp:328:5:328:68 | ... = ... | The memory allocated here may not be released at $@. | tests.cpp:336:1:336:1 | return ... | this exit point |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
index 9c72dced70d..21657b90ace 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
@@ -1,2 +1,2 @@
-| tests.cpp:99:20:99:26 | new | This memory is never freed |
-| tests.cpp:164:24:164:29 | call to malloc | This memory is never freed |
+| tests.cpp:74:20:74:26 | new | This memory is never freed |
+| tests.cpp:139:24:139:29 | call to malloc | This memory is never freed |
From a0b712d44ba736b53640cc0af6088a3a97f89268 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 2 Sep 2021 13:50:50 +0100
Subject: [PATCH 109/459] C++: Add notice about the SAMATE Juliet tests.
---
cpp/ql/test/query-tests/Security/CWE/notice.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 cpp/ql/test/query-tests/Security/CWE/notice.txt
diff --git a/cpp/ql/test/query-tests/Security/CWE/notice.txt b/cpp/ql/test/query-tests/Security/CWE/notice.txt
new file mode 100644
index 00000000000..07a3835d4c6
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/notice.txt
@@ -0,0 +1,2 @@
+
+Some of the the files in these tests contain source code copied or derived from the public domain "Juliet Test Suite for C/C++" (provided by NIST / SAMATE Team at https://samate.nist.gov/SARD/testsuite.php). Such tests are typically in subdirectories named "SAMATE".
From f2047ee4d03abaef73b8d9935bdf6d5d11ecba60 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 3 Sep 2021 09:13:26 +0100
Subject: [PATCH 110/459] C++: Actually fix expected files after layout
changes.
---
.../CWE-190/SAMATE/ArithmeticTainted.expected | 20 +--
.../SAMATE/ArithmeticUncontrolled.expected | 148 +++++++++---------
.../SAMATE/IntegerOverflowTainted.expected | 2 +-
.../CWE-497/SAMATE/ExposedSystemData.expected | 2 +-
.../CWE/CWE-497/SAMATE/OutputWrite.expected | 14 +-
.../CWE-772/SAMATE/FileNeverClosed.expected | 6 +-
.../SAMATE/MemoryMayNotBeFreed.expected | 4 +-
.../CWE-772/SAMATE/MemoryNeverFreed.expected | 4 +-
8 files changed, 100 insertions(+), 100 deletions(-)
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected
index 858be731077..be606d4e1dd 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticTainted.expected
@@ -1,13 +1,13 @@
edges
-| examples.cpp:62:26:62:30 | & ... | examples.cpp:65:11:65:14 | data |
-| examples.cpp:62:26:62:30 | & ... | examples.cpp:65:11:65:14 | data |
-| examples.cpp:62:26:62:30 | fscanf output argument | examples.cpp:65:11:65:14 | data |
-| examples.cpp:62:26:62:30 | fscanf output argument | examples.cpp:65:11:65:14 | data |
+| examples.cpp:63:26:63:30 | & ... | examples.cpp:66:11:66:14 | data |
+| examples.cpp:63:26:63:30 | & ... | examples.cpp:66:11:66:14 | data |
+| examples.cpp:63:26:63:30 | fscanf output argument | examples.cpp:66:11:66:14 | data |
+| examples.cpp:63:26:63:30 | fscanf output argument | examples.cpp:66:11:66:14 | data |
nodes
-| examples.cpp:62:26:62:30 | & ... | semmle.label | & ... |
-| examples.cpp:62:26:62:30 | fscanf output argument | semmle.label | fscanf output argument |
-| examples.cpp:65:11:65:14 | data | semmle.label | data |
-| examples.cpp:65:11:65:14 | data | semmle.label | data |
-| examples.cpp:65:11:65:14 | data | semmle.label | data |
+| examples.cpp:63:26:63:30 | & ... | semmle.label | & ... |
+| examples.cpp:63:26:63:30 | fscanf output argument | semmle.label | fscanf output argument |
+| examples.cpp:66:11:66:14 | data | semmle.label | data |
+| examples.cpp:66:11:66:14 | data | semmle.label | data |
+| examples.cpp:66:11:66:14 | data | semmle.label | data |
#select
-| examples.cpp:65:11:65:14 | data | examples.cpp:62:26:62:30 | & ... | examples.cpp:65:11:65:14 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:62:26:62:30 | & ... | User-provided value |
+| examples.cpp:66:11:66:14 | data | examples.cpp:63:26:63:30 | & ... | examples.cpp:66:11:66:14 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:63:26:63:30 | & ... | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected
index 14f313e62cf..9ae2ff0ecff 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/ArithmeticUncontrolled.expected
@@ -1,77 +1,77 @@
edges
-| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
-| examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
-| examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data |
+| examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data |
+| examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data |
nodes
-| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:21:26:21:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:21:26:21:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:24:31:24:34 | data | semmle.label | data |
-| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:34:26:34:33 | (unsigned int)... | semmle.label | (unsigned int)... |
-| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:34:26:34:33 | call to rand | semmle.label | call to rand |
-| examples.cpp:37:9:37:12 | data | semmle.label | data |
+| examples.cpp:22:26:22:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:22:26:22:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:22:26:22:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:22:26:22:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:22:26:22:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:22:26:22:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:22:26:22:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:22:26:22:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:22:26:22:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:22:26:22:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:22:26:22:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:22:26:22:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:25:31:25:34 | data | semmle.label | data |
+| examples.cpp:35:26:35:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:35:26:35:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:35:26:35:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:35:26:35:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:35:26:35:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:35:26:35:33 | (unsigned int)... | semmle.label | (unsigned int)... |
+| examples.cpp:35:26:35:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:35:26:35:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:35:26:35:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:35:26:35:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:35:26:35:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:35:26:35:33 | call to rand | semmle.label | call to rand |
+| examples.cpp:38:9:38:12 | data | semmle.label | data |
#select
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | (unsigned int)... | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:24:31:24:34 | data | examples.cpp:21:26:21:33 | call to rand | examples.cpp:24:31:24:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:21:26:21:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | (unsigned int)... | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
-| examples.cpp:37:9:37:12 | data | examples.cpp:34:26:34:33 | call to rand | examples.cpp:37:9:37:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:34:26:34:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | (unsigned int)... | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:25:31:25:34 | data | examples.cpp:22:26:22:33 | call to rand | examples.cpp:25:31:25:34 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:22:26:22:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | (unsigned int)... | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
+| examples.cpp:38:9:38:12 | data | examples.cpp:35:26:35:33 | call to rand | examples.cpp:38:9:38:12 | data | $@ flows to here and is used in arithmetic, potentially causing an underflow. | examples.cpp:35:26:35:33 | call to rand | Uncontrolled value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected
index 9a9a0eba1ab..9d1fa5388ff 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/SAMATE/IntegerOverflowTainted.expected
@@ -1 +1 @@
-| examples.cpp:65:9:65:14 | -- ... | $@ flows to here and is used in an expression which might overflow negatively. | examples.cpp:62:26:62:30 | & ... | User-provided value |
+| examples.cpp:66:9:66:14 | -- ... | $@ flows to here and is used in an expression which might overflow negatively. | examples.cpp:63:26:63:30 | & ... | User-provided value |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
index b16a6cf1beb..ffd6f77205e 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/ExposedSystemData.expected
@@ -1 +1 @@
-| tests.c:71:9:71:15 | call to fprintf | This operation exposes system data from $@. | tests.c:55:13:55:22 | call to LogonUserA | call to LogonUserA |
+| tests.c:70:9:70:15 | call to fprintf | This operation exposes system data from $@. | tests.c:54:13:54:22 | call to LogonUserA | call to LogonUserA |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
index 9f2f7d6ee3f..fe7e5b34c77 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-497/SAMATE/OutputWrite.expected
@@ -1,7 +1,7 @@
-| tests.c:30:9:30:14 | call to printf | tests.c:30:16:30:21 | %s\n |
-| tests.c:30:9:30:14 | call to printf | tests.c:30:24:30:27 | line |
-| tests.c:44:13:44:21 | call to printLine | tests.c:44:23:44:38 | fgets() failed |
-| tests.c:63:13:63:21 | call to printLine | tests.c:63:23:63:52 | User logged in successfully. |
-| tests.c:68:13:68:21 | call to printLine | tests.c:68:23:68:40 | Unable to login. |
-| tests.c:71:9:71:15 | call to fprintf | tests.c:71:25:71:67 | User attempted access with password: %s\n |
-| tests.c:71:9:71:15 | call to fprintf | tests.c:71:70:71:77 | password |
+| tests.c:29:9:29:14 | call to printf | tests.c:29:16:29:21 | %s\n |
+| tests.c:29:9:29:14 | call to printf | tests.c:29:24:29:27 | line |
+| tests.c:43:13:43:21 | call to printLine | tests.c:43:23:43:38 | fgets() failed |
+| tests.c:62:13:62:21 | call to printLine | tests.c:62:23:62:52 | User logged in successfully. |
+| tests.c:67:13:67:21 | call to printLine | tests.c:67:23:67:40 | Unable to login. |
+| tests.c:70:9:70:15 | call to fprintf | tests.c:70:25:70:67 | User attempted access with password: %s\n |
+| tests.c:70:9:70:15 | call to fprintf | tests.c:70:70:70:77 | password |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
index a0c7281a6f4..c328cee7ec9 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/FileNeverClosed.expected
@@ -1,3 +1,3 @@
-| tests.cpp:223:12:223:16 | call to fopen | The file is never closed |
-| tests.cpp:255:12:255:15 | call to open | The file is never closed |
-| tests.cpp:281:12:281:21 | call to CreateFile | The file is never closed |
+| tests.cpp:220:12:220:16 | call to fopen | The file is never closed |
+| tests.cpp:252:12:252:15 | call to open | The file is never closed |
+| tests.cpp:278:12:278:21 | call to CreateFile | The file is never closed |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
index b3d74dbf808..11a19a071d0 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryMayNotBeFreed.expected
@@ -1,2 +1,2 @@
-| tests.cpp:201:31:201:36 | call to malloc | The memory allocated here may not be released at $@. | tests.cpp:215:1:215:1 | return ... | this exit point |
-| tests.cpp:328:5:328:68 | ... = ... | The memory allocated here may not be released at $@. | tests.cpp:336:1:336:1 | return ... | this exit point |
+| tests.cpp:198:31:198:36 | call to malloc | The memory allocated here may not be released at $@. | tests.cpp:212:1:212:1 | return ... | this exit point |
+| tests.cpp:325:5:325:68 | ... = ... | The memory allocated here may not be released at $@. | tests.cpp:333:1:333:1 | return ... | this exit point |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
index 21657b90ace..087186d9dc6 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-772/SAMATE/MemoryNeverFreed.expected
@@ -1,2 +1,2 @@
-| tests.cpp:74:20:74:26 | new | This memory is never freed |
-| tests.cpp:139:24:139:29 | call to malloc | This memory is never freed |
+| tests.cpp:71:20:71:26 | new | This memory is never freed |
+| tests.cpp:136:24:136:29 | call to malloc | This memory is never freed |
From 8c37e90a7776b3561c025c482e2527a74dd44af1 Mon Sep 17 00:00:00 2001
From: james
Date: Fri, 3 Sep 2021 09:31:54 +0100
Subject: [PATCH 111/459] revert a couple of changes
---
.../debugging-data-flow-queries-using-partial-flow.rst | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
index b661a68fcd1..05a12ad98ee 100644
--- a/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
+++ b/docs/codeql/writing-codeql-queries/debugging-data-flow-queries-using-partial-flow.rst
@@ -38,7 +38,7 @@ You can try to debug the potential problem by following the steps described belo
Checking sources and sinks
--------------------------
-Initially, you should make sure that the ``source`` and ``sink`` definitions contain what you expect. If either the ``source`` or ``sink`` is empty then there can never be any data flow. The easiest way to check this is using quick evaluation in CodeQL for VS Code. Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources. For more information, see :ref:`Analyzing your projects ` in the CodeQL for VS Code help.
+Initially, you should make sure that the source and sink definitions contain what you expect. If either the source or sink is empty then there can never be any data flow. The easiest way to check this is using quick evaluation in CodeQL for VS Code. Select the text ``node instanceof MySource``, right-click, and choose "CodeQL: Quick Evaluation". This will evaluate the highlighted text, which in this case means the set of sources. For more information, see :ref:`Analyzing your projects ` in the CodeQL for VS Code help.
If both source and sink definitions look good then we will need to look for missing flow steps.
@@ -56,7 +56,7 @@ If there are still no results and performance is still useable, then it is best
Partial flow
------------
-A naive next step could be to change the ``sink`` definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the ``source``\ s. While this approach may work in some cases, you might find that it produces so many results that it's very hard to explore the findings. It can can also dramatically affect query performance. More importantly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
+A naive next step could be to change the sink definition to ``any()``. This would mean that we would get a lot of flow to all the places that are reachable from the sources. While this approach may work in some cases, you might find that it produces so many results that it's very hard to explore the findings. It can can also dramatically affect query performance. More importantly, you might not even see all the partial flow paths. This is because the data-flow library tries very hard to prune impossible paths and, since field stores and reads must be evenly matched along a path, we will never see paths going through a store that fail to reach a corresponding read. This can make it hard to see where flow actually stops.
To avoid these problems, a data-flow ``Configuration`` comes with a mechanism for exploring partial flow that tries to deal with these caveats. This is the ``Configuration.hasPartialFlow`` predicate:
@@ -87,7 +87,7 @@ As noted in the documentation for ``hasPartialFlow`` (for example, in the `CodeQ
This defines the exploration radius within which ``hasPartialFlow`` returns results.
-It is also useful to focus on a single ``source`` at a time as the starting point for the flow exploration. This is most easily done by adding a temporary restriction in the ``isSource`` predicate.
+It is also useful to focus on a single source at a time as the starting point for the flow exploration. This is most easily done by adding a temporary restriction in the ``isSource`` predicate.
To do quick evaluations of partial flow it is often easiest to add a predicate to the query that is solely intended for quick evaluation (right-click the predicate name and choose "CodeQL: Quick Evaluation"). A good starting point is something like:
@@ -101,12 +101,12 @@ To do quick evaluations of partial flow it is often easiest to add a predicate t
)
}
-If you are focusing on a single ``source`` then the ``src`` column is meaningless. You may of course also add other columns of interest based on ``n``, but including the enclosing callable and the distance to the source at the very least is generally recommended, as they can be useful columns to sort on to better inspect the results.
+If you are focusing on a single source then the ``src`` column is superfluous. You may of course also add other columns of interest based on ``n``, but including the enclosing callable and the distance to the source at the very least is generally recommended, as they can be useful columns to sort on to better inspect the results.
If you see a large number of partial flow results, you can focus them in a couple of ways:
-- If flow travels a long distance following an expected path, that can result in a lot of uninteresting flow being included in the exploration radius. To reduce the amount of uninteresting flow, you can replace the ``source`` definition with a suitable ``node`` that appears along the path and restart the partial flow exploration from that point.
+- If flow travels a long distance following an expected path, that can result in a lot of uninteresting flow being included in the exploration radius. To reduce the amount of uninteresting flow, you can replace the source definition with a suitable ``node`` that appears along the path and restart the partial flow exploration from that point.
- Creative use of barriers and sanitizers can be used to cut off flow paths that are uninteresting. This also reduces the number of partial flow results to explore while debugging.
Further reading
From d2f833d02cdb38f19cce3452eab1af8e7f712815 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:13:12 +0100
Subject: [PATCH 112/459] deep implications => implications
---
docs/codeql/ql-language-reference/types.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index 1216454bcf2..6c0ea6cc7fe 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -407,7 +407,7 @@ results in a compile time error. Note from the example that it is still possible
methods from instanceof supertypes from within the specialising class with the `super` keyword.
Crucially, the base class methods are not just hidden. The extension relationship is severed.
-This has deep implications on method resolution when complex class hierarchies are involved.
+This has implications on method resolution when complex class hierarchies are involved.
The following example demonstrates this.
.. code-block:: ql
From abaa0633d7e17b71883ce810d56fb5b7c149effd Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:20:14 +0100
Subject: [PATCH 113/459] consistently distinguish base types and supertypes
---
docs/codeql/ql-language-reference/types.rst | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index 6c0ea6cc7fe..30c028f0a0c 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -78,7 +78,7 @@ To define a class, you write:
#. The keyword ``class``.
#. The name of the class. This is an `identifier `_
starting with an uppercase letter.
-#. The base types that the class is derived from via `extends` and/or `instanceof`
+#. The supertypes that the class is derived from via `extends` and/or `instanceof`
#. The :ref:`body of the class `, enclosed in braces.
For example:
@@ -106,10 +106,10 @@ This defines a class ``OneTwoThree``, which contains the values ``1``, ``2``, an
.. index:: extends
``OneTwoThree`` extends ``int``, that is, it is a subtype of ``int``. A class in QL must always
-extend at least one existing type. Those types are called the **base types** of the class. The
-values of a class are contained within the intersection of the base types (that is, they are in
-the :ref:`class domain type `). A class inherits all member predicates from its
-base types.
+have at least one supertype. Supertypes that are referenced with the `extends` keyword are called
+the **base types** of the class. The values of a class are contained within the intersection of
+the supertypes (that is, they are in the :ref:`class domain type `).
+A class inherits all member predicates from its base types.
A class can extend multiple types. For more information, see ":ref:`multiple-inheritance`."
Classes can also specialise other types without extending the class interface via `instanceof`,
@@ -230,7 +230,7 @@ Concrete classes
The classes in the above examples are all **concrete** classes. They are defined by
restricting the values in a larger type. The values in a concrete class are precisely those
-values in the intersection of the base types that also satisfy the
+values in the intersection of the supertypes that also satisfy the
:ref:`characteristic predicate ` of the class.
.. _abstract-classes:
From 6e025186aba8da3f95fb9c727714d2eebb9088ff Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:23:58 +0100
Subject: [PATCH 114/459] make clear that instanceof supertypes are not base
types
---
docs/codeql/ql-language-reference/types.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index 30c028f0a0c..751e56d7d24 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -406,7 +406,9 @@ However, `foo_method` is not exposed in `Bar`, so the query `select any(Bar b).f
results in a compile time error. Note from the example that it is still possible to access
methods from instanceof supertypes from within the specialising class with the `super` keyword.
-Crucially, the base class methods are not just hidden. The extension relationship is severed.
+Crucially, the instanceof **supertypes** are not **base types**.
+This means that these supertypes do not participate in overriding, and any fields of such
+supertypes are not part of the new class.
This has implications on method resolution when complex class hierarchies are involved.
The following example demonstrates this.
From 35b0e833708080d736fa77db2e2814f581cf8ca5 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:52:05 +0100
Subject: [PATCH 115/459] simpler first instanceof extension example
---
docs/codeql/ql-language-reference/types.rst | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index 751e56d7d24..9d3984b5f5e 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -397,8 +397,8 @@ Besides extending base types, classes can also declare `instanceof` relationship
string foo_method() { result = "foo" }
}
- class Bar extends int instanceof Foo {
- string bar_method() { result = super.foo_method() }
+ class Bar instanceof Foo {
+ string toString() { result = super.foo_method() }
}
In this example, the characteristic predicate from `Foo` also applies to `Bar`.
From cd646c819dc7e4349f90fe75c4e91f74b764f5d1 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:55:03 +0100
Subject: [PATCH 116/459] explain instanceof extensions via charpred instanceof
---
docs/codeql/ql-language-reference/types.rst | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index 9d3984b5f5e..ddbd1bc04c4 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -388,6 +388,8 @@ Non-extending subtypes
======================
Besides extending base types, classes can also declare `instanceof` relationships with other types.
+Declaring a class as `instanceof Foo` is roughly equivalent to saying `this instanceof Foo` the charpred.
+The main differences are that you can call methods on Bar via `super` and you can get better optimisation.
.. code-block:: ql
From c6eb795e76aaaa728eb3c61d5e0b1eef533c6284 Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 3 Sep 2021 14:23:57 +0200
Subject: [PATCH 117/459] Apply suggestions from code review
Co-authored-by: Rasmus Wriedt Larsen
---
.../ModificationOfParameterWithDefaultCustomizations.qll | 8 +++++++-
.../Functions/ModificationOfParameterWithDefault/test.py | 4 ++--
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
index 96dd41adfcb..7cd16415dd9 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -53,7 +53,7 @@ module ModificationOfParameterWithDefault {
}
/**
- * A source of remote user input, considered as a flow source.
+ * A mutable default value for a parameter, considered as a flow source.
*/
class MutableDefaultValue extends Source {
boolean nonEmpty;
@@ -120,6 +120,9 @@ module ModificationOfParameterWithDefault {
}
}
+ /**
+ * An expression that is checked directly in an `if`, possibly with `not`, such as `if x:` or `if not x:`.
+ */
private class IdentityGuarded extends Expr {
boolean inverted;
@@ -136,6 +139,9 @@ module ModificationOfParameterWithDefault {
)
}
+ /**
+ * Whether this guard has been inverted. For `if x:` the result is `false`, and for `if not x:` the result is `true`.
+ */
boolean isInverted() { result = inverted }
}
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 09ad7ed986c..3246d7362a2 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -15,7 +15,7 @@ def list_del(l = [0]):
# Not OK
def append_op(l = []):
- l += 1 #$ modification=l
+ l += [1, 2, 3] #$ modification=l
return l
# Not OK
@@ -123,6 +123,6 @@ def dict_update_op_nochange(d = {}):
# OK
def sanitizer(l = []):
- if not l == []:
+ if l:
l.append(1) #$ SPURIOUS: modification=l
return l
From 913990bc6224ec0d1248bf373f9a713d0931611c Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 3 Sep 2021 14:40:16 +0200
Subject: [PATCH 118/459] Python: Add suggested comments and test case
---
.../ModificationOfParameterWithDefaultCustomizations.qll | 6 ++++--
.../Functions/ModificationOfParameterWithDefault/test.py | 6 ++++++
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
index 7cd16415dd9..3aed2eeaf16 100644
--- a/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
+++ b/python/ql/src/semmle/python/functions/ModificationOfParameterWithDefaultCustomizations.qll
@@ -17,6 +17,7 @@ module ModificationOfParameterWithDefault {
* A data flow source for detecting modifications of a parameters default value.
*/
abstract class Source extends DataFlow::Node {
+ /** Result is true if the default value is non-empty for this source and false if not. */
abstract boolean isNonEmpty();
}
@@ -34,6 +35,7 @@ module ModificationOfParameterWithDefault {
* A sanitizer guard for detecting modifications of a parameters default value.
*/
abstract class BarrierGuard extends DataFlow::BarrierGuard {
+ /** Result is true if this guard blocks non-empty values and false if it blocks empty values. */
abstract boolean blocksNonEmpty();
}
@@ -120,7 +122,7 @@ module ModificationOfParameterWithDefault {
}
}
- /**
+ /**
* An expression that is checked directly in an `if`, possibly with `not`, such as `if x:` or `if not x:`.
*/
private class IdentityGuarded extends Expr {
@@ -139,7 +141,7 @@ module ModificationOfParameterWithDefault {
)
}
- /**
+ /**
* Whether this guard has been inverted. For `if x:` the result is `false`, and for `if not x:` the result is `true`.
*/
boolean isInverted() { result = inverted }
diff --git a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
index 3246d7362a2..d7aef0b24d0 100644
--- a/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
+++ b/python/ql/test/query-tests/Functions/ModificationOfParameterWithDefault/test.py
@@ -124,5 +124,11 @@ def dict_update_op_nochange(d = {}):
# OK
def sanitizer(l = []):
if l:
+ l.append(1) #$ modification=l
+ return l
+
+# OK
+def sanitizer_negated(l = [1]):
+ if not l:
l.append(1) #$ SPURIOUS: modification=l
return l
From 863eede75b0c1685b4181b2851b1b07246a5a8e9 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 16:12:52 +0100
Subject: [PATCH 119/459] easier second example for instanceof extensions
---
docs/codeql/ql-language-reference/types.rst | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/codeql/ql-language-reference/types.rst b/docs/codeql/ql-language-reference/types.rst
index ddbd1bc04c4..253d7dca61f 100644
--- a/docs/codeql/ql-language-reference/types.rst
+++ b/docs/codeql/ql-language-reference/types.rst
@@ -417,13 +417,13 @@ The following example demonstrates this.
.. code-block:: ql
class Interface extends int {
- Interface() { this in [1 .. 100] }
+ Interface() { this in [1 .. 10] }
string foo() { result = "" }
}
- class Foo extends Interface {
- Foo() { this in [1 .. 10] }
- override string foo() { result = "foo" }
+ class Foo extends int {
+ Foo() { this in [1 .. 5] }
+ string foo() { result = "foo" }
}
class Bar extends Interface instanceof Foo {
@@ -432,8 +432,8 @@ The following example demonstrates this.
Here, the method `Bar::foo` does not override `Foo::foo`.
Instead, it overrides only `Interface::foo`.
-This means that `select any(Foo b).foo()` yields only `foo`.
-Had `bar been defined as `extends Foo`, then `select any(Foo b).foo()` would yield `bar`.
+This means that `select any(Foo f).foo()` yields only `foo`.
+Had `Bar` been defined as `extends Foo`, then `select any(Foo b)` would yield `bar`.
.. _character-types:
.. _domain-types:
From 8b0d5a2e7bc864689ff00e366c8abe23f6de37e7 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Sun, 5 Sep 2021 22:46:37 +0300
Subject: [PATCH 120/459] Update
cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
---
.../src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
index 8ec155e100f..2b590e025b8 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-675/DoubleRelease.qhelp
@@ -12,7 +12,7 @@
The following example demonstrates an erroneous and corrected use of descriptor deallocation.
-
+
From 8d4af3ad81ae8056ac9af4a6e20d53c4f04aeed8 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 12 May 2021 16:49:43 +0200
Subject: [PATCH 121/459] convert field based range pattern to casting based
range pattern
---
.../ql/lib/semmle/javascript/Base64.qll | 16 +++---
.../ql/lib/semmle/javascript/Closure.qll | 16 +++---
.../lib/semmle/javascript/InclusionTests.qll | 10 ++--
.../javascript/MembershipCandidates.qll | 12 ++---
.../ql/lib/semmle/javascript/StringOps.qll | 32 +++++------
.../lib/semmle/javascript/dataflow/Nodes.qll | 54 +++++++++----------
.../semmle/javascript/frameworks/Cheerio.qll | 4 +-
.../javascript/frameworks/ClientRequests.qll | 14 +++--
.../frameworks/ComposedFunctions.qll | 10 ++--
.../semmle/javascript/frameworks/Electron.qll | 2 +-
.../javascript/frameworks/EventEmitter.qll | 36 ++++++-------
.../lib/semmle/javascript/frameworks/HTTP.qll | 6 +--
.../javascript/frameworks/NodeJSLib.qll | 2 +-
.../frameworks/PropertyProjection.qll | 10 ++--
.../semmle/javascript/frameworks/Redux.qll | 14 ++---
.../semmle/javascript/frameworks/ShellJS.qll | 6 +--
16 files changed, 108 insertions(+), 136 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/Base64.qll b/javascript/ql/lib/semmle/javascript/Base64.qll
index f2e91a7060c..d7a463da2b0 100644
--- a/javascript/ql/lib/semmle/javascript/Base64.qll
+++ b/javascript/ql/lib/semmle/javascript/Base64.qll
@@ -7,15 +7,13 @@ import javascript
module Base64 {
/** A call to a base64 encoder. */
class Encode extends DataFlow::Node {
- Encode::Range encode;
-
- Encode() { this = encode }
+ Encode() { this instanceof Encode::Range }
/** Gets the input passed to the encoder. */
- DataFlow::Node getInput() { result = encode.getInput() }
+ DataFlow::Node getInput() { result = this.(Encode::Range).getInput() }
/** Gets the base64-encoded output of the encoder. */
- DataFlow::Node getOutput() { result = encode.getOutput() }
+ DataFlow::Node getOutput() { result = this.(Encode::Range).getOutput() }
}
module Encode {
@@ -35,15 +33,13 @@ module Base64 {
/** A call to a base64 decoder. */
class Decode extends DataFlow::Node {
- Decode::Range encode;
-
- Decode() { this = encode }
+ Decode() { this instanceof Decode::Range }
/** Gets the base64-encoded input passed to the decoder. */
- DataFlow::Node getInput() { result = encode.getInput() }
+ DataFlow::Node getInput() { result = this.(Decode::Range).getInput() }
/** Gets the output of the decoder. */
- DataFlow::Node getOutput() { result = encode.getOutput() }
+ DataFlow::Node getOutput() { result = this.(Decode::Range).getOutput() }
}
module Decode {
diff --git a/javascript/ql/lib/semmle/javascript/Closure.qll b/javascript/ql/lib/semmle/javascript/Closure.qll
index 5a4e9b4c7e7..637dc6f9687 100644
--- a/javascript/ql/lib/semmle/javascript/Closure.qll
+++ b/javascript/ql/lib/semmle/javascript/Closure.qll
@@ -9,14 +9,14 @@ module Closure {
* A reference to a Closure namespace.
*/
class ClosureNamespaceRef extends DataFlow::Node {
- ClosureNamespaceRef::Range range;
-
- ClosureNamespaceRef() { this = range }
+ ClosureNamespaceRef() { this instanceof ClosureNamespaceRef::Range }
/**
* Gets the namespace being referenced.
*/
- string getClosureNamespace() { result = range.getClosureNamespace() }
+ string getClosureNamespace() {
+ result = this.(ClosureNamespaceRef::Range).getClosureNamespace()
+ }
}
module ClosureNamespaceRef {
@@ -37,7 +37,7 @@ module Closure {
* A data flow node that returns the value of a closure namespace.
*/
class ClosureNamespaceAccess extends ClosureNamespaceRef {
- override ClosureNamespaceAccess::Range range;
+ ClosureNamespaceAccess() { this instanceof ClosureNamespaceAccess::Range }
}
module ClosureNamespaceAccess {
@@ -81,7 +81,7 @@ module Closure {
* A top-level call to `goog.provide`.
*/
class ClosureProvideCall extends ClosureNamespaceRef, DataFlow::MethodCallNode {
- override DefaultClosureProvideCall range;
+ ClosureProvideCall() { this instanceof DefaultClosureProvideCall }
}
/**
@@ -95,7 +95,7 @@ module Closure {
* A call to `goog.require`.
*/
class ClosureRequireCall extends ClosureNamespaceAccess, DataFlow::MethodCallNode {
- override DefaultClosureRequireCall range;
+ ClosureRequireCall() { this instanceof DefaultClosureRequireCall }
}
/**
@@ -112,7 +112,7 @@ module Closure {
* A top-level call to `goog.module` or `goog.declareModuleId`.
*/
class ClosureModuleDeclaration extends ClosureNamespaceRef, DataFlow::MethodCallNode {
- override DefaultClosureModuleDeclaration range;
+ ClosureModuleDeclaration() { this instanceof DefaultClosureModuleDeclaration }
}
private GlobalVariable googVariable() { variables(result, "goog", any(GlobalScope sc)) }
diff --git a/javascript/ql/lib/semmle/javascript/InclusionTests.qll b/javascript/ql/lib/semmle/javascript/InclusionTests.qll
index 2d0cf1752a4..69f037a664d 100644
--- a/javascript/ql/lib/semmle/javascript/InclusionTests.qll
+++ b/javascript/ql/lib/semmle/javascript/InclusionTests.qll
@@ -17,15 +17,13 @@ private import javascript
* ```
*/
class InclusionTest extends DataFlow::Node {
- InclusionTest::Range range;
-
- InclusionTest() { this = range }
+ InclusionTest() { this instanceof InclusionTest::Range }
/** Gets the `A` in `A.includes(B)`. */
- DataFlow::Node getContainerNode() { result = range.getContainerNode() }
+ DataFlow::Node getContainerNode() { result = this.(InclusionTest::Range).getContainerNode() }
/** Gets the `B` in `A.includes(B)`. */
- DataFlow::Node getContainedNode() { result = range.getContainedNode() }
+ DataFlow::Node getContainedNode() { result = this.(InclusionTest::Range).getContainedNode() }
/**
* Gets the polarity of the check.
@@ -33,7 +31,7 @@ class InclusionTest extends DataFlow::Node {
* If the polarity is `false` the check returns `true` if the container does not contain
* the given element.
*/
- boolean getPolarity() { result = range.getPolarity() }
+ boolean getPolarity() { result = this.(InclusionTest::Range).getPolarity() }
}
module InclusionTest {
diff --git a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll
index 89573252ab5..6cc6aa53fe1 100644
--- a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll
+++ b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll
@@ -10,26 +10,24 @@ import javascript
* Additional candidates can be added by subclassing `MembershipCandidate::Range`
*/
class MembershipCandidate extends DataFlow::Node {
- MembershipCandidate::Range range;
-
- MembershipCandidate() { this = range }
+ MembershipCandidate() { this instanceof MembershipCandidate::Range }
/**
* Gets the expression that performs the membership test, if any.
*/
- DataFlow::Node getTest() { result = range.getTest() }
+ DataFlow::Node getTest() { result = this.(MembershipCandidate::Range).getTest() }
/**
* Gets a string that this candidate is tested against, if
* it can be determined.
*/
- string getAMemberString() { result = range.getAMemberString() }
+ string getAMemberString() { result = this.(MembershipCandidate::Range).getAMemberString() }
/**
* Gets a node that this candidate is tested against, if
* it can be determined.
*/
- DataFlow::Node getAMemberNode() { result = range.getAMemberNode() }
+ DataFlow::Node getAMemberNode() { result = this.(MembershipCandidate::Range).getAMemberNode() }
/**
* Gets the polarity of the test.
@@ -37,7 +35,7 @@ class MembershipCandidate extends DataFlow::Node {
* If the polarity is `false` the test returns `true` if the
* collection does not contain this candidate.
*/
- boolean getTestPolarity() { result = range.getTestPolarity() }
+ boolean getTestPolarity() { result = this.(MembershipCandidate::Range).getTestPolarity() }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/StringOps.qll b/javascript/ql/lib/semmle/javascript/StringOps.qll
index a82ffe95a13..78083720fcf 100644
--- a/javascript/ql/lib/semmle/javascript/StringOps.qll
+++ b/javascript/ql/lib/semmle/javascript/StringOps.qll
@@ -9,19 +9,17 @@ module StringOps {
* A expression that is equivalent to `A.startsWith(B)` or `!A.startsWith(B)`.
*/
class StartsWith extends DataFlow::Node {
- StartsWith::Range range;
-
- StartsWith() { range = this }
+ StartsWith() { this instanceof StartsWith::Range }
/**
* Gets the `A` in `A.startsWith(B)`.
*/
- DataFlow::Node getBaseString() { result = range.getBaseString() }
+ DataFlow::Node getBaseString() { result = this.(StartsWith::Range).getBaseString() }
/**
* Gets the `B` in `A.startsWith(B)`.
*/
- DataFlow::Node getSubstring() { result = range.getSubstring() }
+ DataFlow::Node getSubstring() { result = this.(StartsWith::Range).getSubstring() }
/**
* Gets the polarity of the check.
@@ -29,7 +27,7 @@ module StringOps {
* If the polarity is `false` the check returns `true` if the string does not start
* with the given substring.
*/
- boolean getPolarity() { result = range.getPolarity() }
+ boolean getPolarity() { result = this.(StartsWith::Range).getPolarity() }
}
module StartsWith {
@@ -238,19 +236,17 @@ module StringOps {
* An expression that is equivalent to `A.endsWith(B)` or `!A.endsWith(B)`.
*/
class EndsWith extends DataFlow::Node {
- EndsWith::Range range;
-
- EndsWith() { this = range }
+ EndsWith() { this instanceof EndsWith::Range }
/**
* Gets the `A` in `A.startsWith(B)`.
*/
- DataFlow::Node getBaseString() { result = range.getBaseString() }
+ DataFlow::Node getBaseString() { result = this.(EndsWith::Range).getBaseString() }
/**
* Gets the `B` in `A.startsWith(B)`.
*/
- DataFlow::Node getSubstring() { result = range.getSubstring() }
+ DataFlow::Node getSubstring() { result = this.(EndsWith::Range).getSubstring() }
/**
* Gets the polarity if the check.
@@ -258,7 +254,7 @@ module StringOps {
* If the polarity is `false` the check returns `true` if the string does not end
* with the given substring.
*/
- boolean getPolarity() { result = range.getPolarity() }
+ boolean getPolarity() { result = this.(EndsWith::Range).getPolarity() }
}
module EndsWith {
@@ -663,9 +659,7 @@ module StringOps {
* ```
*/
class RegExpTest extends DataFlow::Node {
- RegExpTest::Range range;
-
- RegExpTest() { this = range }
+ RegExpTest() { this instanceof RegExpTest::Range }
/**
* Gets the AST of the regular expression used in the test, if it can be seen locally.
@@ -673,7 +667,7 @@ module StringOps {
RegExpTerm getRegExp() {
result = getRegExpOperand().getALocalSource().(DataFlow::RegExpCreationNode).getRoot()
or
- result = range.getRegExpOperand(true).asExpr().(StringLiteral).asRegExp()
+ result = this.(RegExpTest::Range).getRegExpOperand(true).asExpr().(StringLiteral).asRegExp()
}
/**
@@ -681,12 +675,12 @@ module StringOps {
*
* In some cases this represents a string value being coerced to a RegExp object.
*/
- DataFlow::Node getRegExpOperand() { result = range.getRegExpOperand(_) }
+ DataFlow::Node getRegExpOperand() { result = this.(RegExpTest::Range).getRegExpOperand(_) }
/**
* Gets the data flow node corresponding to the string being tested against the regular expression.
*/
- DataFlow::Node getStringOperand() { result = range.getStringOperand() }
+ DataFlow::Node getStringOperand() { result = this.(RegExpTest::Range).getStringOperand() }
/**
* Gets the return value indicating that the string matched the regular expression.
@@ -694,7 +688,7 @@ module StringOps {
* For example, for `regexp.exec(str) == null`, the polarity is `false`, and for
* `regexp.exec(str) != null` the polarity is `true`.
*/
- boolean getPolarity() { result = range.getPolarity() }
+ boolean getPolarity() { result = this.(RegExpTest::Range).getPolarity() }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll
index 7c832e20aed..f8bdb6f46ad 100644
--- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll
+++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll
@@ -703,12 +703,10 @@ class ArrayCreationNode extends DataFlow::ValueNode, DataFlow::SourceNode {
* ```
*/
class ModuleImportNode extends DataFlow::SourceNode {
- ModuleImportNode::Range range;
-
- ModuleImportNode() { this = range }
+ ModuleImportNode() { this instanceof ModuleImportNode::Range }
/** Gets the path of the imported module. */
- string getPath() { result = range.getPath() }
+ string getPath() { result = this.(ModuleImportNode::Range).getPath() }
}
module ModuleImportNode {
@@ -845,24 +843,22 @@ module MemberKind {
* Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`.
*/
class ClassNode extends DataFlow::SourceNode {
- ClassNode::Range impl;
-
- ClassNode() { this = impl }
+ ClassNode() { this instanceof ClassNode::Range }
/**
* Gets the unqualified name of the class, if it has one or one can be determined from the context.
*/
- string getName() { result = impl.getName() }
+ string getName() { result = this.(ClassNode::Range).getName() }
/**
* Gets a description of the class.
*/
- string describe() { result = impl.describe() }
+ string describe() { result = this.(ClassNode::Range).describe() }
/**
* Gets the constructor function of this class.
*/
- FunctionNode getConstructor() { result = impl.getConstructor() }
+ FunctionNode getConstructor() { result = this.(ClassNode::Range).getConstructor() }
/**
* Gets an instance method declared in this class, with the given name, if any.
@@ -870,7 +866,7 @@ class ClassNode extends DataFlow::SourceNode {
* Does not include methods from superclasses.
*/
FunctionNode getInstanceMethod(string name) {
- result = impl.getInstanceMember(name, MemberKind::method())
+ result = this.(ClassNode::Range).getInstanceMember(name, MemberKind::method())
}
/**
@@ -880,7 +876,9 @@ class ClassNode extends DataFlow::SourceNode {
*
* Does not include methods from superclasses.
*/
- FunctionNode getAnInstanceMethod() { result = impl.getAnInstanceMember(MemberKind::method()) }
+ FunctionNode getAnInstanceMethod() {
+ result = this.(ClassNode::Range).getAnInstanceMember(MemberKind::method())
+ }
/**
* Gets the instance method, getter, or setter with the given name and kind.
@@ -888,7 +886,7 @@ class ClassNode extends DataFlow::SourceNode {
* Does not include members from superclasses.
*/
FunctionNode getInstanceMember(string name, MemberKind kind) {
- result = impl.getInstanceMember(name, kind)
+ result = this.(ClassNode::Range).getInstanceMember(name, kind)
}
/**
@@ -896,31 +894,35 @@ class ClassNode extends DataFlow::SourceNode {
*
* Does not include members from superclasses.
*/
- FunctionNode getAnInstanceMember(MemberKind kind) { result = impl.getAnInstanceMember(kind) }
+ FunctionNode getAnInstanceMember(MemberKind kind) {
+ result = this.(ClassNode::Range).getAnInstanceMember(kind)
+ }
/**
* Gets an instance method, getter, or setter declared in this class.
*
* Does not include members from superclasses.
*/
- FunctionNode getAnInstanceMember() { result = impl.getAnInstanceMember(_) }
+ FunctionNode getAnInstanceMember() { result = this.(ClassNode::Range).getAnInstanceMember(_) }
/**
* Gets the static method declared in this class with the given name.
*/
- FunctionNode getStaticMethod(string name) { result = impl.getStaticMethod(name) }
+ FunctionNode getStaticMethod(string name) {
+ result = this.(ClassNode::Range).getStaticMethod(name)
+ }
/**
* Gets a static method declared in this class.
*
* The constructor is not considered a static method.
*/
- FunctionNode getAStaticMethod() { result = impl.getAStaticMethod() }
+ FunctionNode getAStaticMethod() { result = this.(ClassNode::Range).getAStaticMethod() }
/**
* Gets a dataflow node that refers to the superclass of this class.
*/
- DataFlow::Node getASuperClassNode() { result = impl.getASuperClassNode() }
+ DataFlow::Node getASuperClassNode() { result = this.(ClassNode::Range).getASuperClassNode() }
/**
* Gets a direct super class of this class.
@@ -1066,13 +1068,13 @@ class ClassNode extends DataFlow::SourceNode {
* Gets the type annotation for the field `fieldName`, if any.
*/
TypeAnnotation getFieldTypeAnnotation(string fieldName) {
- result = impl.getFieldTypeAnnotation(fieldName)
+ result = this.(ClassNode::Range).getFieldTypeAnnotation(fieldName)
}
/**
* Gets a decorator applied to this class.
*/
- DataFlow::Node getADecorator() { result = impl.getADecorator() }
+ DataFlow::Node getADecorator() { result = this.(ClassNode::Range).getADecorator() }
}
module ClassNode {
@@ -1359,9 +1361,7 @@ module ClassNode {
* ```
*/
class PartialInvokeNode extends DataFlow::Node {
- PartialInvokeNode::Range range;
-
- PartialInvokeNode() { this = range }
+ PartialInvokeNode() { this instanceof PartialInvokeNode::Range }
/** Gets a node holding a callback invoked by this partial invocation node. */
DataFlow::Node getACallbackNode() {
@@ -1374,26 +1374,26 @@ class PartialInvokeNode extends DataFlow::Node {
* Holds if `argument` is passed as argument `index` to the function in `callback`.
*/
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
- range.isPartialArgument(callback, argument, index)
+ this.(PartialInvokeNode::Range).isPartialArgument(callback, argument, index)
}
/**
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
*/
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
- result = range.getBoundFunction(callback, boundArgs)
+ result = this.(PartialInvokeNode::Range).getBoundFunction(callback, boundArgs)
}
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
- DataFlow::Node getBoundReceiver() { result = range.getBoundReceiver(_) }
+ DataFlow::Node getBoundReceiver() { result = this.(PartialInvokeNode::Range).getBoundReceiver(_) }
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
- result = range.getBoundReceiver(callback)
+ result = this.(PartialInvokeNode::Range).getBoundReceiver(callback)
}
}
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll b/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll
index b9705aebfbd..182f760ac04 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll
@@ -33,9 +33,7 @@ module Cheerio {
* with an interface similar to that of a jQuery object.
*/
class CheerioObjectCreation extends DataFlow::SourceNode {
- CheerioObjectCreation::Range range;
-
- CheerioObjectCreation() { this = range }
+ CheerioObjectCreation() { this instanceof CheerioObjectCreation::Range }
}
module CheerioObjectCreation {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll
index 3d496f28177..f15821a403e 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll
@@ -19,24 +19,22 @@ import javascript
* predicates.
*/
class ClientRequest extends DataFlow::InvokeNode {
- ClientRequest::Range self;
-
- ClientRequest() { this = self }
+ ClientRequest() { this instanceof ClientRequest::Range }
/**
* Gets the URL of the request.
*/
- DataFlow::Node getUrl() { result = self.getUrl() }
+ DataFlow::Node getUrl() { result = this.(ClientRequest::Range).getUrl() }
/**
* Gets the host of the request.
*/
- DataFlow::Node getHost() { result = self.getHost() }
+ DataFlow::Node getHost() { result = this.(ClientRequest::Range).getHost() }
/**
* Gets a node that contributes to the data-part this request.
*/
- DataFlow::Node getADataNode() { result = self.getADataNode() }
+ DataFlow::Node getADataNode() { result = this.(ClientRequest::Range).getADataNode() }
/**
* Gets a data flow node that refers to some representation of the response, possibly
@@ -60,7 +58,7 @@ class ClientRequest extends DataFlow::InvokeNode {
* - Any value provided by custom implementations of `ClientRequest::Range`.
*/
DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
- result = self.getAResponseDataNode(responseType, promise)
+ result = this.(ClientRequest::Range).getAResponseDataNode(responseType, promise)
}
/**
@@ -72,7 +70,7 @@ class ClientRequest extends DataFlow::InvokeNode {
/**
* Gets a data-flow node that determines where in the file-system the result of the request should be saved.
*/
- DataFlow::Node getASavePath() { result = self.getASavePath() }
+ DataFlow::Node getASavePath() { result = this.(ClientRequest::Range).getASavePath() }
}
deprecated class CustomClientRequest = ClientRequest::Range;
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll b/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll
index b1016ce56be..4cd2c9e3c65 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll
@@ -9,9 +9,7 @@ import javascript
* series of functions `f, g, h, ...`.
*/
class FunctionCompositionCall extends DataFlow::CallNode {
- FunctionCompositionCall::Range range;
-
- FunctionCompositionCall() { this = range }
+ FunctionCompositionCall() { this instanceof FunctionCompositionCall::Range }
/**
* Gets the `i`th function in the composition `f(g(h(...)))`, counting from left to right.
@@ -19,7 +17,9 @@ class FunctionCompositionCall extends DataFlow::CallNode {
* Note that this is the opposite of the order in which the function are invoked,
* that is, `g` occurs later than `f` in `f(g(...))` but is invoked before `f`.
*/
- DataFlow::Node getOperandNode(int i) { result = range.getOperandNode(i) }
+ DataFlow::Node getOperandNode(int i) {
+ result = this.(FunctionCompositionCall::Range).getOperandNode(i)
+ }
/** Gets a node holding one of the functions to be composed. */
final DataFlow::Node getAnOperandNode() { result = getOperandNode(_) }
@@ -38,7 +38,7 @@ class FunctionCompositionCall extends DataFlow::CallNode {
final DataFlow::FunctionNode getAnOperandFunction() { result = getOperandFunction(_) }
/** Gets the number of functions being composed. */
- int getNumOperand() { result = range.getNumOperand() }
+ int getNumOperand() { result = this.(FunctionCompositionCall::Range).getNumOperand() }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll b/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll
index 7d099932738..2c326e7cd31 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/Electron.qll
@@ -182,7 +182,7 @@ module Electron {
* A Node.js-style HTTP or HTTPS request made using an Electron module.
*/
class ElectronClientRequest extends NodeJSLib::NodeJSClientRequest {
- override ElectronClientRequest::Range self;
+ ElectronClientRequest() { this instanceof ElectronClientRequest::Range }
}
module ElectronClientRequest {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll b/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
index f044d69524d..c428f070cd5 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
@@ -69,39 +69,41 @@ module EventEmitter {
* Extend EventEmitter::Range to mark something as being an EventEmitter.
*/
class EventEmitter extends DataFlow::Node {
- EventEmitter::Range range;
-
- EventEmitter() { this = range }
+ EventEmitter() { this instanceof EventEmitter::Range }
}
/**
* A registration of an event handler on an EventEmitter.
*/
class EventRegistration extends DataFlow::Node {
- EventRegistration::Range range;
-
- EventRegistration() { this = range }
+ EventRegistration() { this instanceof EventRegistration::Range }
/** Gets the EventEmitter that the event handler is registered on. */
- final EventEmitter getEmitter() { result = range.getEmitter() }
+ final EventEmitter getEmitter() { result = this.(EventRegistration::Range).getEmitter() }
/** Gets the name of the channel if possible. */
- string getChannel() { result = range.getChannel() }
+ string getChannel() { result = this.(EventRegistration::Range).getChannel() }
/** Gets the `i`th parameter in the event handler. */
- DataFlow::Node getReceivedItem(int i) { result = range.getReceivedItem(i) }
+ DataFlow::Node getReceivedItem(int i) {
+ result = this.(EventRegistration::Range).getReceivedItem(i)
+ }
/**
* Gets a value that is returned by the event handler.
* The default implementation is that no value can be returned.
*/
- DataFlow::Node getAReturnedValue() { result = range.getAReturnedValue() }
+ DataFlow::Node getAReturnedValue() {
+ result = this.(EventRegistration::Range).getAReturnedValue()
+ }
/**
* Get a dispatch that this event handler can return a value to.
* The default implementation is that there exists no such dispatch.
*/
- EventDispatch getAReturnDispatch() { result = range.getAReturnDispatch() }
+ EventDispatch getAReturnDispatch() {
+ result = this.(EventRegistration::Range).getAReturnDispatch()
+ }
}
module EventRegistration {
@@ -141,25 +143,23 @@ module EventRegistration {
* A dispatch of an event on an EventEmitter.
*/
class EventDispatch extends DataFlow::Node {
- EventDispatch::Range range;
-
- EventDispatch() { this = range }
+ EventDispatch() { this instanceof EventDispatch::Range }
/** Gets the emitter that the event dispatch happens on. */
- EventEmitter getEmitter() { result = range.getEmitter() }
+ EventEmitter getEmitter() { result = this.(EventDispatch::Range).getEmitter() }
/** Gets the name of the channel if possible. */
- string getChannel() { result = range.getChannel() }
+ string getChannel() { result = this.(EventDispatch::Range).getChannel() }
/** Gets the `i`th argument that is send to the event handler. */
- DataFlow::Node getSentItem(int i) { result = range.getSentItem(i) }
+ DataFlow::Node getSentItem(int i) { result = this.(EventDispatch::Range).getSentItem(i) }
/**
* Get an EventRegistration that this event dispatch can send an event to.
* The default implementation is that the emitters of the dispatch and registration have to be equal.
* Channels are by default ignored.
*/
- EventRegistration getAReceiver() { result = range.getAReceiver() }
+ EventRegistration getAReceiver() { result = this.(EventDispatch::Range).getAReceiver() }
}
module EventDispatch {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll b/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll
index 9475c32eeab..1154dff7452 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll
@@ -541,15 +541,13 @@ module HTTP {
* An object that contains one or more potential route handlers.
*/
class RouteHandlerCandidateContainer extends DataFlow::Node {
- RouteHandlerCandidateContainer::Range self;
-
- RouteHandlerCandidateContainer() { this = self }
+ RouteHandlerCandidateContainer() { this instanceof RouteHandlerCandidateContainer::Range }
/**
* Gets the route handler in this container that is accessed at `access`.
*/
DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
- result = self.getRouteHandler(access)
+ result = this.(RouteHandlerCandidateContainer::Range).getRouteHandler(access)
}
}
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll
index fa753220005..41faffcf656 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/NodeJSLib.qll
@@ -826,7 +826,7 @@ module NodeJSLib {
* for example `http.request(url)`.
*/
class NodeJSClientRequest extends ClientRequest {
- override NodeJSClientRequest::Range self;
+ NodeJSClientRequest() { this instanceof NodeJSClientRequest::Range }
}
module NodeJSClientRequest {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll b/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll
index e3fe62be164..f6db62d5632 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll
@@ -15,19 +15,17 @@ import javascript
* predicates.
*/
class PropertyProjection extends DataFlow::CallNode {
- PropertyProjection::Range self;
-
- PropertyProjection() { this = self }
+ PropertyProjection() { this instanceof PropertyProjection::Range }
/**
* Gets the argument for the object to project properties from, such as `o` in `_.get(o, 'a.b')`.
*/
- DataFlow::Node getObject() { result = self.getObject() }
+ DataFlow::Node getObject() { result = this.(PropertyProjection::Range).getObject() }
/**
* Gets an argument that selects the properties to project, such as `'a.b'` in `_.get(o, 'a.b')`.
*/
- DataFlow::Node getASelector() { result = self.getASelector() }
+ DataFlow::Node getASelector() { result = this.(PropertyProjection::Range).getASelector() }
/**
* Holds if this call returns the value of a single projected property, as opposed to an object that can contain multiple projected properties.
@@ -36,7 +34,7 @@ class PropertyProjection extends DataFlow::CallNode {
* - This predicate holds for `_.get({a: 'b'}, 'a')`, which returns `'b'`,
* - This predicate does not hold for `_.pick({a: 'b', c: 'd'}}, 'a')`, which returns `{a: 'b'}`,
*/
- predicate isSingletonProjection() { self.isSingletonProjection() }
+ predicate isSingletonProjection() { this.(PropertyProjection::Range).isSingletonProjection() }
}
module PropertyProjection {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
index ad3f985c397..cc14e88712a 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
@@ -57,9 +57,7 @@ module Redux {
* Creation of a redux store, usually via a call to `createStore`.
*/
class StoreCreation extends DataFlow::SourceNode {
- StoreCreation::Range range;
-
- StoreCreation() { this = range }
+ StoreCreation() { this instanceof StoreCreation::Range }
/** Gets a reference to the store. */
DataFlow::SourceNode ref() { result = asApiNode().getAUse() }
@@ -68,7 +66,7 @@ module Redux {
API::Node asApiNode() { result.getAnImmediateUse() = this }
/** Gets the data flow node holding the root reducer for this store. */
- DataFlow::Node getReducerArg() { result = range.getReducerArg() }
+ DataFlow::Node getReducerArg() { result = this.(StoreCreation::Range).getReducerArg() }
/** Gets a data flow node referring to the root reducer. */
DataFlow::SourceNode getAReducerSource() { result = getReducerArg().(ReducerArg).getASource() }
@@ -424,12 +422,10 @@ module Redux {
* at some point. We model all action creators as if they dispatch the action they create.
*/
class ActionCreator extends DataFlow::SourceNode {
- ActionCreator::Range range;
-
- ActionCreator() { this = range }
+ ActionCreator() { this instanceof ActionCreator::Range }
/** Gets the `type` property of actions created by this action creator, if it is known. */
- string getTypeTag() { result = range.getTypeTag() }
+ string getTypeTag() { result = this.(ActionCreator::Range).getTypeTag() }
/**
* Gets the middleware function that transforms arguments passed to this function into the
@@ -442,7 +438,7 @@ module Redux {
* the action payload. Otherwise, the return value is the payload itself.
*/
DataFlow::FunctionNode getMiddlewareFunction(boolean async) {
- result = range.getMiddlewareFunction(async)
+ result = this.(ActionCreator::Range).getMiddlewareFunction(async)
}
/** Gets a data flow node referring to this action creator. */
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll b/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll
index 3fc1644fdfd..385243436a1 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll
@@ -15,12 +15,10 @@ module ShellJS {
/** A member of the `shelljs` library. */
class Member extends DataFlow::SourceNode {
- Member::Range range;
-
- Member() { this = range }
+ Member() { this instanceof Member::Range }
/** Gets the name of `shelljs` member being referenced, such as `cat` in `shelljs.cat`. */
- string getName() { result = range.getName() }
+ string getName() { result = this.(Member::Range).getName() }
}
module Member {
From 85e1c87d14801c60f36b9467680bead339aa18a6 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Mon, 6 Sep 2021 11:19:50 +0200
Subject: [PATCH 122/459] use the new non-extending-subtypes syntax
---
.../ql/lib/semmle/javascript/Base64.qll | 16 +++---
.../ql/lib/semmle/javascript/Closure.qll | 11 ++--
.../lib/semmle/javascript/InclusionTests.qll | 10 ++--
.../javascript/MembershipCandidates.qll | 12 ++---
.../ql/lib/semmle/javascript/StringOps.qll | 32 +++++------
.../lib/semmle/javascript/dataflow/Nodes.qll | 54 ++++++++-----------
.../semmle/javascript/frameworks/Cheerio.qll | 3 +-
.../javascript/frameworks/ClientRequests.qll | 14 +++--
.../frameworks/ComposedFunctions.qll | 10 ++--
.../javascript/frameworks/EventEmitter.qll | 36 +++++--------
.../lib/semmle/javascript/frameworks/HTTP.qll | 6 +--
.../frameworks/PropertyProjection.qll | 10 ++--
.../semmle/javascript/frameworks/Redux.qll | 14 ++---
.../semmle/javascript/frameworks/ShellJS.qll | 6 +--
14 files changed, 87 insertions(+), 147 deletions(-)
diff --git a/javascript/ql/lib/semmle/javascript/Base64.qll b/javascript/ql/lib/semmle/javascript/Base64.qll
index d7a463da2b0..b9743fb4ec2 100644
--- a/javascript/ql/lib/semmle/javascript/Base64.qll
+++ b/javascript/ql/lib/semmle/javascript/Base64.qll
@@ -6,14 +6,12 @@ import javascript
module Base64 {
/** A call to a base64 encoder. */
- class Encode extends DataFlow::Node {
- Encode() { this instanceof Encode::Range }
-
+ class Encode extends DataFlow::Node instanceof Encode::Range {
/** Gets the input passed to the encoder. */
- DataFlow::Node getInput() { result = this.(Encode::Range).getInput() }
+ DataFlow::Node getInput() { result = super.getInput() }
/** Gets the base64-encoded output of the encoder. */
- DataFlow::Node getOutput() { result = this.(Encode::Range).getOutput() }
+ DataFlow::Node getOutput() { result = super.getOutput() }
}
module Encode {
@@ -32,14 +30,12 @@ module Base64 {
}
/** A call to a base64 decoder. */
- class Decode extends DataFlow::Node {
- Decode() { this instanceof Decode::Range }
-
+ class Decode extends DataFlow::Node instanceof Decode::Range {
/** Gets the base64-encoded input passed to the decoder. */
- DataFlow::Node getInput() { result = this.(Decode::Range).getInput() }
+ DataFlow::Node getInput() { result = super.getInput() }
/** Gets the output of the decoder. */
- DataFlow::Node getOutput() { result = this.(Decode::Range).getOutput() }
+ DataFlow::Node getOutput() { result = super.getOutput() }
}
module Decode {
diff --git a/javascript/ql/lib/semmle/javascript/Closure.qll b/javascript/ql/lib/semmle/javascript/Closure.qll
index 637dc6f9687..ca2c4a4fcd8 100644
--- a/javascript/ql/lib/semmle/javascript/Closure.qll
+++ b/javascript/ql/lib/semmle/javascript/Closure.qll
@@ -8,15 +8,11 @@ module Closure {
/**
* A reference to a Closure namespace.
*/
- class ClosureNamespaceRef extends DataFlow::Node {
- ClosureNamespaceRef() { this instanceof ClosureNamespaceRef::Range }
-
+ class ClosureNamespaceRef extends DataFlow::Node instanceof ClosureNamespaceRef::Range {
/**
* Gets the namespace being referenced.
*/
- string getClosureNamespace() {
- result = this.(ClosureNamespaceRef::Range).getClosureNamespace()
- }
+ string getClosureNamespace() { result = super.getClosureNamespace() }
}
module ClosureNamespaceRef {
@@ -36,8 +32,7 @@ module Closure {
/**
* A data flow node that returns the value of a closure namespace.
*/
- class ClosureNamespaceAccess extends ClosureNamespaceRef {
- ClosureNamespaceAccess() { this instanceof ClosureNamespaceAccess::Range }
+ class ClosureNamespaceAccess extends ClosureNamespaceRef instanceof ClosureNamespaceAccess::Range {
}
module ClosureNamespaceAccess {
diff --git a/javascript/ql/lib/semmle/javascript/InclusionTests.qll b/javascript/ql/lib/semmle/javascript/InclusionTests.qll
index 69f037a664d..b714c670b19 100644
--- a/javascript/ql/lib/semmle/javascript/InclusionTests.qll
+++ b/javascript/ql/lib/semmle/javascript/InclusionTests.qll
@@ -16,14 +16,12 @@ private import javascript
* ~A.indexOf(B)
* ```
*/
-class InclusionTest extends DataFlow::Node {
- InclusionTest() { this instanceof InclusionTest::Range }
-
+class InclusionTest extends DataFlow::Node instanceof InclusionTest::Range {
/** Gets the `A` in `A.includes(B)`. */
- DataFlow::Node getContainerNode() { result = this.(InclusionTest::Range).getContainerNode() }
+ DataFlow::Node getContainerNode() { result = super.getContainerNode() }
/** Gets the `B` in `A.includes(B)`. */
- DataFlow::Node getContainedNode() { result = this.(InclusionTest::Range).getContainedNode() }
+ DataFlow::Node getContainedNode() { result = super.getContainedNode() }
/**
* Gets the polarity of the check.
@@ -31,7 +29,7 @@ class InclusionTest extends DataFlow::Node {
* If the polarity is `false` the check returns `true` if the container does not contain
* the given element.
*/
- boolean getPolarity() { result = this.(InclusionTest::Range).getPolarity() }
+ boolean getPolarity() { result = super.getPolarity() }
}
module InclusionTest {
diff --git a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll
index 6cc6aa53fe1..fe46eff040e 100644
--- a/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll
+++ b/javascript/ql/lib/semmle/javascript/MembershipCandidates.qll
@@ -9,25 +9,23 @@ import javascript
*
* Additional candidates can be added by subclassing `MembershipCandidate::Range`
*/
-class MembershipCandidate extends DataFlow::Node {
- MembershipCandidate() { this instanceof MembershipCandidate::Range }
-
+class MembershipCandidate extends DataFlow::Node instanceof MembershipCandidate::Range {
/**
* Gets the expression that performs the membership test, if any.
*/
- DataFlow::Node getTest() { result = this.(MembershipCandidate::Range).getTest() }
+ DataFlow::Node getTest() { result = super.getTest() }
/**
* Gets a string that this candidate is tested against, if
* it can be determined.
*/
- string getAMemberString() { result = this.(MembershipCandidate::Range).getAMemberString() }
+ string getAMemberString() { result = super.getAMemberString() }
/**
* Gets a node that this candidate is tested against, if
* it can be determined.
*/
- DataFlow::Node getAMemberNode() { result = this.(MembershipCandidate::Range).getAMemberNode() }
+ DataFlow::Node getAMemberNode() { result = super.getAMemberNode() }
/**
* Gets the polarity of the test.
@@ -35,7 +33,7 @@ class MembershipCandidate extends DataFlow::Node {
* If the polarity is `false` the test returns `true` if the
* collection does not contain this candidate.
*/
- boolean getTestPolarity() { result = this.(MembershipCandidate::Range).getTestPolarity() }
+ boolean getTestPolarity() { result = super.getTestPolarity() }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/StringOps.qll b/javascript/ql/lib/semmle/javascript/StringOps.qll
index 78083720fcf..53390c4230d 100644
--- a/javascript/ql/lib/semmle/javascript/StringOps.qll
+++ b/javascript/ql/lib/semmle/javascript/StringOps.qll
@@ -8,18 +8,16 @@ module StringOps {
/**
* A expression that is equivalent to `A.startsWith(B)` or `!A.startsWith(B)`.
*/
- class StartsWith extends DataFlow::Node {
- StartsWith() { this instanceof StartsWith::Range }
-
+ class StartsWith extends DataFlow::Node instanceof StartsWith::Range {
/**
* Gets the `A` in `A.startsWith(B)`.
*/
- DataFlow::Node getBaseString() { result = this.(StartsWith::Range).getBaseString() }
+ DataFlow::Node getBaseString() { result = super.getBaseString() }
/**
* Gets the `B` in `A.startsWith(B)`.
*/
- DataFlow::Node getSubstring() { result = this.(StartsWith::Range).getSubstring() }
+ DataFlow::Node getSubstring() { result = super.getSubstring() }
/**
* Gets the polarity of the check.
@@ -27,7 +25,7 @@ module StringOps {
* If the polarity is `false` the check returns `true` if the string does not start
* with the given substring.
*/
- boolean getPolarity() { result = this.(StartsWith::Range).getPolarity() }
+ boolean getPolarity() { result = super.getPolarity() }
}
module StartsWith {
@@ -235,18 +233,16 @@ module StringOps {
/**
* An expression that is equivalent to `A.endsWith(B)` or `!A.endsWith(B)`.
*/
- class EndsWith extends DataFlow::Node {
- EndsWith() { this instanceof EndsWith::Range }
-
+ class EndsWith extends DataFlow::Node instanceof EndsWith::Range {
/**
* Gets the `A` in `A.startsWith(B)`.
*/
- DataFlow::Node getBaseString() { result = this.(EndsWith::Range).getBaseString() }
+ DataFlow::Node getBaseString() { result = super.getBaseString() }
/**
* Gets the `B` in `A.startsWith(B)`.
*/
- DataFlow::Node getSubstring() { result = this.(EndsWith::Range).getSubstring() }
+ DataFlow::Node getSubstring() { result = super.getSubstring() }
/**
* Gets the polarity if the check.
@@ -254,7 +250,7 @@ module StringOps {
* If the polarity is `false` the check returns `true` if the string does not end
* with the given substring.
*/
- boolean getPolarity() { result = this.(EndsWith::Range).getPolarity() }
+ boolean getPolarity() { result = super.getPolarity() }
}
module EndsWith {
@@ -658,16 +654,14 @@ module StringOps {
* if (!match) { ... } // <--- 'match' is the RegExpTest
* ```
*/
- class RegExpTest extends DataFlow::Node {
- RegExpTest() { this instanceof RegExpTest::Range }
-
+ class RegExpTest extends DataFlow::Node instanceof RegExpTest::Range {
/**
* Gets the AST of the regular expression used in the test, if it can be seen locally.
*/
RegExpTerm getRegExp() {
result = getRegExpOperand().getALocalSource().(DataFlow::RegExpCreationNode).getRoot()
or
- result = this.(RegExpTest::Range).getRegExpOperand(true).asExpr().(StringLiteral).asRegExp()
+ result = super.getRegExpOperand(true).asExpr().(StringLiteral).asRegExp()
}
/**
@@ -675,12 +669,12 @@ module StringOps {
*
* In some cases this represents a string value being coerced to a RegExp object.
*/
- DataFlow::Node getRegExpOperand() { result = this.(RegExpTest::Range).getRegExpOperand(_) }
+ DataFlow::Node getRegExpOperand() { result = super.getRegExpOperand(_) }
/**
* Gets the data flow node corresponding to the string being tested against the regular expression.
*/
- DataFlow::Node getStringOperand() { result = this.(RegExpTest::Range).getStringOperand() }
+ DataFlow::Node getStringOperand() { result = super.getStringOperand() }
/**
* Gets the return value indicating that the string matched the regular expression.
@@ -688,7 +682,7 @@ module StringOps {
* For example, for `regexp.exec(str) == null`, the polarity is `false`, and for
* `regexp.exec(str) != null` the polarity is `true`.
*/
- boolean getPolarity() { result = this.(RegExpTest::Range).getPolarity() }
+ boolean getPolarity() { result = super.getPolarity() }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll
index f8bdb6f46ad..c697c43dcce 100644
--- a/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll
+++ b/javascript/ql/lib/semmle/javascript/dataflow/Nodes.qll
@@ -702,11 +702,9 @@ class ArrayCreationNode extends DataFlow::ValueNode, DataFlow::SourceNode {
* define(["fs"], function(fs) { ... }); // AMD module
* ```
*/
-class ModuleImportNode extends DataFlow::SourceNode {
- ModuleImportNode() { this instanceof ModuleImportNode::Range }
-
+class ModuleImportNode extends DataFlow::SourceNode instanceof ModuleImportNode::Range {
/** Gets the path of the imported module. */
- string getPath() { result = this.(ModuleImportNode::Range).getPath() }
+ string getPath() { result = super.getPath() }
}
module ModuleImportNode {
@@ -842,23 +840,21 @@ module MemberKind {
*
* Additional patterns can be recognized as class nodes, by extending `DataFlow::ClassNode::Range`.
*/
-class ClassNode extends DataFlow::SourceNode {
- ClassNode() { this instanceof ClassNode::Range }
-
+class ClassNode extends DataFlow::SourceNode instanceof ClassNode::Range {
/**
* Gets the unqualified name of the class, if it has one or one can be determined from the context.
*/
- string getName() { result = this.(ClassNode::Range).getName() }
+ string getName() { result = super.getName() }
/**
* Gets a description of the class.
*/
- string describe() { result = this.(ClassNode::Range).describe() }
+ string describe() { result = super.describe() }
/**
* Gets the constructor function of this class.
*/
- FunctionNode getConstructor() { result = this.(ClassNode::Range).getConstructor() }
+ FunctionNode getConstructor() { result = super.getConstructor() }
/**
* Gets an instance method declared in this class, with the given name, if any.
@@ -866,7 +862,7 @@ class ClassNode extends DataFlow::SourceNode {
* Does not include methods from superclasses.
*/
FunctionNode getInstanceMethod(string name) {
- result = this.(ClassNode::Range).getInstanceMember(name, MemberKind::method())
+ result = super.getInstanceMember(name, MemberKind::method())
}
/**
@@ -876,9 +872,7 @@ class ClassNode extends DataFlow::SourceNode {
*
* Does not include methods from superclasses.
*/
- FunctionNode getAnInstanceMethod() {
- result = this.(ClassNode::Range).getAnInstanceMember(MemberKind::method())
- }
+ FunctionNode getAnInstanceMethod() { result = super.getAnInstanceMember(MemberKind::method()) }
/**
* Gets the instance method, getter, or setter with the given name and kind.
@@ -886,7 +880,7 @@ class ClassNode extends DataFlow::SourceNode {
* Does not include members from superclasses.
*/
FunctionNode getInstanceMember(string name, MemberKind kind) {
- result = this.(ClassNode::Range).getInstanceMember(name, kind)
+ result = super.getInstanceMember(name, kind)
}
/**
@@ -894,35 +888,31 @@ class ClassNode extends DataFlow::SourceNode {
*
* Does not include members from superclasses.
*/
- FunctionNode getAnInstanceMember(MemberKind kind) {
- result = this.(ClassNode::Range).getAnInstanceMember(kind)
- }
+ FunctionNode getAnInstanceMember(MemberKind kind) { result = super.getAnInstanceMember(kind) }
/**
* Gets an instance method, getter, or setter declared in this class.
*
* Does not include members from superclasses.
*/
- FunctionNode getAnInstanceMember() { result = this.(ClassNode::Range).getAnInstanceMember(_) }
+ FunctionNode getAnInstanceMember() { result = super.getAnInstanceMember(_) }
/**
* Gets the static method declared in this class with the given name.
*/
- FunctionNode getStaticMethod(string name) {
- result = this.(ClassNode::Range).getStaticMethod(name)
- }
+ FunctionNode getStaticMethod(string name) { result = super.getStaticMethod(name) }
/**
* Gets a static method declared in this class.
*
* The constructor is not considered a static method.
*/
- FunctionNode getAStaticMethod() { result = this.(ClassNode::Range).getAStaticMethod() }
+ FunctionNode getAStaticMethod() { result = super.getAStaticMethod() }
/**
* Gets a dataflow node that refers to the superclass of this class.
*/
- DataFlow::Node getASuperClassNode() { result = this.(ClassNode::Range).getASuperClassNode() }
+ DataFlow::Node getASuperClassNode() { result = super.getASuperClassNode() }
/**
* Gets a direct super class of this class.
@@ -1068,13 +1058,13 @@ class ClassNode extends DataFlow::SourceNode {
* Gets the type annotation for the field `fieldName`, if any.
*/
TypeAnnotation getFieldTypeAnnotation(string fieldName) {
- result = this.(ClassNode::Range).getFieldTypeAnnotation(fieldName)
+ result = super.getFieldTypeAnnotation(fieldName)
}
/**
* Gets a decorator applied to this class.
*/
- DataFlow::Node getADecorator() { result = this.(ClassNode::Range).getADecorator() }
+ DataFlow::Node getADecorator() { result = super.getADecorator() }
}
module ClassNode {
@@ -1360,9 +1350,7 @@ module ClassNode {
* _.partial(fn, x, y, z)
* ```
*/
-class PartialInvokeNode extends DataFlow::Node {
- PartialInvokeNode() { this instanceof PartialInvokeNode::Range }
-
+class PartialInvokeNode extends DataFlow::Node instanceof PartialInvokeNode::Range {
/** Gets a node holding a callback invoked by this partial invocation node. */
DataFlow::Node getACallbackNode() {
isPartialArgument(result, _, _)
@@ -1374,26 +1362,26 @@ class PartialInvokeNode extends DataFlow::Node {
* Holds if `argument` is passed as argument `index` to the function in `callback`.
*/
predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
- this.(PartialInvokeNode::Range).isPartialArgument(callback, argument, index)
+ super.isPartialArgument(callback, argument, index)
}
/**
* Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound.
*/
DataFlow::SourceNode getBoundFunction(DataFlow::Node callback, int boundArgs) {
- result = this.(PartialInvokeNode::Range).getBoundFunction(callback, boundArgs)
+ result = super.getBoundFunction(callback, boundArgs)
}
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
- DataFlow::Node getBoundReceiver() { result = this.(PartialInvokeNode::Range).getBoundReceiver(_) }
+ DataFlow::Node getBoundReceiver() { result = super.getBoundReceiver(_) }
/**
* Gets the node holding the receiver to be passed to the bound function, if specified.
*/
DataFlow::Node getBoundReceiver(DataFlow::Node callback) {
- result = this.(PartialInvokeNode::Range).getBoundReceiver(callback)
+ result = super.getBoundReceiver(callback)
}
}
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll b/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll
index 182f760ac04..beffe148ee3 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/Cheerio.qll
@@ -32,8 +32,7 @@ module Cheerio {
* Creation of `cheerio` object, a collection of virtual DOM elements
* with an interface similar to that of a jQuery object.
*/
- class CheerioObjectCreation extends DataFlow::SourceNode {
- CheerioObjectCreation() { this instanceof CheerioObjectCreation::Range }
+ class CheerioObjectCreation extends DataFlow::SourceNode instanceof CheerioObjectCreation::Range {
}
module CheerioObjectCreation {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll
index f15821a403e..82670947d3d 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/ClientRequests.qll
@@ -18,23 +18,21 @@ import javascript
* To model additional APIs, extend `ClientRequest::Range` and implement its abstract member
* predicates.
*/
-class ClientRequest extends DataFlow::InvokeNode {
- ClientRequest() { this instanceof ClientRequest::Range }
-
+class ClientRequest extends DataFlow::InvokeNode instanceof ClientRequest::Range {
/**
* Gets the URL of the request.
*/
- DataFlow::Node getUrl() { result = this.(ClientRequest::Range).getUrl() }
+ DataFlow::Node getUrl() { result = super.getUrl() }
/**
* Gets the host of the request.
*/
- DataFlow::Node getHost() { result = this.(ClientRequest::Range).getHost() }
+ DataFlow::Node getHost() { result = super.getHost() }
/**
* Gets a node that contributes to the data-part this request.
*/
- DataFlow::Node getADataNode() { result = this.(ClientRequest::Range).getADataNode() }
+ DataFlow::Node getADataNode() { result = super.getADataNode() }
/**
* Gets a data flow node that refers to some representation of the response, possibly
@@ -58,7 +56,7 @@ class ClientRequest extends DataFlow::InvokeNode {
* - Any value provided by custom implementations of `ClientRequest::Range`.
*/
DataFlow::Node getAResponseDataNode(string responseType, boolean promise) {
- result = this.(ClientRequest::Range).getAResponseDataNode(responseType, promise)
+ result = super.getAResponseDataNode(responseType, promise)
}
/**
@@ -70,7 +68,7 @@ class ClientRequest extends DataFlow::InvokeNode {
/**
* Gets a data-flow node that determines where in the file-system the result of the request should be saved.
*/
- DataFlow::Node getASavePath() { result = this.(ClientRequest::Range).getASavePath() }
+ DataFlow::Node getASavePath() { result = super.getASavePath() }
}
deprecated class CustomClientRequest = ClientRequest::Range;
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll b/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll
index 4cd2c9e3c65..6887758b064 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/ComposedFunctions.qll
@@ -8,18 +8,14 @@ import javascript
* A call to a function that constructs a function composition `f(g(h(...)))` from a
* series of functions `f, g, h, ...`.
*/
-class FunctionCompositionCall extends DataFlow::CallNode {
- FunctionCompositionCall() { this instanceof FunctionCompositionCall::Range }
-
+class FunctionCompositionCall extends DataFlow::CallNode instanceof FunctionCompositionCall::Range {
/**
* Gets the `i`th function in the composition `f(g(h(...)))`, counting from left to right.
*
* Note that this is the opposite of the order in which the function are invoked,
* that is, `g` occurs later than `f` in `f(g(...))` but is invoked before `f`.
*/
- DataFlow::Node getOperandNode(int i) {
- result = this.(FunctionCompositionCall::Range).getOperandNode(i)
- }
+ DataFlow::Node getOperandNode(int i) { result = super.getOperandNode(i) }
/** Gets a node holding one of the functions to be composed. */
final DataFlow::Node getAnOperandNode() { result = getOperandNode(_) }
@@ -38,7 +34,7 @@ class FunctionCompositionCall extends DataFlow::CallNode {
final DataFlow::FunctionNode getAnOperandFunction() { result = getOperandFunction(_) }
/** Gets the number of functions being composed. */
- int getNumOperand() { result = this.(FunctionCompositionCall::Range).getNumOperand() }
+ int getNumOperand() { result = super.getNumOperand() }
}
/**
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll b/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
index c428f070cd5..67b746e4473 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/EventEmitter.qll
@@ -68,42 +68,32 @@ module EventEmitter {
* An EventEmitter instance that implements the EventEmitter API.
* Extend EventEmitter::Range to mark something as being an EventEmitter.
*/
-class EventEmitter extends DataFlow::Node {
- EventEmitter() { this instanceof EventEmitter::Range }
-}
+class EventEmitter extends DataFlow::Node instanceof EventEmitter::Range { }
/**
* A registration of an event handler on an EventEmitter.
*/
-class EventRegistration extends DataFlow::Node {
- EventRegistration() { this instanceof EventRegistration::Range }
-
+class EventRegistration extends DataFlow::Node instanceof EventRegistration::Range {
/** Gets the EventEmitter that the event handler is registered on. */
- final EventEmitter getEmitter() { result = this.(EventRegistration::Range).getEmitter() }
+ final EventEmitter getEmitter() { result = super.getEmitter() }
/** Gets the name of the channel if possible. */
- string getChannel() { result = this.(EventRegistration::Range).getChannel() }
+ string getChannel() { result = super.getChannel() }
/** Gets the `i`th parameter in the event handler. */
- DataFlow::Node getReceivedItem(int i) {
- result = this.(EventRegistration::Range).getReceivedItem(i)
- }
+ DataFlow::Node getReceivedItem(int i) { result = super.getReceivedItem(i) }
/**
* Gets a value that is returned by the event handler.
* The default implementation is that no value can be returned.
*/
- DataFlow::Node getAReturnedValue() {
- result = this.(EventRegistration::Range).getAReturnedValue()
- }
+ DataFlow::Node getAReturnedValue() { result = super.getAReturnedValue() }
/**
* Get a dispatch that this event handler can return a value to.
* The default implementation is that there exists no such dispatch.
*/
- EventDispatch getAReturnDispatch() {
- result = this.(EventRegistration::Range).getAReturnDispatch()
- }
+ EventDispatch getAReturnDispatch() { result = super.getAReturnDispatch() }
}
module EventRegistration {
@@ -142,24 +132,22 @@ module EventRegistration {
/**
* A dispatch of an event on an EventEmitter.
*/
-class EventDispatch extends DataFlow::Node {
- EventDispatch() { this instanceof EventDispatch::Range }
-
+class EventDispatch extends DataFlow::Node instanceof EventDispatch::Range {
/** Gets the emitter that the event dispatch happens on. */
- EventEmitter getEmitter() { result = this.(EventDispatch::Range).getEmitter() }
+ EventEmitter getEmitter() { result = super.getEmitter() }
/** Gets the name of the channel if possible. */
- string getChannel() { result = this.(EventDispatch::Range).getChannel() }
+ string getChannel() { result = super.getChannel() }
/** Gets the `i`th argument that is send to the event handler. */
- DataFlow::Node getSentItem(int i) { result = this.(EventDispatch::Range).getSentItem(i) }
+ DataFlow::Node getSentItem(int i) { result = super.getSentItem(i) }
/**
* Get an EventRegistration that this event dispatch can send an event to.
* The default implementation is that the emitters of the dispatch and registration have to be equal.
* Channels are by default ignored.
*/
- EventRegistration getAReceiver() { result = this.(EventDispatch::Range).getAReceiver() }
+ EventRegistration getAReceiver() { result = super.getAReceiver() }
}
module EventDispatch {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll b/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll
index 1154dff7452..94f9c4245b1 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/HTTP.qll
@@ -540,14 +540,12 @@ module HTTP {
/**
* An object that contains one or more potential route handlers.
*/
- class RouteHandlerCandidateContainer extends DataFlow::Node {
- RouteHandlerCandidateContainer() { this instanceof RouteHandlerCandidateContainer::Range }
-
+ class RouteHandlerCandidateContainer extends DataFlow::Node instanceof RouteHandlerCandidateContainer::Range {
/**
* Gets the route handler in this container that is accessed at `access`.
*/
DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
- result = this.(RouteHandlerCandidateContainer::Range).getRouteHandler(access)
+ result = super.getRouteHandler(access)
}
}
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll b/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll
index f6db62d5632..730669abfaf 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/PropertyProjection.qll
@@ -14,18 +14,16 @@ import javascript
* To model additional APIs, extend `PropertyProjection::Range` and implement its abstract member
* predicates.
*/
-class PropertyProjection extends DataFlow::CallNode {
- PropertyProjection() { this instanceof PropertyProjection::Range }
-
+class PropertyProjection extends DataFlow::CallNode instanceof PropertyProjection::Range {
/**
* Gets the argument for the object to project properties from, such as `o` in `_.get(o, 'a.b')`.
*/
- DataFlow::Node getObject() { result = this.(PropertyProjection::Range).getObject() }
+ DataFlow::Node getObject() { result = super.getObject() }
/**
* Gets an argument that selects the properties to project, such as `'a.b'` in `_.get(o, 'a.b')`.
*/
- DataFlow::Node getASelector() { result = this.(PropertyProjection::Range).getASelector() }
+ DataFlow::Node getASelector() { result = super.getASelector() }
/**
* Holds if this call returns the value of a single projected property, as opposed to an object that can contain multiple projected properties.
@@ -34,7 +32,7 @@ class PropertyProjection extends DataFlow::CallNode {
* - This predicate holds for `_.get({a: 'b'}, 'a')`, which returns `'b'`,
* - This predicate does not hold for `_.pick({a: 'b', c: 'd'}}, 'a')`, which returns `{a: 'b'}`,
*/
- predicate isSingletonProjection() { this.(PropertyProjection::Range).isSingletonProjection() }
+ predicate isSingletonProjection() { super.isSingletonProjection() }
}
module PropertyProjection {
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
index cc14e88712a..d26982cef7f 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/Redux.qll
@@ -56,9 +56,7 @@ module Redux {
/**
* Creation of a redux store, usually via a call to `createStore`.
*/
- class StoreCreation extends DataFlow::SourceNode {
- StoreCreation() { this instanceof StoreCreation::Range }
-
+ class StoreCreation extends DataFlow::SourceNode instanceof StoreCreation::Range {
/** Gets a reference to the store. */
DataFlow::SourceNode ref() { result = asApiNode().getAUse() }
@@ -66,7 +64,7 @@ module Redux {
API::Node asApiNode() { result.getAnImmediateUse() = this }
/** Gets the data flow node holding the root reducer for this store. */
- DataFlow::Node getReducerArg() { result = this.(StoreCreation::Range).getReducerArg() }
+ DataFlow::Node getReducerArg() { result = super.getReducerArg() }
/** Gets a data flow node referring to the root reducer. */
DataFlow::SourceNode getAReducerSource() { result = getReducerArg().(ReducerArg).getASource() }
@@ -421,11 +419,9 @@ module Redux {
* Some action creators dispatch the action to a store, while for others, the value is returned and it is simply assumed to be dispatched
* at some point. We model all action creators as if they dispatch the action they create.
*/
- class ActionCreator extends DataFlow::SourceNode {
- ActionCreator() { this instanceof ActionCreator::Range }
-
+ class ActionCreator extends DataFlow::SourceNode instanceof ActionCreator::Range {
/** Gets the `type` property of actions created by this action creator, if it is known. */
- string getTypeTag() { result = this.(ActionCreator::Range).getTypeTag() }
+ string getTypeTag() { result = super.getTypeTag() }
/**
* Gets the middleware function that transforms arguments passed to this function into the
@@ -438,7 +434,7 @@ module Redux {
* the action payload. Otherwise, the return value is the payload itself.
*/
DataFlow::FunctionNode getMiddlewareFunction(boolean async) {
- result = this.(ActionCreator::Range).getMiddlewareFunction(async)
+ result = super.getMiddlewareFunction(async)
}
/** Gets a data flow node referring to this action creator. */
diff --git a/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll b/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll
index 385243436a1..4ba780b0480 100644
--- a/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll
+++ b/javascript/ql/lib/semmle/javascript/frameworks/ShellJS.qll
@@ -14,11 +14,9 @@ module ShellJS {
}
/** A member of the `shelljs` library. */
- class Member extends DataFlow::SourceNode {
- Member() { this instanceof Member::Range }
-
+ class Member extends DataFlow::SourceNode instanceof Member::Range {
/** Gets the name of `shelljs` member being referenced, such as `cat` in `shelljs.cat`. */
- string getName() { result = this.(Member::Range).getName() }
+ string getName() { result = super.getName() }
}
module Member {
From 39a88d2e4376b122ea9e665d8a07ff90e11a01ba Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 17 Aug 2021 14:32:07 +0200
Subject: [PATCH 123/459] Fix dispatch library to handle summarized callables
with no runtime target
---
.../code/csharp/dataflow/internal/DataFlowDispatch.qll | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
index 0f233d7e92d..d38975f24bb 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
@@ -8,6 +8,7 @@ private import FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.ExternalFlow
private import semmle.code.csharp.dispatch.Dispatch
+private import semmle.code.csharp.dispatch.RuntimeCallable
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.collections.Generic
@@ -275,6 +276,10 @@ class NonDelegateDataFlowCall extends DataFlowCall, TNonDelegateCall {
override DataFlowCallable getARuntimeTarget() {
result = getCallableForDataFlow(dc.getADynamicTarget())
+ or
+ result = dc.getAStaticTarget().getUnboundDeclaration() and
+ summarizedCallable(result) and
+ not result instanceof RuntimeCallable
}
override ControlFlow::Nodes::ElementNode getControlFlowNode() { result = cfn }
From 270b56af1bb161684474cecf9e6895b30b785c7d Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 17 Aug 2021 14:32:45 +0200
Subject: [PATCH 124/459] Extend runtime callables to interface members with
default implementation
---
.../ql/src/semmle/code/csharp/dispatch/RuntimeCallable.qll | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/csharp/ql/src/semmle/code/csharp/dispatch/RuntimeCallable.qll b/csharp/ql/src/semmle/code/csharp/dispatch/RuntimeCallable.qll
index 22758fce4ab..bb279fcb4fb 100644
--- a/csharp/ql/src/semmle/code/csharp/dispatch/RuntimeCallable.qll
+++ b/csharp/ql/src/semmle/code/csharp/dispatch/RuntimeCallable.qll
@@ -15,7 +15,10 @@ private import dotnet
class RuntimeCallable extends DotNet::Callable {
RuntimeCallable() {
not this.(Modifiable).isAbstract() and
- not getDeclaringType() instanceof Interface
+ (
+ not getDeclaringType() instanceof Interface or
+ this.(Virtualizable).isVirtual()
+ )
}
}
From e3a49f82131acfa7e46ac79767447b86da349a27 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 11 Aug 2021 11:02:04 +0200
Subject: [PATCH 125/459] C#: improve stubbing to escape more member names (not
just fields)
---
csharp/ql/src/Stubs/Stubs.qll | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/csharp/ql/src/Stubs/Stubs.qll b/csharp/ql/src/Stubs/Stubs.qll
index 4a178980a52..c1815520486 100644
--- a/csharp/ql/src/Stubs/Stubs.qll
+++ b/csharp/ql/src/Stubs/Stubs.qll
@@ -759,8 +759,9 @@ private string stubMethod(Method m, Assembly assembly) {
then
result =
" " + stubModifiers(m) + stubClassName(m.(Method).getReturnType()) + " " +
- stubExplicitImplementation(m) + m.getName() + stubGenericMethodParams(m) + "(" +
- stubParameters(m) + ")" + stubTypeParametersConstraints(m) + stubImplementation(m) + ";\n"
+ stubExplicitImplementation(m) + escapeIfKeyword(m.getName()) + stubGenericMethodParams(m) +
+ "(" + stubParameters(m) + ")" + stubTypeParametersConstraints(m) + stubImplementation(m) +
+ ";\n"
else result = " // Stub generator skipped method: " + m.getName() + "\n"
}
@@ -786,7 +787,7 @@ pragma[noinline]
private string stubEnumConstant(EnumConstant ec, Assembly assembly) {
ec instanceof GeneratedMember and
ec.getALocation() = assembly and
- result = " " + ec.getName() + ",\n"
+ result = " " + escapeIfKeyword(ec.getName()) + ",\n"
}
pragma[noinline]
@@ -795,7 +796,7 @@ private string stubProperty(Property p, Assembly assembly) {
p.getALocation() = assembly and
result =
" " + stubModifiers(p) + stubClassName(p.getType()) + " " + stubExplicitImplementation(p) +
- p.getName() + " { " + stubGetter(p) + stubSetter(p) + "}\n"
+ escapeIfKeyword(p.getName()) + " { " + stubGetter(p) + stubSetter(p) + "}\n"
}
pragma[noinline]
@@ -810,7 +811,7 @@ private string stubConstructor(Constructor c, Assembly assembly) {
c.getNumberOfParameters() > 0
then
result =
- " " + stubModifiers(c) + c.getName() + "(" + stubParameters(c) + ")" +
+ " " + stubModifiers(c) + escapeIfKeyword(c.getName()) + "(" + stubParameters(c) + ")" +
stubConstructorInitializer(c) + " => throw null;\n"
else result = " // Stub generator skipped constructor \n"
}
@@ -844,7 +845,7 @@ private string stubEvent(Event e, Assembly assembly) {
e.getALocation() = assembly and
result =
" " + stubModifiers(e) + "event " + stubClassName(e.getType()) + " " +
- stubExplicitImplementation(e) + e.getName() + stubEventAccessors(e) + "\n"
+ stubExplicitImplementation(e) + escapeIfKeyword(e.getName()) + stubEventAccessors(e) + "\n"
}
pragma[nomagic]
From 43ccc14162754cca30cc2bb5a3526263e08b2d41 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 11 Aug 2021 11:05:58 +0200
Subject: [PATCH 126/459] Add ServiceStack stubs and empty test referencing it
---
.../frameworks/ServiceStack/ServiceStack.cs | 9 +
.../frameworks/ServiceStack/options | 1 +
.../ServiceStack/remoteFlowSource.expected | 0
.../ServiceStack/remoteFlowSource.ql | 7 +
.../5.11.0/ServiceStack.Client.cs | 1924 +++
.../5.11.0/ServiceStack.Client.csproj | 14 +
.../5.11.0/ServiceStack.Common.cs | 5620 ++++++++
.../5.11.0/ServiceStack.Common.csproj | 14 +
.../5.11.0/ServiceStack.Interfaces.cs | 5646 ++++++++
.../5.11.0/ServiceStack.Interfaces.csproj | 12 +
.../5.11.0/ServiceStack.OrmLite.SqlServer.cs | 351 +
.../ServiceStack.OrmLite.SqlServer.csproj | 15 +
.../5.11.0/ServiceStack.OrmLite.cs | 3420 +++++
.../5.11.0/ServiceStack.OrmLite.csproj | 13 +
.../5.11.0/ServiceStack.Redis.cs | 3016 ++++
.../5.11.0/ServiceStack.Redis.csproj | 13 +
.../5.11.0/ServiceStack.Text.cs | 3867 ++++++
.../5.11.0/ServiceStack.Text.csproj | 12 +
.../stubs/ServiceStack/5.11.0/ServiceStack.cs | 11544 ++++++++++++++++
.../ServiceStack/5.11.0/ServiceStack.csproj | 18 +
.../4.7.0/System.Drawing.Common.cs | 3168 +++++
.../4.7.0/System.Drawing.Common.csproj | 12 +
22 files changed, 38696 insertions(+)
create mode 100644 csharp/ql/test/library-tests/frameworks/ServiceStack/ServiceStack.cs
create mode 100644 csharp/ql/test/library-tests/frameworks/ServiceStack/options
create mode 100644 csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.expected
create mode 100644 csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.ql
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Client/5.11.0/ServiceStack.Client.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Client/5.11.0/ServiceStack.Client.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Common/5.11.0/ServiceStack.Common.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Common/5.11.0/ServiceStack.Common.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Interfaces/5.11.0/ServiceStack.Interfaces.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Interfaces/5.11.0/ServiceStack.Interfaces.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.OrmLite.SqlServer/5.11.0/ServiceStack.OrmLite.SqlServer.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.OrmLite.SqlServer/5.11.0/ServiceStack.OrmLite.SqlServer.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.OrmLite/5.11.0/ServiceStack.OrmLite.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.OrmLite/5.11.0/ServiceStack.OrmLite.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Redis/5.11.0/ServiceStack.Redis.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Redis/5.11.0/ServiceStack.Redis.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Text/5.11.0/ServiceStack.Text.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack.Text/5.11.0/ServiceStack.Text.csproj
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack/5.11.0/ServiceStack.cs
create mode 100644 csharp/ql/test/resources/stubs/ServiceStack/5.11.0/ServiceStack.csproj
create mode 100644 csharp/ql/test/resources/stubs/System.Drawing.Common/4.7.0/System.Drawing.Common.cs
create mode 100644 csharp/ql/test/resources/stubs/System.Drawing.Common/4.7.0/System.Drawing.Common.csproj
diff --git a/csharp/ql/test/library-tests/frameworks/ServiceStack/ServiceStack.cs b/csharp/ql/test/library-tests/frameworks/ServiceStack/ServiceStack.cs
new file mode 100644
index 00000000000..ead17ae4813
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/ServiceStack/ServiceStack.cs
@@ -0,0 +1,9 @@
+using System.Collections.Generic;
+using System.Linq;
+using ServiceStack;
+
+namespace ServiceStackTest
+{
+
+
+}
diff --git a/csharp/ql/test/library-tests/frameworks/ServiceStack/options b/csharp/ql/test/library-tests/frameworks/ServiceStack/options
new file mode 100644
index 00000000000..f2bcef4301f
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/ServiceStack/options
@@ -0,0 +1 @@
+semmle-extractor-options: /nostdlib /noconfig --load-sources-from-project:../../../resources/stubs/ServiceStack/5.11.0/ServiceStack.csproj
diff --git a/csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.expected b/csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.ql b/csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.ql
new file mode 100644
index 00000000000..8bea4542dba
--- /dev/null
+++ b/csharp/ql/test/library-tests/frameworks/ServiceStack/remoteFlowSource.ql
@@ -0,0 +1,7 @@
+import semmle.code.csharp.security.dataflow.flowsources.Remote
+
+from RemoteFlowSource source
+where
+ source.getLocation().getFile().fromSource() and
+ not source.getLocation().getFile().getAbsolutePath().matches("%/resources/stubs/%")
+select source, source.getSourceType()
diff --git a/csharp/ql/test/resources/stubs/ServiceStack.Client/5.11.0/ServiceStack.Client.cs b/csharp/ql/test/resources/stubs/ServiceStack.Client/5.11.0/ServiceStack.Client.cs
new file mode 100644
index 00000000000..2dc6766d47f
--- /dev/null
+++ b/csharp/ql/test/resources/stubs/ServiceStack.Client/5.11.0/ServiceStack.Client.cs
@@ -0,0 +1,1924 @@
+// This file contains auto-generated code.
+
+namespace ServiceStack
+{
+ // Generated from `ServiceStack.AdminCreateUser` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminCreateUser : ServiceStack.AdminUserBase, ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IPost
+ {
+ public AdminCreateUser() => throw null;
+ public System.Collections.Generic.List Permissions { get => throw null; set => throw null; }
+ public System.Collections.Generic.List Roles { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminDeleteUser` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminDeleteUser : ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IDelete
+ {
+ public AdminDeleteUser() => throw null;
+ public string Id { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminDeleteUserResponse` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminDeleteUserResponse : ServiceStack.IHasResponseStatus
+ {
+ public AdminDeleteUserResponse() => throw null;
+ public string Id { get => throw null; set => throw null; }
+ public ServiceStack.ResponseStatus ResponseStatus { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminGetUser` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminGetUser : ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IGet
+ {
+ public AdminGetUser() => throw null;
+ public string Id { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminQueryUsers` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminQueryUsers : ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IGet
+ {
+ public AdminQueryUsers() => throw null;
+ public string OrderBy { get => throw null; set => throw null; }
+ public string Query { get => throw null; set => throw null; }
+ public int? Skip { get => throw null; set => throw null; }
+ public int? Take { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminUpdateUser` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminUpdateUser : ServiceStack.AdminUserBase, ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IPut
+ {
+ public System.Collections.Generic.List AddPermissions { get => throw null; set => throw null; }
+ public System.Collections.Generic.List AddRoles { get => throw null; set => throw null; }
+ public AdminUpdateUser() => throw null;
+ public string Id { get => throw null; set => throw null; }
+ public bool? LockUser { get => throw null; set => throw null; }
+ public System.Collections.Generic.List RemovePermissions { get => throw null; set => throw null; }
+ public System.Collections.Generic.List RemoveRoles { get => throw null; set => throw null; }
+ public bool? UnlockUser { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminUserBase` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public abstract class AdminUserBase : ServiceStack.IMeta
+ {
+ protected AdminUserBase() => throw null;
+ public string DisplayName { get => throw null; set => throw null; }
+ public string Email { get => throw null; set => throw null; }
+ public string FirstName { get => throw null; set => throw null; }
+ public string LastName { get => throw null; set => throw null; }
+ public System.Collections.Generic.Dictionary Meta { get => throw null; set => throw null; }
+ public string Password { get => throw null; set => throw null; }
+ public string ProfileUrl { get => throw null; set => throw null; }
+ public System.Collections.Generic.Dictionary UserAuthProperties { get => throw null; set => throw null; }
+ public string UserName { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminUserResponse` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminUserResponse : ServiceStack.IHasResponseStatus
+ {
+ public AdminUserResponse() => throw null;
+ public string Id { get => throw null; set => throw null; }
+ public ServiceStack.ResponseStatus ResponseStatus { get => throw null; set => throw null; }
+ public System.Collections.Generic.Dictionary Result { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AdminUsersResponse` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AdminUsersResponse : ServiceStack.IHasResponseStatus
+ {
+ public AdminUsersResponse() => throw null;
+ public ServiceStack.ResponseStatus ResponseStatus { get => throw null; set => throw null; }
+ public System.Collections.Generic.List> Results { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AesUtils` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public static class AesUtils
+ {
+ public const int BlockSize = default;
+ public const int BlockSizeBytes = default;
+ public static void CreateCryptAuthKeysAndIv(out System.Byte[] cryptKey, out System.Byte[] authKey, out System.Byte[] iv) => throw null;
+ public static System.Byte[] CreateIv() => throw null;
+ public static System.Byte[] CreateKey() => throw null;
+ public static void CreateKeyAndIv(out System.Byte[] cryptKey, out System.Byte[] iv) => throw null;
+ public static System.Security.Cryptography.SymmetricAlgorithm CreateSymmetricAlgorithm() => throw null;
+ public static string Decrypt(string encryptedBase64, System.Byte[] cryptKey, System.Byte[] iv) => throw null;
+ public static System.Byte[] Decrypt(System.Byte[] encryptedBytes, System.Byte[] cryptKey, System.Byte[] iv) => throw null;
+ public static string Encrypt(string text, System.Byte[] cryptKey, System.Byte[] iv) => throw null;
+ public static System.Byte[] Encrypt(System.Byte[] bytesToEncrypt, System.Byte[] cryptKey, System.Byte[] iv) => throw null;
+ public const int KeySize = default;
+ public const int KeySizeBytes = default;
+ }
+
+ // Generated from `ServiceStack.AssignRoles` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AssignRoles : ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IPost, ServiceStack.IMeta
+ {
+ public AssignRoles() => throw null;
+ public System.Collections.Generic.Dictionary Meta { get => throw null; set => throw null; }
+ public System.Collections.Generic.List Permissions { get => throw null; set => throw null; }
+ public System.Collections.Generic.List Roles { get => throw null; set => throw null; }
+ public string UserName { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AssignRolesResponse` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AssignRolesResponse : ServiceStack.IMeta, ServiceStack.IHasResponseStatus
+ {
+ public System.Collections.Generic.List AllPermissions { get => throw null; set => throw null; }
+ public System.Collections.Generic.List AllRoles { get => throw null; set => throw null; }
+ public AssignRolesResponse() => throw null;
+ public System.Collections.Generic.Dictionary Meta { get => throw null; set => throw null; }
+ public ServiceStack.ResponseStatus ResponseStatus { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AsyncServiceClient` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AsyncServiceClient : ServiceStack.IHasVersion, ServiceStack.IHasSessionId, ServiceStack.IHasBearerToken
+ {
+ public bool AlwaysSendBasicAuthHeader { get => throw null; set => throw null; }
+ public AsyncServiceClient() => throw null;
+ public string BaseUri { get => throw null; set => throw null; }
+ public string BearerToken { get => throw null; set => throw null; }
+ public static int BufferSize;
+ public string ContentType { get => throw null; set => throw null; }
+ public System.Net.CookieContainer CookieContainer { get => throw null; set => throw null; }
+ public System.Net.ICredentials Credentials { get => throw null; set => throw null; }
+ public bool DisableAutoCompression { get => throw null; set => throw null; }
+ public static bool DisableTimer { get => throw null; set => throw null; }
+ public void Dispose() => throw null;
+ public bool EmulateHttpViaPost { get => throw null; set => throw null; }
+ public ServiceStack.ExceptionFilterDelegate ExceptionFilter { get => throw null; set => throw null; }
+ public System.Collections.Generic.Dictionary GetCookieValues() => throw null;
+ public static System.Action GlobalRequestFilter { get => throw null; set => throw null; }
+ public static System.Action GlobalResponseFilter { get => throw null; set => throw null; }
+ public System.Collections.Specialized.NameValueCollection Headers { get => throw null; set => throw null; }
+ public System.Action OnAuthenticationRequired { get => throw null; set => throw null; }
+ public ServiceStack.ProgressDelegate OnDownloadProgress { get => throw null; set => throw null; }
+ public ServiceStack.ProgressDelegate OnUploadProgress { get => throw null; set => throw null; }
+ public string Password { get => throw null; set => throw null; }
+ public System.Net.IWebProxy Proxy { get => throw null; set => throw null; }
+ public string RefreshToken { get => throw null; set => throw null; }
+ public string RefreshTokenUri { get => throw null; set => throw null; }
+ public string RequestCompressionType { get => throw null; set => throw null; }
+ public System.Action RequestFilter { get => throw null; set => throw null; }
+ public System.Action ResponseFilter { get => throw null; set => throw null; }
+ public ServiceStack.ResultsFilterDelegate ResultsFilter { get => throw null; set => throw null; }
+ public ServiceStack.ResultsFilterResponseDelegate ResultsFilterResponse { get => throw null; set => throw null; }
+ public System.Threading.Tasks.Task SendAsync(string httpMethod, string absoluteUrl, object request, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public string SessionId { get => throw null; set => throw null; }
+ public void SetCredentials(string userName, string password) => throw null;
+ public bool ShareCookiesWithBrowser { get => throw null; set => throw null; }
+ public bool StoreCookies { get => throw null; set => throw null; }
+ public ServiceStack.Web.StreamDeserializerDelegate StreamDeserializer { get => throw null; set => throw null; }
+ public ServiceStack.Web.StreamSerializerDelegate StreamSerializer { get => throw null; set => throw null; }
+ public System.TimeSpan? Timeout { get => throw null; set => throw null; }
+ public bool UseTokenCookie { get => throw null; set => throw null; }
+ public string UserAgent { get => throw null; set => throw null; }
+ public string UserName { get => throw null; set => throw null; }
+ public int Version { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AsyncTimer` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AsyncTimer : System.IDisposable, ServiceStack.ITimer
+ {
+ public AsyncTimer(System.Threading.Timer timer) => throw null;
+ public void Cancel() => throw null;
+ public void Dispose() => throw null;
+ public System.Threading.Timer Timer;
+ }
+
+ // Generated from `ServiceStack.Authenticate` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class Authenticate : ServiceStack.IVerb, ServiceStack.IReturn, ServiceStack.IReturn, ServiceStack.IPost, ServiceStack.IMeta
+ {
+ public string AccessToken { get => throw null; set => throw null; }
+ public string AccessTokenSecret { get => throw null; set => throw null; }
+ public Authenticate() => throw null;
+ public string ErrorView { get => throw null; set => throw null; }
+ public System.Collections.Generic.Dictionary Meta { get => throw null; set => throw null; }
+ public string Password { get => throw null; set => throw null; }
+ public bool? RememberMe { get => throw null; set => throw null; }
+ public string State { get => throw null; set => throw null; }
+ public bool? UseTokenCookie { get => throw null; set => throw null; }
+ public string UserName { get => throw null; set => throw null; }
+ public string cnonce { get => throw null; set => throw null; }
+ public string nc { get => throw null; set => throw null; }
+ public string nonce { get => throw null; set => throw null; }
+ public string oauth_token { get => throw null; set => throw null; }
+ public string oauth_verifier { get => throw null; set => throw null; }
+ public string provider { get => throw null; set => throw null; }
+ public string qop { get => throw null; set => throw null; }
+ public string response { get => throw null; set => throw null; }
+ public string scope { get => throw null; set => throw null; }
+ public string uri { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AuthenticateResponse` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AuthenticateResponse : ServiceStack.IMeta, ServiceStack.IHasSessionId, ServiceStack.IHasBearerToken
+ {
+ public AuthenticateResponse() => throw null;
+ public string BearerToken { get => throw null; set => throw null; }
+ public string DisplayName { get => throw null; set => throw null; }
+ public System.Collections.Generic.Dictionary Meta { get => throw null; set => throw null; }
+ public System.Collections.Generic.List Permissions { get => throw null; set => throw null; }
+ public string ProfileUrl { get => throw null; set => throw null; }
+ public string ReferrerUrl { get => throw null; set => throw null; }
+ public string RefreshToken { get => throw null; set => throw null; }
+ public ServiceStack.ResponseStatus ResponseStatus { get => throw null; set => throw null; }
+ public System.Collections.Generic.List Roles { get => throw null; set => throw null; }
+ public string SessionId { get => throw null; set => throw null; }
+ public string UserId { get => throw null; set => throw null; }
+ public string UserName { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.AuthenticationException` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AuthenticationException : System.Exception, ServiceStack.IHasStatusCode
+ {
+ public AuthenticationException(string message, System.Exception innerException) => throw null;
+ public AuthenticationException(string message) => throw null;
+ public AuthenticationException() => throw null;
+ public int StatusCode { get => throw null; }
+ }
+
+ // Generated from `ServiceStack.AuthenticationInfo` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class AuthenticationInfo
+ {
+ public AuthenticationInfo(string authHeader) => throw null;
+ public override string ToString() => throw null;
+ public string cnonce { get => throw null; set => throw null; }
+ public string method { get => throw null; set => throw null; }
+ public int nc { get => throw null; set => throw null; }
+ public string nonce { get => throw null; set => throw null; }
+ public string opaque { get => throw null; set => throw null; }
+ public string qop { get => throw null; set => throw null; }
+ public string realm { get => throw null; set => throw null; }
+ }
+
+ // Generated from `ServiceStack.CachedServiceClient` in `ServiceStack.Client, Version=5.0.0.0, Culture=neutral, PublicKeyToken=null`
+ public class CachedServiceClient : System.IDisposable, ServiceStack.IServiceGatewayAsync, ServiceStack.IServiceGateway, ServiceStack.IServiceClientSync, ServiceStack.IServiceClientCommon, ServiceStack.IServiceClientAsync, ServiceStack.IServiceClient, ServiceStack.IRestServiceClient, ServiceStack.IRestClientSync, ServiceStack.IRestClientAsync, ServiceStack.IRestClient, ServiceStack.IReplyClient, ServiceStack.IOneWayClient, ServiceStack.IHttpRestClientAsync, ServiceStack.IHasVersion, ServiceStack.IHasSessionId, ServiceStack.IHasBearerToken, ServiceStack.ICachedServiceClient
+ {
+ public void AddHeader(string name, string value) => throw null;
+ public string BearerToken { get => throw null; set => throw null; }
+ public int CacheCount { get => throw null; }
+ public System.Int64 CacheHits { get => throw null; }
+ public CachedServiceClient(ServiceStack.ServiceClientBase client, System.Collections.Concurrent.ConcurrentDictionary cache) => throw null;
+ public CachedServiceClient(ServiceStack.ServiceClientBase client) => throw null;
+ public System.Int64 CachesAdded { get => throw null; }
+ public System.Int64 CachesRemoved { get => throw null; }
+ public int CleanCachesWhenCountExceeds { get => throw null; set => throw null; }
+ public System.TimeSpan? ClearCachesOlderThan { get => throw null; set => throw null; }
+ public void ClearCookies() => throw null;
+ public System.TimeSpan? ClearExpiredCachesOlderThan { get => throw null; set => throw null; }
+ public void CustomMethod(string httpVerb, ServiceStack.IReturnVoid requestDto) => throw null;
+ public TResponse CustomMethod(string httpVerb, object requestDto) => throw null;
+ public TResponse CustomMethod(string httpVerb, ServiceStack.IReturn requestDto) => throw null;
+ public System.Threading.Tasks.Task CustomMethodAsync(string httpVerb, string relativeOrAbsoluteUrl, object request, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task CustomMethodAsync(string httpVerb, object requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task CustomMethodAsync(string httpVerb, ServiceStack.IReturn requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task CustomMethodAsync(string httpVerb, ServiceStack.IReturnVoid requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public void Delete(ServiceStack.IReturnVoid requestDto) => throw null;
+ public TResponse Delete(string relativeOrAbsoluteUrl) => throw null;
+ public TResponse Delete(object request) => throw null;
+ public TResponse Delete(ServiceStack.IReturn request) => throw null;
+ public System.Threading.Tasks.Task DeleteAsync(string relativeOrAbsoluteUrl, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task DeleteAsync(object requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task DeleteAsync(ServiceStack.IReturn requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task DeleteAsync(ServiceStack.IReturnVoid requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public void Dispose() => throw null;
+ public System.Int64 ErrorFallbackHits { get => throw null; }
+ public void Get(ServiceStack.IReturnVoid request) => throw null;
+ public TResponse Get(string relativeOrAbsoluteUrl) => throw null;
+ public TResponse Get(object requestDto) => throw null;
+ public TResponse Get(ServiceStack.IReturn requestDto) => throw null;
+ public System.Threading.Tasks.Task GetAsync(string relativeOrAbsoluteUrl, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task GetAsync(object requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task GetAsync(ServiceStack.IReturn requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task GetAsync(ServiceStack.IReturnVoid requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Collections.Generic.Dictionary GetCookieValues() => throw null;
+ public System.Collections.Generic.IEnumerable GetLazy(ServiceStack.IReturn> queryDto) => throw null;
+ public System.Int64 NotModifiedHits { get => throw null; }
+ public object OnExceptionFilter(System.Net.WebException webEx, System.Net.WebResponse webRes, string requestUri, System.Type responseType) => throw null;
+ public void Patch(ServiceStack.IReturnVoid requestDto) => throw null;
+ public TResponse Patch(string relativeOrAbsoluteUrl, object requestDto) => throw null;
+ public TResponse Patch(object requestDto) => throw null;
+ public TResponse Patch(ServiceStack.IReturn requestDto) => throw null;
+ public System.Threading.Tasks.Task PatchAsync(object requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task PatchAsync(ServiceStack.IReturn requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task PatchAsync(ServiceStack.IReturnVoid requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public void Post(ServiceStack.IReturnVoid requestDto) => throw null;
+ public TResponse Post(string relativeOrAbsoluteUrl, object request) => throw null;
+ public TResponse Post(object requestDto) => throw null;
+ public TResponse Post(ServiceStack.IReturn requestDto) => throw null;
+ public System.Threading.Tasks.Task PostAsync(string relativeOrAbsoluteUrl, object request, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task PostAsync(object requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task PostAsync(ServiceStack.IReturn requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public System.Threading.Tasks.Task PostAsync(ServiceStack.IReturnVoid requestDto, System.Threading.CancellationToken token = default(System.Threading.CancellationToken)) => throw null;
+ public TResponse PostFile(string relativeOrAbsoluteUrl, System.IO.Stream fileToUpload, string fileName, string mimeType) => throw null;
+ public TResponse PostFileWithRequest(string relativeOrAbsoluteUrl, System.IO.Stream fileToUpload, string fileName, object request, string fieldName = default(string)) => throw null;
+ public TResponse PostFileWithRequest(System.IO.Stream fileToUpload, string fileName, object request, string fieldName = default(string)) => throw null;
+ public TResponse PostFilesWithRequest(string relativeOrAbsoluteUrl, object request, System.Collections.Generic.IEnumerable files) => throw null;
+ public TResponse PostFilesWithRequest(object request, System.Collections.Generic.IEnumerable files) => throw null;
+ public void Publish(object requestDto) => throw null;
+ public void PublishAll(System.Collections.Generic.IEnumerable