From 96d11b79666a8fbf253255cfe0ce436b5206a141 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Mon, 7 Dec 2020 11:22:01 -0800
Subject: [PATCH 001/741] Create ServiceStack.qll
---
cpp/ql/src/Microsoft/ServiceStack.qll | 86 +++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
create mode 100644 cpp/ql/src/Microsoft/ServiceStack.qll
diff --git a/cpp/ql/src/Microsoft/ServiceStack.qll b/cpp/ql/src/Microsoft/ServiceStack.qll
new file mode 100644
index 00000000000..13b43be07f8
--- /dev/null
+++ b/cpp/ql/src/Microsoft/ServiceStack.qll
@@ -0,0 +1,86 @@
+/**
+ * Provides a taint-tracking configuration for reasoning about untrusted user input when using Service Stack framework
+ */
+import csharp
+
+module ServiceStackSQL {
+ import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
+
+ string getSourceType(DataFlow::Node node) {
+ result = node.(RemoteFlowSource).getSourceType()
+ or
+ result = node.(LocalFlowSource).getSourceType()
+ }
+
+ predicate serviceStackRequests(Method m) {
+ exists(ValueOrRefType type, ServiceStackClass ssc |
+ type.fromSource() and
+ m = type.getAMethod() and
+ m = ssc.getAMember().getDeclaringType().getAMethod() and
+
+ // verify other verb methods in service stack docs
+ m.getName() = ["Post","Get", "Put", "Delete","Any", "Option", "Head"]
+ // not reccommended match approach below
+ //.toLowerCase().regexpMatch("(Post|Get|Put|Delete)")
+
+ )
+ }
+
+ class ServiceStackClass extends Class {
+ ServiceStackClass() { this.getBaseClass+().getName()="Service" }
+ }
+
+ class ServiceStackRequestClass extends Class {
+ ServiceStackRequestClass() {
+ // Classes directly used in as param to request method
+ exists(Method m |
+ serviceStackRequests(m) and
+ this = m.getAParameter().getType()) or
+ // Classes of a property or field on another request class
+ exists(ServiceStackRequestClass outer |
+ this = outer.getAProperty().getType() or
+ this = outer.getAField().getType())
+ }
+ }
+
+ class ServiceClassSources extends RemoteFlowSource {
+ ServiceClassSources() {
+ exists(Method m |
+ serviceStackRequests(m) and
+ // keep this for primitive typed request params (string/int)
+ this.asParameter() = m.getAParameter()) or
+ exists(ServiceStackRequestClass reqClass |
+ // look for field reads on request classes
+ reqClass.getAProperty().getAnAccess() = this.asExpr() or
+ reqClass.getAField().getAnAccess() = this.asExpr())
+ }
+
+ override string getSourceType() {
+ result = "ServiceStackSources"
+ }
+ }
+
+ class SqlSinks extends Sink {
+ SqlSinks() { this.asExpr() instanceof SqlInjectionExpr }
+ }
+
+ class SqlStringMethod extends Method {
+ SqlStringMethod() {
+ this.getName() in [
+ "Custom", "CustomSelect", "CustomInsert", "CustomUpdate",
+ "SqlScalar", "SqlList", "SqlColumn", "ColumnDistinct",
+ "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable",
+ "ExecuteSql", "ExecuteSqlAsync",
+ "UnsafeSelect", "UnsafeFrom", "UnsafeWhere", "UnsafeAnd", "UnsafeOr"
+ ]
+ }
+ }
+
+ class SqlInjectionExpr extends Expr {
+ SqlInjectionExpr() {
+ exists(MethodCall mc |
+ mc.getTarget() instanceof SqlStringMethod and mc.getArgument(1) = this
+ )
+ }
+ }
+}
From 188dbde2d6dc5c7fe3a59bc2740c85a856c18ca6 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Mon, 7 Dec 2020 11:29:59 -0800
Subject: [PATCH 002/741] Create SQLInjection.ql
---
.../ServiceStack/SQL-Injection/SQLInjection.ql | 15 +++++++++++++++
1 file changed, 15 insertions(+)
create mode 100644 csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
new file mode 100644
index 00000000000..a046c132249
--- /dev/null
+++ b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
@@ -0,0 +1,15 @@
+/**
+ * @id test-id
+ * @name Test
+ * @description Test description
+ * @tags test
+ */
+
+import csharp
+import semmle.code.csharp.frameworks.microsoft.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()))
+
From dbe017024910f5b5e7c3c5659afffd1b5a476b91 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Mon, 7 Dec 2020 11:30:20 -0800
Subject: [PATCH 003/741] Add files via upload
---
.../SQL-Injection/SQLInjection.cs | 61 +++++++++++++++++++
.../SQL-Injection/SQLInjection.qhelp | 35 +++++++++++
2 files changed, 96 insertions(+)
create mode 100644 csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs
create mode 100644 csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs
new file mode 100644
index 00000000000..e41cdb0ff7f
--- /dev/null
+++ b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs
@@ -0,0 +1,61 @@
+using ServiceStack;
+using ServiceStack.OrmLite;
+using SqlServer.ServiceModel;
+using SqlServer.ServiceModel.Types;
+
+namespace SqlServer.ServiceInterface
+{
+ public class CustomerService : Service
+ {
+ public GetCustomersResponse Get(GetCustomers request)
+ {
+ return new GetCustomersResponse { Results = Db.Select() };
+ }
+
+ public GetCustomerResponse Get(GetCustomer request)
+ {
+ //var customer = Db.SingleById(request.Id);
+ var customer = Db.SqlScalar("SELECT Id FROM Customer WHERE Id = " + request.Id + ";");
+ if (customer == null)
+ throw HttpError.NotFound("Customer not found");
+
+ return new GetCustomerResponse
+ {
+ Result = Db.SingleById(request.Id)
+ };
+ }
+
+ public CreateCustomerResponse Post(CreateCustomer request)
+ {
+ var customer = new Customer { Name = request.Name };
+ //Db.Save(customer);
+ Db.ExecuteSql("INSERT INTO Customer (Name) VALUES ('" + customer.Name + "')");
+ return new CreateCustomerResponse
+ {
+ Result = customer
+ };
+ }
+
+ public UpdateCustomerResponse Put(UpdateCustomer request)
+ {
+ var customer = Db.SingleById(request.Id);
+ if (customer == null)
+ throw HttpError.NotFound("Customer '{0}' does not exist".Fmt(request.Id));
+
+ customer.Name = request.Name;
+ //Db.Update(customer);
+ Db.ExecuteSqlAsync("UPDATE Customer SET Name = '" + customer.Name + "' WHERE Id = " + request.Id);
+ return new UpdateCustomerResponse
+ {
+ Result = customer
+ };
+ }
+
+ public void Delete(DeleteCustomer request)
+ {
+ //Db.DeleteById(request.Id);
+ string q = @"DELETE FROM Customer WHERE Id = " + request.Id;
+ Db.ExecuteSql(q);
+ }
+ }
+}
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp
new file mode 100644
index 00000000000..3b51f6a6d34
--- /dev/null
+++ b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp
@@ -0,0 +1,35 @@
+
+
+
+
+If unsanitized user input is.....
+
+
+
+
+
+
+You can do better....
+
+
+If using some method, you should always pass...
+
+
+Something must be set to this thing
+
+
+
+
+
+
+In the following example....
+
+
+
+
+
+Services Stack: Documentation.
+
+
From cae6f91729b7a13874db9fb8edbed35404e6de02 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Mon, 7 Dec 2020 11:31:38 -0800
Subject: [PATCH 004/741] Create ServiceStack.qll
---
.../frameworks/microsoft/ServiceStack.qll | 86 +++++++++++++++++++
1 file changed, 86 insertions(+)
create mode 100644 csharp/ql/src/semmle/code/csharp/frameworks/microsoft/ServiceStack.qll
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/ServiceStack.qll
new file mode 100644
index 00000000000..13b43be07f8
--- /dev/null
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/ServiceStack.qll
@@ -0,0 +1,86 @@
+/**
+ * Provides a taint-tracking configuration for reasoning about untrusted user input when using Service Stack framework
+ */
+import csharp
+
+module ServiceStackSQL {
+ import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
+
+ string getSourceType(DataFlow::Node node) {
+ result = node.(RemoteFlowSource).getSourceType()
+ or
+ result = node.(LocalFlowSource).getSourceType()
+ }
+
+ predicate serviceStackRequests(Method m) {
+ exists(ValueOrRefType type, ServiceStackClass ssc |
+ type.fromSource() and
+ m = type.getAMethod() and
+ m = ssc.getAMember().getDeclaringType().getAMethod() and
+
+ // verify other verb methods in service stack docs
+ m.getName() = ["Post","Get", "Put", "Delete","Any", "Option", "Head"]
+ // not reccommended match approach below
+ //.toLowerCase().regexpMatch("(Post|Get|Put|Delete)")
+
+ )
+ }
+
+ class ServiceStackClass extends Class {
+ ServiceStackClass() { this.getBaseClass+().getName()="Service" }
+ }
+
+ class ServiceStackRequestClass extends Class {
+ ServiceStackRequestClass() {
+ // Classes directly used in as param to request method
+ exists(Method m |
+ serviceStackRequests(m) and
+ this = m.getAParameter().getType()) or
+ // Classes of a property or field on another request class
+ exists(ServiceStackRequestClass outer |
+ this = outer.getAProperty().getType() or
+ this = outer.getAField().getType())
+ }
+ }
+
+ class ServiceClassSources extends RemoteFlowSource {
+ ServiceClassSources() {
+ exists(Method m |
+ serviceStackRequests(m) and
+ // keep this for primitive typed request params (string/int)
+ this.asParameter() = m.getAParameter()) or
+ exists(ServiceStackRequestClass reqClass |
+ // look for field reads on request classes
+ reqClass.getAProperty().getAnAccess() = this.asExpr() or
+ reqClass.getAField().getAnAccess() = this.asExpr())
+ }
+
+ override string getSourceType() {
+ result = "ServiceStackSources"
+ }
+ }
+
+ class SqlSinks extends Sink {
+ SqlSinks() { this.asExpr() instanceof SqlInjectionExpr }
+ }
+
+ class SqlStringMethod extends Method {
+ SqlStringMethod() {
+ this.getName() in [
+ "Custom", "CustomSelect", "CustomInsert", "CustomUpdate",
+ "SqlScalar", "SqlList", "SqlColumn", "ColumnDistinct",
+ "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable",
+ "ExecuteSql", "ExecuteSqlAsync",
+ "UnsafeSelect", "UnsafeFrom", "UnsafeWhere", "UnsafeAnd", "UnsafeOr"
+ ]
+ }
+ }
+
+ class SqlInjectionExpr extends Expr {
+ SqlInjectionExpr() {
+ exists(MethodCall mc |
+ mc.getTarget() instanceof SqlStringMethod and mc.getArgument(1) = this
+ )
+ }
+ }
+}
From a2615339f7788a58c58394e310c9578e65507f1f Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Mon, 7 Dec 2020 11:32:28 -0800
Subject: [PATCH 005/741] Delete ServiceStack.qll
---
cpp/ql/src/Microsoft/ServiceStack.qll | 86 ---------------------------
1 file changed, 86 deletions(-)
delete mode 100644 cpp/ql/src/Microsoft/ServiceStack.qll
diff --git a/cpp/ql/src/Microsoft/ServiceStack.qll b/cpp/ql/src/Microsoft/ServiceStack.qll
deleted file mode 100644
index 13b43be07f8..00000000000
--- a/cpp/ql/src/Microsoft/ServiceStack.qll
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Provides a taint-tracking configuration for reasoning about untrusted user input when using Service Stack framework
- */
-import csharp
-
-module ServiceStackSQL {
- import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
-
- string getSourceType(DataFlow::Node node) {
- result = node.(RemoteFlowSource).getSourceType()
- or
- result = node.(LocalFlowSource).getSourceType()
- }
-
- predicate serviceStackRequests(Method m) {
- exists(ValueOrRefType type, ServiceStackClass ssc |
- type.fromSource() and
- m = type.getAMethod() and
- m = ssc.getAMember().getDeclaringType().getAMethod() and
-
- // verify other verb methods in service stack docs
- m.getName() = ["Post","Get", "Put", "Delete","Any", "Option", "Head"]
- // not reccommended match approach below
- //.toLowerCase().regexpMatch("(Post|Get|Put|Delete)")
-
- )
- }
-
- class ServiceStackClass extends Class {
- ServiceStackClass() { this.getBaseClass+().getName()="Service" }
- }
-
- class ServiceStackRequestClass extends Class {
- ServiceStackRequestClass() {
- // Classes directly used in as param to request method
- exists(Method m |
- serviceStackRequests(m) and
- this = m.getAParameter().getType()) or
- // Classes of a property or field on another request class
- exists(ServiceStackRequestClass outer |
- this = outer.getAProperty().getType() or
- this = outer.getAField().getType())
- }
- }
-
- class ServiceClassSources extends RemoteFlowSource {
- ServiceClassSources() {
- exists(Method m |
- serviceStackRequests(m) and
- // keep this for primitive typed request params (string/int)
- this.asParameter() = m.getAParameter()) or
- exists(ServiceStackRequestClass reqClass |
- // look for field reads on request classes
- reqClass.getAProperty().getAnAccess() = this.asExpr() or
- reqClass.getAField().getAnAccess() = this.asExpr())
- }
-
- override string getSourceType() {
- result = "ServiceStackSources"
- }
- }
-
- class SqlSinks extends Sink {
- SqlSinks() { this.asExpr() instanceof SqlInjectionExpr }
- }
-
- class SqlStringMethod extends Method {
- SqlStringMethod() {
- this.getName() in [
- "Custom", "CustomSelect", "CustomInsert", "CustomUpdate",
- "SqlScalar", "SqlList", "SqlColumn", "ColumnDistinct",
- "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable",
- "ExecuteSql", "ExecuteSqlAsync",
- "UnsafeSelect", "UnsafeFrom", "UnsafeWhere", "UnsafeAnd", "UnsafeOr"
- ]
- }
- }
-
- class SqlInjectionExpr extends Expr {
- SqlInjectionExpr() {
- exists(MethodCall mc |
- mc.getTarget() instanceof SqlStringMethod and mc.getArgument(1) = this
- )
- }
- }
-}
From 386eb2d56b4780e2ba573015ca3aa46ad3f83d43 Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Tue, 15 Dec 2020 11:06:21 -0800
Subject: [PATCH 006/741] move ServiceStack out of microsoft
---
.../src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql | 2 +-
.../code/csharp/frameworks/{microsoft => }/ServiceStack.qll | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename csharp/ql/src/semmle/code/csharp/frameworks/{microsoft => }/ServiceStack.qll (100%)
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
index a046c132249..2d78074db7a 100644
--- a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
+++ b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
@@ -6,7 +6,7 @@
*/
import csharp
-import semmle.code.csharp.frameworks.microsoft.ServiceStack::ServiceStackSQL
+import semmle.code.csharp.frameworks.ServiceStack::ServiceStackSQL
from TaintTrackingConfiguration c, DataFlow::PathNode source, DataFlow::PathNode sink
where c.hasFlowPath(source, sink)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/microsoft/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
similarity index 100%
rename from csharp/ql/src/semmle/code/csharp/frameworks/microsoft/ServiceStack.qll
rename to csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
From d408ae7e10719d8e9cf9e31b152a4369ceb32c64 Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Tue, 15 Dec 2020 11:29:22 -0800
Subject: [PATCH 007/741] Split ServiceStack into modules and incorporate into
main lib
---
.../code/csharp/frameworks/ServiceStack.qll | 67 ++++++++++---------
.../src/semmle/code/csharp/frameworks/Sql.qll | 1 +
.../code/csharp/security/dataflow/XSS.qll | 1 +
.../security/dataflow/flowsources/Remote.qll | 1 +
4 files changed, 40 insertions(+), 30 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 13b43be07f8..f878366d4f0 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -1,35 +1,11 @@
-/**
- * Provides a taint-tracking configuration for reasoning about untrusted user input when using Service Stack framework
- */
import csharp
-module ServiceStackSQL {
- import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
-
- string getSourceType(DataFlow::Node node) {
- result = node.(RemoteFlowSource).getSourceType()
- or
- result = node.(LocalFlowSource).getSourceType()
+/** Provides definitions related to the namespace `ServiceStack`. */
+module ServiceStack {
+ class ServiceClass extends Class {
+ ServiceClass() { this.getBaseClass+().getName()="Service" }
}
-
- predicate serviceStackRequests(Method m) {
- exists(ValueOrRefType type, ServiceStackClass ssc |
- type.fromSource() and
- m = type.getAMethod() and
- m = ssc.getAMember().getDeclaringType().getAMethod() and
-
- // verify other verb methods in service stack docs
- m.getName() = ["Post","Get", "Put", "Delete","Any", "Option", "Head"]
- // not reccommended match approach below
- //.toLowerCase().regexpMatch("(Post|Get|Put|Delete)")
-
- )
- }
-
- class ServiceStackClass extends Class {
- ServiceStackClass() { this.getBaseClass+().getName()="Service" }
- }
-
+
class ServiceStackRequestClass extends Class {
ServiceStackRequestClass() {
// Classes directly used in as param to request method
@@ -42,7 +18,27 @@ module ServiceStackSQL {
this = outer.getAField().getType())
}
}
-
+
+ predicate serviceStackRequests(Method m) {
+ exists(ValueOrRefType type, ServiceClass ssc |
+ type.fromSource() and
+ m = type.getAMethod() and
+ m = ssc.getAMember().getDeclaringType().getAMethod() and
+
+ // verify other verb methods in service stack docs
+ m.getName() = ["Post","Get", "Put", "Delete","Any", "Option", "Head"]
+ // not reccommended match approach below
+ //.toLowerCase().regexpMatch("(Post|Get|Put|Delete)")
+
+ )
+ }
+}
+
+/** Flow sources for the ServiceStack framework */
+module Sources {
+ private import ServiceStack::ServiceStack
+ private import semmle.code.csharp.security.dataflow.flowsources.Remote
+
class ServiceClassSources extends RemoteFlowSource {
ServiceClassSources() {
exists(Method m |
@@ -59,6 +55,12 @@ module ServiceStackSQL {
result = "ServiceStackSources"
}
}
+}
+
+/** SQL sinks for the ServiceStack framework */
+module SQL {
+ private import ServiceStack::ServiceStack
+ private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
class SqlSinks extends Sink {
SqlSinks() { this.asExpr() instanceof SqlInjectionExpr }
@@ -84,3 +86,8 @@ module ServiceStackSQL {
}
}
}
+
+/** XSS sinks for the ServiceStack framework */
+module XSS {
+ // TODO
+}
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/Sql.qll b/csharp/ql/src/semmle/code/csharp/frameworks/Sql.qll
index b2b1eeef809..647fdfe3a54 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/Sql.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/Sql.qll
@@ -5,6 +5,7 @@ private import semmle.code.csharp.frameworks.system.Data
private import semmle.code.csharp.frameworks.system.data.SqlClient
private import semmle.code.csharp.frameworks.EntityFramework
private import semmle.code.csharp.frameworks.NHibernate
+private import semmle.code.csharp.frameworks.ServiceStack::SQL
/** An expression containing a SQL command. */
abstract class SqlExpr extends Expr {
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll
index 763fb46a4f1..6d5d8a336c1 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/XSS.qll
@@ -10,6 +10,7 @@ module XSS {
import semmle.code.csharp.frameworks.system.Net
import semmle.code.csharp.frameworks.system.Web
import semmle.code.csharp.frameworks.system.web.UI
+ import semmle.code.csharp.frameworks.ServiceStack::XSS
import semmle.code.csharp.security.Sanitizers
import semmle.code.csharp.security.dataflow.flowsinks.Html
import semmle.code.csharp.security.dataflow.flowsinks.Remote
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Remote.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
index f8240583108..8ec6eed5ad0 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/flowsources/Remote.qll
@@ -12,6 +12,7 @@ private import semmle.code.csharp.frameworks.system.web.ui.WebControls
private import semmle.code.csharp.frameworks.WCF
private import semmle.code.csharp.frameworks.microsoft.Owin
private import semmle.code.csharp.frameworks.microsoft.AspNetCore
+import semmle.code.csharp.frameworks.ServiceStack::Sources
/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends DataFlow::Node {
From 71a08c3237df28fb4590919191fc6240bdf7ca19 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Tue, 15 Dec 2020 14:08:34 -0800
Subject: [PATCH 008/741] Update servicestack lib
---
.../code/csharp/frameworks/ServiceStack.qll | 27 ++++++++++++++++++-
1 file changed, 26 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 f878366d4f0..cc9c7333c5d 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -89,5 +89,30 @@ module SQL {
/** XSS sinks for the ServiceStack framework */
module XSS {
- // TODO
+ private import ServiceStack::ServiceStack
+ private import semmle.code.csharp.security.dataflow.flowsources.Remote
+
+ class XssSinks extends RemoteFlowSource {
+ XssSinks() {
+ exists( Method m |
+ ((
+ this.asParameter() = m.getAParameter() and
+ serviceStackRequests(m)
+ ) or
+ // if object is tainted then the rest follows as well
+ ( serviceStackRequests(m) and
+ m.getAParameter().getType().(RefType).getAProperty().getAnAccess() = this.asExpr()
+ ))
+ // along with finding the right methods, find ones that return strings/object which are strings
+ and (
+ m.getReturnType() instanceof ObjectType or
+ m.getReturnType() instanceof StringType
+ )
+ )
+ }
+
+ override string getSourceType() {
+ result = "XssSinks"
+ }
+ }
}
From 5c7dedffb339a08d54adfe8f9f08a9d3938c5d93 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Tue, 15 Dec 2020 23:09:40 -0800
Subject: [PATCH 009/741] Update sinks
---
.../code/csharp/frameworks/ServiceStack.qll | 44 +++++++++----------
1 file changed, 22 insertions(+), 22 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index cc9c7333c5d..eb7300a6741 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -90,29 +90,29 @@ module SQL {
/** XSS sinks for the ServiceStack framework */
module XSS {
private import ServiceStack::ServiceStack
- private import semmle.code.csharp.security.dataflow.flowsources.Remote
+ private import semmle.code.csharp.security.dataflow.XSS::XSS
- class XssSinks extends RemoteFlowSource {
- XssSinks() {
- exists( Method m |
- ((
- this.asParameter() = m.getAParameter() and
- serviceStackRequests(m)
- ) or
- // if object is tainted then the rest follows as well
- ( serviceStackRequests(m) and
- m.getAParameter().getType().(RefType).getAProperty().getAnAccess() = this.asExpr()
- ))
- // along with finding the right methods, find ones that return strings/object which are strings
- and (
- m.getReturnType() instanceof ObjectType or
- m.getReturnType() instanceof StringType
- )
- )
- }
-
- override string getSourceType() {
- result = "XssSinks"
+ class XssSinks extends Sink {
+ XssSinks() { this.asExpr() instanceof XssExpr }
+ }
+
+ class XssExpr extends Expr {
+ XssExpr() {
+ exists(ReturnStmt r, Method m |
+ this = r.getExpr() and
+ (
+ (
+ r.getExpr() instanceof ObjectCreation
+ and r.getExpr().getType().hasName("HttpResult")
+ //TODO check if we have a valid content type
+ //TODO write another check for the decorated version
+ )
+ or
+ (
+ r.getExpr().getType().hasName("String")
+ )
+ )
+ )
}
}
}
From 12e8107492530dd3829a64b728c43b514173ba92 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Tue, 15 Dec 2020 23:11:00 -0800
Subject: [PATCH 010/741] Add example
---
.../src/experimental/ServiceStack/XSS/XSS.cs | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs
diff --git a/csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs b/csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs
new file mode 100644
index 00000000000..5bb53450a9e
--- /dev/null
+++ b/csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs
@@ -0,0 +1,37 @@
+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 3c493511e988d97b0872cc9ad8a05f8be62cd42d Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Tue, 15 Dec 2020 23:13:46 -0800
Subject: [PATCH 011/741] Update file
---
csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index eb7300a6741..99812dc9942 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -104,8 +104,7 @@ module XSS {
(
r.getExpr() instanceof ObjectCreation
and r.getExpr().getType().hasName("HttpResult")
- //TODO check if we have a valid content type
- //TODO write another check for the decorated version
+ //TODO write a content type check for this decorated version
)
or
(
From ba46eaa1438a2717a7d66fb7f919118da1fd9faf Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Wed, 16 Dec 2020 10:50:14 -0800
Subject: [PATCH 012/741] Refactor sink
---
.../semmle/code/csharp/frameworks/ServiceStack.qll | 11 +++++------
1 file changed, 5 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 99812dc9942..0c80bf197ed 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -98,18 +98,17 @@ module XSS {
class XssExpr extends Expr {
XssExpr() {
- exists(ReturnStmt r, Method m |
- this = r.getExpr() and
+ exists(ReturnStmt r |
+ this = r.getExpr() and
(
(
- r.getExpr() instanceof ObjectCreation
- and r.getExpr().getType().hasName("HttpResult")
+ r.getExpr().(ObjectCreation).getType().hasName("HttpResult")
//TODO write a content type check for this decorated version
)
or
(
- r.getExpr().getType().hasName("String")
- )
+ r.getExpr().getType() instanceof StringType
+ )
)
)
}
From 4e0f3a30eef3cde996cf6672200d3cbace8d8375 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Wed, 16 Dec 2020 14:10:08 -0800
Subject: [PATCH 013/741] Update sink based on feedback
---
.../code/csharp/frameworks/ServiceStack.qll | 21 ++++++++++---------
1 file changed, 11 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 0c80bf197ed..f72ddd0fd42 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -98,18 +98,19 @@ module XSS {
class XssExpr extends Expr {
XssExpr() {
- exists(ReturnStmt r |
- this = r.getExpr() and
+ exists(ReturnStmt r, Expr arg |
(
- (
- r.getExpr().(ObjectCreation).getType().hasName("HttpResult")
- //TODO write a content type check for this decorated version
- )
- or
- (
- r.getExpr().getType() instanceof StringType
- )
+ r.getExpr().(ObjectCreation).getType().hasName("HttpResult") and
+ r.getExpr().(ObjectCreation).getAnArgument().getType() instanceof StringType and
+ arg = r.getExpr().(ObjectCreation).getAnArgument()
)
+ or
+ (
+ r.getExpr().getType() instanceof StringType and
+ arg = r.getExpr()
+ )
+ |
+ this = arg
)
}
}
From 0a7e4b6840289c09cf55a5360d1d05b93abd2626 Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Wed, 16 Dec 2020 17:04:08 -0800
Subject: [PATCH 014/741] Update sink based on feedback
---
.../semmle/code/csharp/frameworks/ServiceStack.qll | 12 ++++++------
1 file changed, 6 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 f72ddd0fd42..938ffd2ed46 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -99,18 +99,18 @@ module XSS {
class XssExpr extends Expr {
XssExpr() {
exists(ReturnStmt r, Expr arg |
- (
- r.getExpr().(ObjectCreation).getType().hasName("HttpResult") and
- r.getExpr().(ObjectCreation).getAnArgument().getType() instanceof StringType and
- arg = r.getExpr().(ObjectCreation).getAnArgument()
- )
- or
(
r.getExpr().getType() instanceof StringType and
arg = r.getExpr()
)
|
this = arg
+ ) or
+ exists(ObjectCreation oc |
+ oc.getType().hasName("HttpResult") and
+ oc.getAnArgument().getType() instanceof StringType
+ |
+ this = oc.getAnArgument()
)
}
}
From d4acccb13c9c55e525112c8f2fac82bdbfc2e8bd Mon Sep 17 00:00:00 2001
From: Chelsea Boling
Date: Wed, 16 Dec 2020 20:33:09 -0800
Subject: [PATCH 015/741] Update sink
---
.../src/semmle/code/csharp/frameworks/ServiceStack.qll | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 938ffd2ed46..2b4b8e89a7e 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -98,19 +98,18 @@ module XSS {
class XssExpr extends Expr {
XssExpr() {
- exists(ReturnStmt r, Expr arg |
+ exists(ReturnStmt r |
(
- r.getExpr().getType() instanceof StringType and
- arg = r.getExpr()
+ r.getExpr().getType() instanceof StringType
)
|
- this = arg
+ this = r.getExpr()
) or
exists(ObjectCreation oc |
oc.getType().hasName("HttpResult") and
oc.getAnArgument().getType() instanceof StringType
|
- this = oc.getAnArgument()
+ this = oc.getArgument(0)
)
}
}
From 7d47bffd53a4a168f1b4e48ccafb434079ca3aef Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Tue, 15 Dec 2020 17:04:09 -0800
Subject: [PATCH 016/741] Tidy up ServiceStack.qll
Use fully qualified names for classes
Make util predicate private
Make naming more consistent with rest of ql libs
---
.../code/csharp/frameworks/ServiceStack.qll | 143 +++++++++++-------
1 file changed, 86 insertions(+), 57 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 2b4b8e89a7e..610c97ce59e 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -2,35 +2,33 @@ import csharp
/** Provides definitions related to the namespace `ServiceStack`. */
module ServiceStack {
+
+ /** A class representing a Service */
class ServiceClass extends Class {
- ServiceClass() { this.getBaseClass+().getName()="Service" }
- }
-
- class ServiceStackRequestClass extends Class {
- ServiceStackRequestClass() {
- // Classes directly used in as param to request method
- exists(Method m |
- serviceStackRequests(m) and
- this = m.getAParameter().getType()) or
- // Classes of a property or field on another request class
- exists(ServiceStackRequestClass outer |
- this = outer.getAProperty().getType() or
- this = outer.getAField().getType())
+ 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"])
}
}
-
- predicate serviceStackRequests(Method m) {
- exists(ValueOrRefType type, ServiceClass ssc |
- type.fromSource() and
- m = type.getAMethod() and
- m = ssc.getAMember().getDeclaringType().getAMethod() and
-
- // verify other verb methods in service stack docs
- m.getName() = ["Post","Get", "Put", "Delete","Any", "Option", "Head"]
- // not reccommended match approach below
- //.toLowerCase().regexpMatch("(Post|Get|Put|Delete)")
-
- )
+
+ /** 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
+ )
+ }
}
}
@@ -38,21 +36,36 @@ module ServiceStack {
module Sources {
private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.flowsources.Remote
+ private import semmle.code.csharp.commons.Collections
- class ServiceClassSources extends RemoteFlowSource {
- ServiceClassSources() {
- exists(Method m |
- serviceStackRequests(m) and
- // keep this for primitive typed request params (string/int)
- this.asParameter() = m.getAParameter()) or
- exists(ServiceStackRequestClass reqClass |
- // look for field reads on request classes
- reqClass.getAProperty().getAnAccess() = this.asExpr() or
- reqClass.getAField().getAnAccess() = this.asExpr())
+ /** 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
+ )
+ )
+ }
+
+ 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 = "ServiceStackSources"
+ result = "ServiceStack request DTO field"
}
}
}
@@ -62,28 +75,44 @@ module SQL {
private import ServiceStack::ServiceStack
private import semmle.code.csharp.security.dataflow.SqlInjection::SqlInjection
- class SqlSinks extends Sink {
- SqlSinks() { this.asExpr() instanceof SqlInjectionExpr }
- }
-
- class SqlStringMethod extends Method {
- SqlStringMethod() {
- this.getName() in [
- "Custom", "CustomSelect", "CustomInsert", "CustomUpdate",
- "SqlScalar", "SqlList", "SqlColumn", "ColumnDistinct",
- "PreCreateTable", "PostCreateTable", "PreDropTable", "PostDropTable",
- "ExecuteSql", "ExecuteSqlAsync",
- "UnsafeSelect", "UnsafeFrom", "UnsafeWhere", "UnsafeAnd", "UnsafeOr"
- ]
+ 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())
}
}
-
- class SqlInjectionExpr extends Expr {
- SqlInjectionExpr() {
- exists(MethodCall mc |
- mc.getTarget() instanceof SqlStringMethod and mc.getArgument(1) = this
- )
- }
+
+ 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"
+
+ )
}
}
From 6d5f9035e6c14d8177fc2732e9a49129b7853f1b Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Thu, 17 Dec 2020 09:00:23 -0800
Subject: [PATCH 017/741] Minor fixes to XSS:
Only want returns in request methods
Also care about non-string 1st args to HttpResult e.g. streams
---
.../semmle/code/csharp/frameworks/ServiceStack.qll | 13 ++++---------
1 file changed, 4 insertions(+), 9 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
index 610c97ce59e..91169fd318e 100644
--- a/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
+++ b/csharp/ql/src/semmle/code/csharp/frameworks/ServiceStack.qll
@@ -127,17 +127,12 @@ module XSS {
class XssExpr extends Expr {
XssExpr() {
- exists(ReturnStmt r |
- (
- r.getExpr().getType() instanceof StringType
- )
- |
- this = r.getExpr()
+ exists(ServiceClass service, ReturnStmt r |
+ this = r.getExpr() and
+ r.getEnclosingCallable() = service.getARequestMethod()
) or
exists(ObjectCreation oc |
- oc.getType().hasName("HttpResult") and
- oc.getAnArgument().getType() instanceof StringType
- |
+ oc.getType().hasQualifiedName("ServiceStack.HttpResult") and
this = oc.getArgument(0)
)
}
From 3f1f83f66765f93805c4e3ec63c005d9197019e8 Mon Sep 17 00:00:00 2001
From: John Lugton
Date: Thu, 17 Dec 2020 16:24:52 -0800
Subject: [PATCH 018/741] remove experimental
---
.../SQL-Injection/SQLInjection.cs | 61 -------------------
.../SQL-Injection/SQLInjection.qhelp | 35 -----------
.../SQL-Injection/SQLInjection.ql | 15 -----
.../src/experimental/ServiceStack/XSS/XSS.cs | 37 -----------
4 files changed, 148 deletions(-)
delete mode 100644 csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs
delete mode 100644 csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp
delete mode 100644 csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.ql
delete mode 100644 csharp/ql/src/experimental/ServiceStack/XSS/XSS.cs
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs
deleted file mode 100644
index e41cdb0ff7f..00000000000
--- a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using ServiceStack;
-using ServiceStack.OrmLite;
-using SqlServer.ServiceModel;
-using SqlServer.ServiceModel.Types;
-
-namespace SqlServer.ServiceInterface
-{
- public class CustomerService : Service
- {
- public GetCustomersResponse Get(GetCustomers request)
- {
- return new GetCustomersResponse { Results = Db.Select() };
- }
-
- public GetCustomerResponse Get(GetCustomer request)
- {
- //var customer = Db.SingleById(request.Id);
- var customer = Db.SqlScalar("SELECT Id FROM Customer WHERE Id = " + request.Id + ";");
- if (customer == null)
- throw HttpError.NotFound("Customer not found");
-
- return new GetCustomerResponse
- {
- Result = Db.SingleById(request.Id)
- };
- }
-
- public CreateCustomerResponse Post(CreateCustomer request)
- {
- var customer = new Customer { Name = request.Name };
- //Db.Save(customer);
- Db.ExecuteSql("INSERT INTO Customer (Name) VALUES ('" + customer.Name + "')");
- return new CreateCustomerResponse
- {
- Result = customer
- };
- }
-
- public UpdateCustomerResponse Put(UpdateCustomer request)
- {
- var customer = Db.SingleById(request.Id);
- if (customer == null)
- throw HttpError.NotFound("Customer '{0}' does not exist".Fmt(request.Id));
-
- customer.Name = request.Name;
- //Db.Update(customer);
- Db.ExecuteSqlAsync("UPDATE Customer SET Name = '" + customer.Name + "' WHERE Id = " + request.Id);
- return new UpdateCustomerResponse
- {
- Result = customer
- };
- }
-
- public void Delete(DeleteCustomer request)
- {
- //Db.DeleteById(request.Id);
- string q = @"DELETE FROM Customer WHERE Id = " + request.Id;
- Db.ExecuteSql(q);
- }
- }
-}
diff --git a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp b/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp
deleted file mode 100644
index 3b51f6a6d34..00000000000
--- a/csharp/ql/src/experimental/ServiceStack/SQL-Injection/SQLInjection.qhelp
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-If unsanitized user input is.....
-
-
-
-
-
-
-You can do better....
-
-
-If using some method, you should always pass...
-
-
-Something must be set to this thing
-
-
-
-
-
-
-In the following example....
-
-
-
-
-
-Services Stack: Documentation.
-
-
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/741] 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/741] 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/741] 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/741] 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/741] 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/741] 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/741] 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/741] 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/741] 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 fc7d340a898ce44f3f1f093f480c8356a195a56f Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 7 May 2021 12:51:01 +0000
Subject: [PATCH 028/741] Query to detect hard-coded Azure credentials
---
.../CWE-798/HardcodedAzureCredentials.java | 52 ++++++
.../CWE-798/HardcodedCredentialsApiCall.qhelp | 30 ++++
.../src/Security/CWE/CWE-798/SensitiveApi.qll | 10 +-
.../tests/HardcodedAzureCredentials.java | 66 ++++++++
.../HardcodedCredentialsApiCall.expected | 37 +++++
.../HardcodedCredentialsSourceCall.expected | 28 ++++
.../security/CWE-798/semmle/tests/options | 2 +-
.../core/credential/TokenCredential.java | 10 ++
.../identity/AadCredentialBuilderBase.java | 43 +++++
.../identity/ClientSecretCredential.java | 18 ++
.../ClientSecretCredentialBuilder.java | 63 +++++++
.../azure/identity/CredentialBuilderBase.java | 13 ++
.../TokenCachePersistenceOptions.java | 48 ++++++
.../identity/UsernamePasswordCredential.java | 14 ++
.../UsernamePasswordCredentialBuilder.java | 74 +++++++++
.../keyvault/secrets/SecretClient.java | 155 ++++++++++++++++++
.../keyvault/secrets/SecretClientBuilder.java | 92 +++++++++++
.../secrets/models/KeyVaultSecret.java | 78 +++++++++
.../secrets/models/SecretProperties.java | 145 ++++++++++++++++
19 files changed, 974 insertions(+), 4 deletions(-)
create mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java
create mode 100644 java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedAzureCredentials.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/core/credential/TokenCredential.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/AadCredentialBuilderBase.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredential.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredentialBuilder.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/CredentialBuilderBase.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/TokenCachePersistenceOptions.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredential.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredentialBuilder.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClient.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClientBuilder.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/KeyVaultSecret.java
create mode 100644 java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/SecretProperties.java
diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java b/java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java
new file mode 100644
index 00000000000..a8b52aa43a1
--- /dev/null
+++ b/java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java
@@ -0,0 +1,52 @@
+public class HardcodedAzureCredentials {
+ private final String clientId = "81734019-15a3-50t8-3253-5abe78abc3a1";
+ private final String username = "username@example.onmicrosoft.com";
+ private final String clientSecret = "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_";
+ private final String tenantId = "22f367ce-535x-357w-2179-a33517mn166h";
+
+ //BAD: hard-coded username/password credentials
+ public void testHardcodedUsernamePassword(String input) {
+ UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
+ .clientId(clientId)
+ .username(username)
+ .password(clientSecret)
+ .build();
+
+ SecretClient client = new SecretClientBuilder()
+ .vaultUrl("https://myKeyVault.vault.azure.net")
+ .credential(usernamePasswordCredential)
+ .buildClient();
+ }
+
+ //GOOD: username/password credentials stored as environment variables
+ public void testEnvironmentUsernamePassword(String input) {
+ UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
+ .clientId(clientId)
+ .username(System.getenv("myUsername"))
+ .password(System.getenv("mySuperSecurePass"))
+ .build();
+
+ SecretClient client = new SecretClientBuilder()
+ .vaultUrl("https://myKeyVault.vault.azure.net")
+ .credential(usernamePasswordCredential)
+ .buildClient();
+ }
+
+ //BAD: hard-coded client secret
+ public void testHardcodedClientSecret(String input) {
+ ClientSecretCredential defaultCredential = new ClientSecretCredentialBuilder()
+ .clientId(clientId)
+ .clientSecret(clientSecret)
+ .tenantId(tenantId)
+ .build();
+ }
+
+ //GOOD: client secret stored as environment variables
+ public void testEnvironmentClientSecret(String input) {
+ ClientSecretCredential defaultCredential = new ClientSecretCredentialBuilder()
+ .clientId(clientId)
+ .clientSecret(System.getenv("myClientSecret"))
+ .tenantId(tenantId)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp
index 231140a287e..efcf6b15abf 100644
--- a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp
+++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp
@@ -32,6 +32,28 @@
Instead, the user name and password could be supplied through environment variables,
which can be set externally without hard-coding credentials in the source code.
+
+
+ The following code example connects to AWS using a hard-coded access key ID and secret key:
+
+
+
+
+
+ Instead, the access key ID and secret key could be supplied through environment variables,
+ which can be set externally without hard-coding credentials in the source code.
+
+
+
+ The following code example connects to Azure using a hard-coded user name and password or client secret:
+
+
+
+
+
+ Instead, the username and password or client secret could be supplied through environment variables,
+ which can be set externally without hard-coding credentials in the source code.
+
@@ -39,6 +61,14 @@
OWASP:
Use of hard-coded password.
+
+Microsoft:
+Azure authentication with user credentials.
+
+
+Amazon:
+Working with AWS Credentials.
+
diff --git a/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll b/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll
index 56072496293..928a3562ec6 100644
--- a/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll
+++ b/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll
@@ -130,7 +130,8 @@ private predicate javaApiCallablePasswordParam(string s) {
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, int, String, String);3" or
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, String, String);2" or
s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, int, String, String);3" or
- s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);1"
+ s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);1" or
+ s = "com.azure.identity.UsernamePasswordCredentialBuilder;password(String);0"
}
/**
@@ -202,7 +203,8 @@ private predicate javaApiCallableUsernameParam(string s) {
s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, String);1" or
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, int, String, String);2" or
s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, int, String);2" or
- s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);0"
+ s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);0" or
+ s = "com.azure.identity.UsernamePasswordCredentialBuilder;username(String);0"
}
/**
@@ -510,5 +512,7 @@ private predicate otherApiCallableCredentialParam(string s) {
s =
"org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection extends GrantedAuthority>);0" or
s =
- "org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection extends GrantedAuthority>);1"
+ "org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection extends GrantedAuthority>);1" or
+ s = "com.azure.identity.ClientSecretCredentialBuilder;clientSecret(String);0"
+
}
diff --git a/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedAzureCredentials.java b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedAzureCredentials.java
new file mode 100644
index 00000000000..d243e18d608
--- /dev/null
+++ b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedAzureCredentials.java
@@ -0,0 +1,66 @@
+import com.azure.identity.ClientSecretCredential;
+import com.azure.identity.ClientSecretCredentialBuilder;
+import com.azure.identity.UsernamePasswordCredential;
+import com.azure.identity.UsernamePasswordCredentialBuilder;
+import com.azure.security.keyvault.secrets.SecretClient;
+import com.azure.security.keyvault.secrets.SecretClientBuilder;
+
+public class HardcodedAzureCredentials {
+ private final String clientId = "81734019-15a3-50t8-3253-5abe78abc3a1";
+ private final String username = "username@example.onmicrosoft.com";
+ private final String clientSecret = "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_";
+ private final String tenantId = "22f367ce-535x-357w-2179-a33517mn166h";
+
+ //BAD: hard-coded username/password credentials
+ public void testHardcodedUsernamePassword(String input) {
+ UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
+ .clientId(clientId)
+ .username(username)
+ .password(clientSecret)
+ .build();
+
+ SecretClient client = new SecretClientBuilder()
+ .vaultUrl("https://myKeyVault.vault.azure.net")
+ .credential(usernamePasswordCredential)
+ .buildClient();
+ }
+
+ //GOOD: username/password credentials stored as environment variables
+ public void testEnvironmentUsernamePassword(String input) {
+ UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
+ .clientId(clientId)
+ .username(System.getenv("myUsername"))
+ .password(System.getenv("mySuperSecurePass"))
+ .build();
+
+ SecretClient client = new SecretClientBuilder()
+ .vaultUrl("https://myKeyVault.vault.azure.net")
+ .credential(usernamePasswordCredential)
+ .buildClient();
+ }
+
+ //BAD: hard-coded client secret
+ public void testHardcodedClientSecret(String input) {
+ ClientSecretCredential defaultCredential = new ClientSecretCredentialBuilder()
+ .clientId(clientId)
+ .clientSecret(clientSecret)
+ .tenantId(tenantId)
+ .build();
+ }
+
+ //GOOD: client secret stored as environment variables
+ public void testEnvironmentClientSecret(String input) {
+ ClientSecretCredential defaultCredential = new ClientSecretCredentialBuilder()
+ .clientId(clientId)
+ .clientSecret(System.getenv("myClientSecret"))
+ .tenantId(tenantId)
+ .build();
+ }
+
+ public static void main(String[] args) {
+ new HardcodedAzureCredentials().testHardcodedUsernamePassword(args[0]);
+ new HardcodedAzureCredentials().testEnvironmentUsernamePassword(args[0]);
+ new HardcodedAzureCredentials().testHardcodedClientSecret(args[0]);
+ new HardcodedAzureCredentials().testEnvironmentClientSecret(args[0]);
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsApiCall.expected b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsApiCall.expected
index ccd0259322e..8c87565b553 100644
--- a/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsApiCall.expected
+++ b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsApiCall.expected
@@ -10,6 +10,22 @@ edges
| FileCredentialTest.java:13:14:13:20 | "admin" : String | FileCredentialTest.java:19:13:19:13 | u : String |
| FileCredentialTest.java:19:13:19:13 | u : String | FileCredentialTest.java:22:38:22:45 | v : String |
| FileCredentialTest.java:22:38:22:45 | v : String | FileCredentialTest.java:23:36:23:36 | v |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String | HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String | HardcodedAzureCredentials.java:63:3:63:33 | new HardcodedAzureCredentials(...) [clientSecret] : String |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [username] : String | HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String |
+| HardcodedAzureCredentials.java:10:2:10:68 | this <.field> [post update] [username] : String | HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [username] : String |
+| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | HardcodedAzureCredentials.java:10:2:10:68 | this <.field> [post update] [username] : String |
+| HardcodedAzureCredentials.java:11:2:11:74 | this <.field> [post update] [clientSecret] : String | HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:11:2:11:74 | this <.field> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String | HardcodedAzureCredentials.java:19:13:19:24 | this <.field> [clientSecret] : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String | HardcodedAzureCredentials.java:18:13:18:20 | this <.field> [username] : String |
+| HardcodedAzureCredentials.java:18:13:18:20 | this <.field> [username] : String | HardcodedAzureCredentials.java:18:13:18:20 | username |
+| HardcodedAzureCredentials.java:19:13:19:24 | this <.field> [clientSecret] : String | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret |
+| HardcodedAzureCredentials.java:43:14:43:38 | parameter this [clientSecret] : String | HardcodedAzureCredentials.java:46:17:46:28 | this <.field> [clientSecret] : String |
+| HardcodedAzureCredentials.java:46:17:46:28 | this <.field> [clientSecret] : String | HardcodedAzureCredentials.java:46:17:46:28 | clientSecret |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String | HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String |
+| HardcodedAzureCredentials.java:63:3:63:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | HardcodedAzureCredentials.java:43:14:43:38 | parameter this [clientSecret] : String |
| Test.java:9:16:9:22 | "admin" : String | Test.java:12:13:12:15 | usr : String |
| Test.java:9:16:9:22 | "admin" : String | Test.java:15:36:15:38 | usr |
| Test.java:9:16:9:22 | "admin" : String | Test.java:17:39:17:41 | usr |
@@ -42,6 +58,24 @@ nodes
| FileCredentialTest.java:23:36:23:36 | v | semmle.label | v |
| HardcodedAWSCredentials.java:8:50:8:61 | "ACCESS_KEY" | semmle.label | "ACCESS_KEY" |
| HardcodedAWSCredentials.java:8:64:8:75 | "SECRET_KEY" | semmle.label | "SECRET_KEY" |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String | semmle.label | this <.method> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [username] : String | semmle.label | this <.method> [post update] [username] : String |
+| HardcodedAzureCredentials.java:10:2:10:68 | this <.field> [post update] [username] : String | semmle.label | this <.field> [post update] [username] : String |
+| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | semmle.label | "username@example.onmicrosoft.com" : String |
+| HardcodedAzureCredentials.java:11:2:11:74 | this <.field> [post update] [clientSecret] : String | semmle.label | this <.field> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | semmle.label | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String | semmle.label | parameter this [clientSecret] : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String | semmle.label | parameter this [username] : String |
+| HardcodedAzureCredentials.java:18:13:18:20 | this <.field> [username] : String | semmle.label | this <.field> [username] : String |
+| HardcodedAzureCredentials.java:18:13:18:20 | username | semmle.label | username |
+| HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | semmle.label | clientSecret |
+| HardcodedAzureCredentials.java:19:13:19:24 | this <.field> [clientSecret] : String | semmle.label | this <.field> [clientSecret] : String |
+| HardcodedAzureCredentials.java:43:14:43:38 | parameter this [clientSecret] : String | semmle.label | parameter this [clientSecret] : String |
+| HardcodedAzureCredentials.java:46:17:46:28 | clientSecret | semmle.label | clientSecret |
+| HardcodedAzureCredentials.java:46:17:46:28 | this <.field> [clientSecret] : String | semmle.label | this <.field> [clientSecret] : String |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | semmle.label | new HardcodedAzureCredentials(...) [clientSecret] : String |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String | semmle.label | new HardcodedAzureCredentials(...) [username] : String |
+| HardcodedAzureCredentials.java:63:3:63:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | semmle.label | new HardcodedAzureCredentials(...) [clientSecret] : String |
| Test.java:9:16:9:22 | "admin" : String | semmle.label | "admin" : String |
| Test.java:10:17:10:24 | "123456" : String | semmle.label | "123456" : String |
| Test.java:12:13:12:15 | usr : String | semmle.label | usr : String |
@@ -72,6 +106,9 @@ nodes
| FileCredentialTest.java:18:35:18:41 | "admin" | FileCredentialTest.java:18:35:18:41 | "admin" | FileCredentialTest.java:18:35:18:41 | "admin" | Hard-coded value flows to $@. | FileCredentialTest.java:18:35:18:41 | "admin" | sensitive API call |
| HardcodedAWSCredentials.java:8:50:8:61 | "ACCESS_KEY" | HardcodedAWSCredentials.java:8:50:8:61 | "ACCESS_KEY" | HardcodedAWSCredentials.java:8:50:8:61 | "ACCESS_KEY" | Hard-coded value flows to $@. | HardcodedAWSCredentials.java:8:50:8:61 | "ACCESS_KEY" | sensitive API call |
| HardcodedAWSCredentials.java:8:64:8:75 | "SECRET_KEY" | HardcodedAWSCredentials.java:8:64:8:75 | "SECRET_KEY" | HardcodedAWSCredentials.java:8:64:8:75 | "SECRET_KEY" | Hard-coded value flows to $@. | HardcodedAWSCredentials.java:8:64:8:75 | "SECRET_KEY" | sensitive API call |
+| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" | HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | HardcodedAzureCredentials.java:18:13:18:20 | username | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:18:13:18:20 | username | sensitive API call |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" | HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | sensitive API call |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" | HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:46:17:46:28 | clientSecret | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:46:17:46:28 | clientSecret | sensitive API call |
| Test.java:9:16:9:22 | "admin" | Test.java:9:16:9:22 | "admin" : String | Test.java:15:36:15:38 | usr | Hard-coded value flows to $@. | Test.java:15:36:15:38 | usr | sensitive API call |
| Test.java:9:16:9:22 | "admin" | Test.java:9:16:9:22 | "admin" : String | Test.java:17:39:17:41 | usr | Hard-coded value flows to $@. | Test.java:17:39:17:41 | usr | sensitive API call |
| Test.java:9:16:9:22 | "admin" | Test.java:9:16:9:22 | "admin" : String | Test.java:18:39:18:41 | usr | Hard-coded value flows to $@. | Test.java:18:39:18:41 | usr | sensitive API call |
diff --git a/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsSourceCall.expected b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsSourceCall.expected
index 05e47dacf3f..cabd4a20ca2 100644
--- a/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsSourceCall.expected
+++ b/java/ql/test/query-tests/security/CWE-798/semmle/tests/HardcodedCredentialsSourceCall.expected
@@ -1,11 +1,39 @@
edges
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String | HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [username] : String | HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String |
+| HardcodedAzureCredentials.java:10:2:10:68 | this <.field> [post update] [username] : String | HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [username] : String |
+| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | HardcodedAzureCredentials.java:10:2:10:68 | this <.field> [post update] [username] : String |
+| HardcodedAzureCredentials.java:11:2:11:74 | this <.field> [post update] [clientSecret] : String | HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:11:2:11:74 | this <.field> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String | HardcodedAzureCredentials.java:19:13:19:24 | this <.field> [clientSecret] : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String | HardcodedAzureCredentials.java:18:13:18:20 | this <.field> [username] : String |
+| HardcodedAzureCredentials.java:18:13:18:20 | this <.field> [username] : String | HardcodedAzureCredentials.java:18:13:18:20 | username |
+| HardcodedAzureCredentials.java:19:13:19:24 | this <.field> [clientSecret] : String | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String | HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String |
| Test.java:10:17:10:24 | "123456" : String | Test.java:26:17:26:20 | pass |
| User.java:2:43:2:50 | "123456" : String | User.java:5:15:5:24 | DEFAULT_PW |
nodes
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [clientSecret] : String | semmle.label | this <.method> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:8:14:8:38 | this <.method> [post update] [username] : String | semmle.label | this <.method> [post update] [username] : String |
+| HardcodedAzureCredentials.java:10:2:10:68 | this <.field> [post update] [username] : String | semmle.label | this <.field> [post update] [username] : String |
+| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | semmle.label | "username@example.onmicrosoft.com" : String |
+| HardcodedAzureCredentials.java:11:2:11:74 | this <.field> [post update] [clientSecret] : String | semmle.label | this <.field> [post update] [clientSecret] : String |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | semmle.label | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [clientSecret] : String | semmle.label | parameter this [clientSecret] : String |
+| HardcodedAzureCredentials.java:15:14:15:42 | parameter this [username] : String | semmle.label | parameter this [username] : String |
+| HardcodedAzureCredentials.java:18:13:18:20 | this <.field> [username] : String | semmle.label | this <.field> [username] : String |
+| HardcodedAzureCredentials.java:18:13:18:20 | username | semmle.label | username |
+| HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | semmle.label | clientSecret |
+| HardcodedAzureCredentials.java:19:13:19:24 | this <.field> [clientSecret] : String | semmle.label | this <.field> [clientSecret] : String |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [clientSecret] : String | semmle.label | new HardcodedAzureCredentials(...) [clientSecret] : String |
+| HardcodedAzureCredentials.java:61:3:61:33 | new HardcodedAzureCredentials(...) [username] : String | semmle.label | new HardcodedAzureCredentials(...) [username] : String |
| Test.java:10:17:10:24 | "123456" : String | semmle.label | "123456" : String |
| Test.java:26:17:26:20 | pass | semmle.label | pass |
| User.java:2:43:2:50 | "123456" : String | semmle.label | "123456" : String |
| User.java:5:15:5:24 | DEFAULT_PW | semmle.label | DEFAULT_PW |
#select
+| HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" | HardcodedAzureCredentials.java:10:34:10:67 | "username@example.onmicrosoft.com" : String | HardcodedAzureCredentials.java:18:13:18:20 | username | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:18:13:18:20 | username | sensitive call |
+| HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" | HardcodedAzureCredentials.java:11:38:11:73 | "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_" : String | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | Hard-coded value flows to $@. | HardcodedAzureCredentials.java:19:13:19:24 | clientSecret | sensitive call |
| Test.java:10:17:10:24 | "123456" | Test.java:10:17:10:24 | "123456" : String | Test.java:26:17:26:20 | pass | Hard-coded value flows to $@. | Test.java:26:17:26:20 | pass | sensitive call |
| User.java:2:43:2:50 | "123456" | User.java:2:43:2:50 | "123456" : String | User.java:5:15:5:24 | DEFAULT_PW | Hard-coded value flows to $@. | User.java:5:15:5:24 | DEFAULT_PW | sensitive call |
diff --git a/java/ql/test/query-tests/security/CWE-798/semmle/tests/options b/java/ql/test/query-tests/security/CWE-798/semmle/tests/options
index e13da5319f0..4e56d220d7e 100644
--- a/java/ql/test/query-tests/security/CWE-798/semmle/tests/options
+++ b/java/ql/test/query-tests/security/CWE-798/semmle/tests/options
@@ -1 +1 @@
-// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700
+// semmle-extractor-options: --javac-args -cp ${testdir}/../../../../../stubs/amazon-aws-sdk-1.11.700:${testdir}/../../../../../stubs/azure-sdk-for-java
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/core/credential/TokenCredential.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/core/credential/TokenCredential.java
new file mode 100644
index 00000000000..bacaa46a251
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/core/credential/TokenCredential.java
@@ -0,0 +1,10 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.core.credential;
+
+/**
+ * The interface for credentials that can provide a token.
+ */
+public interface TokenCredential {
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/AadCredentialBuilderBase.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/AadCredentialBuilderBase.java
new file mode 100644
index 00000000000..bedaab87e69
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/AadCredentialBuilderBase.java
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.identity;
+
+/**
+ * The base class for credential builders that allow specifying a client ID and tenant ID for an Azure Active Directory.
+ * @param the type of the credential builder
+ */
+public abstract class AadCredentialBuilderBase> extends CredentialBuilderBase {
+
+ /**
+ * Specifies the Azure Active Directory endpoint to acquire tokens.
+ * @param authorityHost the Azure Active Directory endpoint
+ * @return An updated instance of this builder with the authority host set as specified.
+ */
+ @SuppressWarnings("unchecked")
+ public T authorityHost(String authorityHost) {
+ return null;
+ }
+
+ /**
+ * Sets the client ID of the application.
+ *
+ * @param clientId the client ID of the application.
+ * @return An updated instance of this builder with the client id set as specified.
+ */
+ @SuppressWarnings("unchecked")
+ public T clientId(String clientId) {
+ return null;
+ }
+
+ /**
+ * Sets the tenant ID of the application.
+ *
+ * @param tenantId the tenant ID of the application.
+ * @return An updated instance of this builder with the tenant id set as specified.
+ */
+ @SuppressWarnings("unchecked")
+ public T tenantId(String tenantId) {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredential.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredential.java
new file mode 100644
index 00000000000..9b8182451b8
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredential.java
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.identity;
+
+import com.azure.core.credential.TokenCredential;
+
+/**
+ * An AAD credential that acquires a token with a client secret for an AAD application.
+ *
+ * Sample: Construct a simple ClientSecretCredential
+ * {@codesnippet com.azure.identity.credential.clientsecretcredential.construct}
+ *
+ * Sample: Construct a ClientSecretCredential behind a proxy
+ * {@codesnippet com.azure.identity.credential.clientsecretcredential.constructwithproxy}
+ */
+public class ClientSecretCredential implements TokenCredential {
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredentialBuilder.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredentialBuilder.java
new file mode 100644
index 00000000000..85ab73c3060
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/ClientSecretCredentialBuilder.java
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.identity;
+
+/**
+ * Fluent credential builder for instantiating a {@link ClientSecretCredential}.
+ *
+ * @see ClientSecretCredential
+ */
+public class ClientSecretCredentialBuilder extends AadCredentialBuilderBase {
+ /**
+ * Sets the client secret for the authentication.
+ * @param clientSecret the secret value of the AAD application.
+ * @return An updated instance of this builder.
+ */
+ public ClientSecretCredentialBuilder clientSecret(String clientSecret) {
+ return null;
+ }
+
+ /**
+ * Enables the shared token cache which is disabled by default. If enabled, the credential will store tokens
+ * in a cache persisted to the machine, protected to the current user, which can be shared by other credentials
+ * and processes.
+ *
+ * @return An updated instance of this builder.
+ */
+ ClientSecretCredentialBuilder enablePersistentCache() {
+ return null;
+ }
+
+ /**
+ * Allows to use an unprotected file specified by cacheFileLocation() instead of
+ * Gnome keyring on Linux. This is restricted by default.
+ *
+ * @return An updated instance of this builder.
+ */
+ ClientSecretCredentialBuilder allowUnencryptedCache() {
+ return null;
+ }
+
+ /**
+ * Configures the persistent shared token cache options and enables the persistent token cache which is disabled
+ * by default. If configured, the credential will store tokens in a cache persisted to the machine, protected to
+ * the current user, which can be shared by other credentials and processes.
+ *
+ * @param tokenCachePersistenceOptions the token cache configuration options
+ * @return An updated instance of this builder with the token cache options configured.
+ */
+ public ClientSecretCredentialBuilder tokenCachePersistenceOptions(TokenCachePersistenceOptions
+ tokenCachePersistenceOptions) {
+ return null;
+ }
+
+ /**
+ * Creates a new {@link ClientCertificateCredential} with the current configurations.
+ *
+ * @return a {@link ClientSecretCredentialBuilder} with the current configurations.
+ */
+ public ClientSecretCredential build() {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/CredentialBuilderBase.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/CredentialBuilderBase.java
new file mode 100644
index 00000000000..c1210942ea0
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/CredentialBuilderBase.java
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.identity;
+
+/**
+ * The base class for all the credential builders.
+ * @param the type of the credential builder
+ */
+public abstract class CredentialBuilderBase> {
+ CredentialBuilderBase() {
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/TokenCachePersistenceOptions.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/TokenCachePersistenceOptions.java
new file mode 100644
index 00000000000..d5580fb05f6
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/TokenCachePersistenceOptions.java
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.identity;
+
+/**
+ * Represents the Persistence Token Cache options used to setup the persistent access token cache.
+ */
+public final class TokenCachePersistenceOptions {
+
+ /**
+ * Allows to use an unprotected file specified by cacheFileLocation() instead of
+ * Gnome keyring on Linux. This is restricted by default. For other platforms this setting currently doesn't apply.
+ *
+ * @param unencryptedStorageAllowed The flag indicating if unencrypted storage is allowed for the cache or not.
+ * @return An updated instance of the options bag.
+ */
+ public TokenCachePersistenceOptions setUnencryptedStorageAllowed(boolean unencryptedStorageAllowed) {
+ return null;
+ }
+
+ /**
+ * Gets the status whether unencrypted storage is allowed for the persistent token cache.
+ *
+ * @return The status indicating if unencrypted storage is allowed for the persistent token cache.
+ */
+ public boolean isUnencryptedStorageAllowed() {
+ return false;
+ }
+
+ /**
+ * Set the name uniquely identifying the cache.
+ *
+ * @param name the name of the cache
+ * @return the updated instance of the cache.
+ */
+ public TokenCachePersistenceOptions setName(String name) {
+ return null;
+ }
+
+ /**
+ * Get the name uniquely identifying the cache.
+ *
+ * @return the name of the cache.
+ */
+ public String getName() {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredential.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredential.java
new file mode 100644
index 00000000000..5cb9463eb95
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredential.java
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.identity;
+
+import com.azure.core.credential.TokenCredential;
+
+/**
+ * An AAD credential that acquires a token with a username and a password. Users with 2FA/MFA (Multi-factored auth)
+ * turned on will not be able to use this credential. Please use {@link DeviceCodeCredential} or {@link
+ * InteractiveBrowserCredential} instead, or create a service principal if you want to authenticate silently.
+ */
+public class UsernamePasswordCredential implements TokenCredential {
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredentialBuilder.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredentialBuilder.java
new file mode 100644
index 00000000000..b6aa411ed84
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/identity/UsernamePasswordCredentialBuilder.java
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.identity;
+
+import com.azure.security.keyvault.secrets.SecretClient;
+
+/**
+ * Fluent credential builder for instantiating a {@link UsernamePasswordCredential}.
+ *
+ * @see UsernamePasswordCredential
+ */
+public class UsernamePasswordCredentialBuilder extends AadCredentialBuilderBase {
+ /**
+ * Sets the username of the user.
+ * @param username the username of the user
+ * @return the UserCredentialBuilder itself
+ */
+ public UsernamePasswordCredentialBuilder username(String username) {
+ return null;
+ }
+
+ /**
+ * Sets the password of the user.
+ * @param password the password of the user
+ * @return the UserCredentialBuilder itself
+ */
+ public UsernamePasswordCredentialBuilder password(String password) {
+ return null;
+ }
+
+ /**
+ * Configures the persistent shared token cache options and enables the persistent token cache which is disabled
+ * by default. If configured, the credential will store tokens in a cache persisted to the machine, protected to
+ * the current user, which can be shared by other credentials and processes.
+ *
+ * @param tokenCachePersistenceOptions the token cache configuration options
+ * @return An updated instance of this builder with the token cache options configured.
+ */
+ public UsernamePasswordCredentialBuilder tokenCachePersistenceOptions(TokenCachePersistenceOptions
+ tokenCachePersistenceOptions) {
+ return null;
+ }
+
+ /**
+ * Allows to use an unprotected file specified by cacheFileLocation() instead of
+ * Gnome keyring on Linux. This is restricted by default.
+ *
+ * @return An updated instance of this builder.
+ */
+ UsernamePasswordCredentialBuilder allowUnencryptedCache() {
+ return null;
+ }
+
+ /**
+ * Enables the shared token cache which is disabled by default. If enabled, the credential will store tokens
+ * in a cache persisted to the machine, protected to the current user, which can be shared by other credentials
+ * and processes.
+ *
+ * @return An updated instance of this builder with if the shared token cache enabled specified.
+ */
+ UsernamePasswordCredentialBuilder enablePersistentCache() {
+ return null;
+ }
+
+ /**
+ * Creates a new {@link UsernamePasswordCredential} with the current configurations.
+ *
+ * @return a {@link UsernamePasswordCredential} with the current configurations.
+ */
+ public UsernamePasswordCredential build() {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClient.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClient.java
new file mode 100644
index 00000000000..94cdc7d1be7
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClient.java
@@ -0,0 +1,155 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.security.keyvault.secrets;
+
+import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
+import com.azure.security.keyvault.secrets.models.SecretProperties;
+
+/**
+ * The SecretClient provides synchronous methods to manage {@link KeyVaultSecret secrets} in the Azure Key Vault. The client
+ * supports creating, retrieving, updating, deleting, purging, backing up, restoring, and listing the {@link KeyVaultSecret
+ * secrets}. The client also supports listing {@link DeletedSecret deleted secrets} for a soft-delete enabled Azure Key
+ * Vault.
+ *
+ * Construct the sync client
+ * {@codesnippet com.azure.security.keyvault.secretclient.sync.construct}
+ *
+ * @see SecretClientBuilder
+ * @see PagedIterable
+ */
+public final class SecretClient {
+
+ /**
+ * Gets the vault endpoint url to which service requests are sent to.
+ * @return the vault endpoint url.
+ */
+ public String getVaultUrl() {
+ return null;
+ }
+
+ /**
+ * Adds a secret to the key vault if it does not exist. If the named secret exists, a new version of the secret is
+ * created. This operation requires the {@code secrets/set} permission.
+ *
+ * The {@link SecretProperties#getExpiresOn() expires}, {@link SecretProperties#getContentType() contentType},
+ * and {@link SecretProperties#getNotBefore() notBefore} values in {@code secret} are optional.
+ * If not specified, {@link SecretProperties#isEnabled() enabled} is set to true by key vault.
+ *
+ * Code sample
+ * Creates a new secret in the key vault. Prints out the details of the newly created secret returned in the
+ * response.
+ * {@codesnippet com.azure.security.keyvault.secretclient.setSecret#secret}
+ *
+ * @param secret The Secret object containing information about the secret and its properties. The properties
+ * {@link KeyVaultSecret#getName() secret.name} and {@link KeyVaultSecret#getValue() secret.value} cannot be
+ * null.
+ * @return The {@link KeyVaultSecret created secret}.
+ * @throws NullPointerException if {@code secret} is {@code null}.
+ * @throws ResourceModifiedException if {@code secret} is malformed.
+ * @throws HttpResponseException if {@link KeyVaultSecret#getName() name} or {@link KeyVaultSecret#getValue() value}
+ * is an empty string.
+ */
+ public KeyVaultSecret setSecret(KeyVaultSecret secret) {
+ return null;
+ }
+
+ /**
+ * Adds a secret to the key vault if it does not exist. If the named secret exists, a new version of the secret is
+ * created. This operation requires the {@code secrets/set} permission.
+ *
+ * Code sample
+ * Creates a new secret in the key vault. Prints out the details of the newly created secret returned in the
+ * response.
+ * {@codesnippet com.azure.security.keyvault.secretclient.setSecret#string-string}
+ *
+ * @param name The name of the secret. It is required and cannot be null.
+ * @param value The value of the secret. It is required and cannot be null.
+ * @return The {@link KeyVaultSecret created secret}.
+ * @throws ResourceModifiedException if invalid {@code name} or {@code value} is specified.
+ * @throws HttpResponseException if {@code name} or {@code value} is empty string.
+ */
+ public KeyVaultSecret setSecret(String name, String value) {
+ return null;
+ }
+
+ /**
+ * Gets the specified secret with specified version from the key vault. This operation requires the
+ * {@code secrets/get} permission.
+ *
+ * Code sample
+ * Gets a specific version of the secret in the key vault. Prints out the details of the returned secret.
+ * {@codesnippet com.azure.security.keyvault.secretclient.getSecret#string-string}
+ *
+ * @param name The name of the secret, cannot be null.
+ * @param version The version of the secret to retrieve. If this is an empty string or null, this call is
+ * equivalent to calling {@link #getSecret(String)}, with the latest version being retrieved.
+ * @return The requested {@link KeyVaultSecret secret}.
+ * @throws ResourceNotFoundException when a secret with {@code name} and {@code version} doesn't exist in the
+ * key vault.
+ * @throws HttpResponseException if {@code name} or {@code version} is empty string.
+ */
+ public KeyVaultSecret getSecret(String name, String version) {
+ return null;
+ }
+
+ /**
+ * Gets the latest version of the specified secret from the key vault.
+ * This operation requires the {@code secrets/get} permission.
+ *
+ * Code sample
+ * Gets the latest version of the secret in the key vault. Prints out the details of the returned secret.
+ * {@codesnippet com.azure.security.keyvault.secretclient.getSecret#string}
+ *
+ * @param name The name of the secret.
+ * @return The requested {@link KeyVaultSecret}.
+ * @throws ResourceNotFoundException when a secret with {@code name} doesn't exist in the key vault.
+ * @throws HttpResponseException if {@code name} is empty string.
+ */
+ public KeyVaultSecret getSecret(String name) {
+ return null;
+ }
+
+ /**
+ * Updates the attributes associated with the secret. The value of the secret in the key vault cannot be changed.
+ * Only attributes populated in {@code secretProperties} are changed. Attributes not specified in the request are
+ * not changed. This operation requires the {@code secrets/set} permission.
+ *
+ * The {@code secret} is required and its fields {@link SecretProperties#getName() name} and
+ * {@link SecretProperties#getVersion() version} cannot be null.
+ *
+ * Code sample
+ * Gets the latest version of the secret, changes its expiry time, and the updates the secret in the key
+ * vault.
+ * {@codesnippet com.azure.security.keyvault.secretclient.updateSecretProperties#secretProperties}
+ *
+ * @param secretProperties The {@link SecretProperties secret properties} object with updated properties.
+ * @return The {@link SecretProperties updated secret}.
+ * @throws NullPointerException if {@code secret} is {@code null}.
+ * @throws ResourceNotFoundException when a secret with {@link SecretProperties#getName() name} and {@link
+ * SecretProperties#getVersion() version} doesn't exist in the key vault.
+ * @throws HttpResponseException if {@link SecretProperties#getName() name} or {@link SecretProperties#getVersion() version} is
+ * empty string.
+ */
+ public SecretProperties updateSecretProperties(SecretProperties secretProperties) {
+ return null;
+ }
+
+ /**
+ * Requests a backup of the secret be downloaded to the client. All versions of the secret will be downloaded.
+ * This operation requires the {@code secrets/backup} permission.
+ *
+ * Code sample
+ * Backs up the secret from the key vault and prints out the length of the secret's backup byte array returned in
+ * the response
+ * {@codesnippet com.azure.security.keyvault.secretclient.backupSecret#string}
+ *
+ * @param name The name of the secret.
+ * @return A {@link Response} whose {@link Response#getValue() value} contains the backed up secret blob.
+ * @throws ResourceNotFoundException when a secret with {@code name} doesn't exist in the key vault.
+ * @throws HttpResponseException when a secret with {@code name} is empty string.
+ */
+ public byte[] backupSecret(String name) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClientBuilder.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClientBuilder.java
new file mode 100644
index 00000000000..0a90f44f8dd
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/SecretClientBuilder.java
@@ -0,0 +1,92 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.security.keyvault.secrets;
+
+import com.azure.core.credential.TokenCredential;
+
+/**
+ * This class provides a fluent builder API to help aid the configuration and instantiation of the {@link
+ * SecretAsyncClient secret async client} and {@link SecretClient secret client},
+ * by calling {@link SecretClientBuilder#buildAsyncClient() buildAsyncClient} and {@link
+ * SecretClientBuilder#buildClient() buildClient} respectively.
+ * It constructs an instance of the desired client.
+ *
+ * The minimal configuration options required by {@link SecretClientBuilder secretClientBuilder} to build
+ * {@link SecretAsyncClient} are {@link String vaultUrl} and {@link TokenCredential credential}.
+ *
+ * {@codesnippet com.azure.security.keyvault.secrets.async.secretclient.construct}
+ *
+ * Samples to construct the sync client
+ * {@codesnippet com.azure.security.keyvault.secretclient.sync.construct}
+ *
+ * The {@link HttpLogDetailLevel log detail level}, multiple custom {@link HttpLoggingPolicy policies} and custom
+ * {@link HttpClient http client} can be optionally configured in the {@link SecretClientBuilder}.
+ *
+ * {@codesnippet com.azure.security.keyvault.secrets.async.secretclient.withhttpclient.instantiation}
+ *
+ * Alternatively, custom {@link HttpPipeline http pipeline} with custom {@link HttpPipelinePolicy} policies and
+ * {@link String vaultUrl}
+ * can be specified. It provides finer control over the construction of {@link SecretAsyncClient client}
+ *
+ * {@codesnippet com.azure.security.keyvault.secrets.async.secretclient.pipeline.instantiation}
+ *
+ * @see SecretClient
+ * @see SecretAsyncClient
+ */
+public final class SecretClientBuilder {
+ /**
+ * The constructor with defaults.
+ */
+ public SecretClientBuilder() {
+ }
+
+ /**
+ * Creates a {@link SecretClient} based on options set in the builder.
+ * Every time {@code buildClient()} is called, a new instance of {@link SecretClient} is created.
+ *
+ * If {@link SecretClientBuilder#pipeline(HttpPipeline) pipeline} is set, then the {@code pipeline} and
+ * {@link SecretClientBuilder#vaultUrl(String) serviceEndpoint} are used to create the
+ * {@link SecretClientBuilder client}. All other builder settings are ignored. If {@code pipeline} is not set,
+ * then {@link SecretClientBuilder#credential(TokenCredential) key vault credential}, and
+ * {@link SecretClientBuilder#vaultUrl(String)} key vault url are required to build the {@link SecretClient
+ * client}.
+ *
+ * @return A {@link SecretClient} with the options set from the builder.
+ *
+ * @throws IllegalStateException If {@link SecretClientBuilder#credential(TokenCredential)} or
+ * {@link SecretClientBuilder#vaultUrl(String)} have not been set.
+ */
+ public SecretClient buildClient() {
+ return null;
+ }
+
+ /**
+ * Sets the vault URL to send HTTP requests to.
+ *
+ * @param vaultUrl The vault url is used as destination on Azure to send requests to. If you have a secret
+ * identifier, create a new {@link KeyVaultSecretIdentifier} to parse it and obtain the {@code vaultUrl} and
+ * other information.
+ *
+ * @return The updated {@link SecretClientBuilder} object.
+ *
+ * @throws IllegalArgumentException If {@code vaultUrl} is null or it cannot be parsed into a valid URL.
+ * @throws NullPointerException If {@code vaultUrl} is {@code null}.
+ */
+ public SecretClientBuilder vaultUrl(String vaultUrl) {
+ return null;
+ }
+
+ /**
+ * Sets the credential to use when authenticating HTTP requests.
+ *
+ * @param credential The credential to use for authenticating HTTP requests.
+ *
+ * @return The updated {@link SecretClientBuilder} object.
+ *
+ * @throws NullPointerException If {@code credential} is {@code null}.
+ */
+ public SecretClientBuilder credential(TokenCredential credential) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/KeyVaultSecret.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/KeyVaultSecret.java
new file mode 100644
index 00000000000..1f2f252a323
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/KeyVaultSecret.java
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.security.keyvault.secrets.models;
+
+import com.azure.security.keyvault.secrets.SecretClient;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Secret is the resource consisting of name, value and its attributes specified in {@link SecretProperties}.
+ * It is managed by Secret Service.
+ *
+ * @see SecretClient
+ * @see SecretAsyncClient
+ */
+public class KeyVaultSecret {
+ /**
+ * Creates an empty instance of the Secret.
+ */
+ KeyVaultSecret() {
+ }
+
+ /**
+ * Creates a Secret with {@code name} and {@code value}.
+ *
+ * @param name The name of the secret.
+ * @param value the value of the secret.
+ */
+ public KeyVaultSecret(String name, String value) {
+ }
+
+ /**
+ * Get the value of the secret.
+ *
+ * @return the secret value
+ */
+ public String getValue() {
+ return null;
+ }
+
+ /**
+ * Get the secret identifier.
+ *
+ * @return the secret identifier.
+ */
+ public String getId() {
+ return null;
+ }
+
+ /**
+ * Get the secret name.
+ *
+ * @return the secret name.
+ */
+ public String getName() {
+ return null;
+ }
+
+ /**
+ * Get the secret properties
+ * @return the Secret properties
+ */
+ public SecretProperties getProperties() {
+ return null;
+ }
+
+ /**
+ * Set the secret properties
+ * @param properties The Secret properties
+ * @throws NullPointerException if {@code properties} is null.
+ * @return the updated secret object
+ */
+ public KeyVaultSecret setProperties(SecretProperties properties) {
+ return null;
+ }
+}
diff --git a/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/SecretProperties.java b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/SecretProperties.java
new file mode 100644
index 00000000000..750db3ff177
--- /dev/null
+++ b/java/ql/test/stubs/azure-sdk-for-java/com/azure/security/keyvault/secrets/models/SecretProperties.java
@@ -0,0 +1,145 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.security.keyvault.secrets.models;
+
+import java.util.Map;
+
+import com.azure.security.keyvault.secrets.SecretClient;
+
+/**
+ * SecretProperties is the resource containing all the properties of the secret except its value.
+ * It is managed by the Secret Service.
+ *
+ * @see SecretClient
+ * @see SecretAsyncClient
+ */
+public class SecretProperties {
+ SecretProperties(String secretName) {
+ }
+
+ /**
+ * Creates empty instance of SecretProperties.
+ */
+ public SecretProperties() { }
+
+ /**
+ * Get the secret name.
+ *
+ * @return the name of the secret.
+ */
+ public String getName() {
+ return null;
+ }
+
+ /**
+ * Get the recovery level of the secret.
+
+ * @return the recoveryLevel of the secret.
+ */
+ public String getRecoveryLevel() {
+ return null;
+ }
+
+ /**
+ * Get the enabled value.
+ *
+ * @return the enabled value
+ */
+ public Boolean isEnabled() {
+ return false;
+ }
+
+ /**
+ * Set the enabled value.
+ *
+ * @param enabled The enabled value to set
+ * @throws NullPointerException if {@code enabled} is null.
+ * @return the SecretProperties object itself.
+ */
+ public SecretProperties setEnabled(Boolean enabled) {
+ return null;
+ }
+
+ /**
+ * Get the secret identifier.
+ *
+ * @return the secret identifier.
+ */
+ public String getId() {
+ return null;
+ }
+
+ /**
+ * Get the content type.
+ *
+ * @return the content type.
+ */
+ public String getContentType() {
+ return null;
+ }
+
+ /**
+ * Set the contentType.
+ *
+ * @param contentType The contentType to set
+ * @return the updated SecretProperties object itself.
+ */
+ public SecretProperties setContentType(String contentType) {
+ return null;
+ }
+
+ /**
+ * Get the tags associated with the secret.
+ *
+ * @return the value of the tags.
+ */
+ public Map getTags() {
+ return null;
+ }
+
+ /**
+ * Set the tags to be associated with the secret.
+ *
+ * @param tags The tags to set
+ * @return the updated SecretProperties object itself.
+ */
+ public SecretProperties setTags(Map tags) {
+ return null;
+ }
+
+ /**
+ * Get the keyId identifier.
+ *
+ * @return the keyId identifier.
+ */
+ public String getKeyId() {
+ return null;
+ }
+
+ /**
+ * Get the managed value.
+ *
+ * @return the managed value
+ */
+ public Boolean isManaged() {
+ return null;
+ }
+
+ /**
+ * Get the version of the secret.
+ *
+ * @return the version of the secret.
+ */
+ public String getVersion() {
+ return null;
+ }
+
+ /**
+ * Gets the number of days a secret is retained before being deleted for a soft delete-enabled Key Vault.
+ * @return the recoverable days.
+ */
+ public Integer getRecoverableDays() {
+ return null;
+ }
+}
From 4d014717b6a109d61ae3f9c803092f8df27ede07 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 12 May 2021 15:50:40 +0000
Subject: [PATCH 029/741] Add a change note and reset the qhelp file
---
...hardcoded-azure-credentials-in-api-call.md | 3 ++
.../CWE-798/HardcodedCredentialsApiCall.qhelp | 30 -------------------
2 files changed, 3 insertions(+), 30 deletions(-)
create mode 100644 java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md
diff --git a/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md b/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md
new file mode 100644
index 00000000000..48cbc7c4760
--- /dev/null
+++ b/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md
@@ -0,0 +1,3 @@
+lgtm,codescanning
+* The query "Hard-coded credential in API call" (`java/hardcoded-credential-api-call`)
+ now recognizes hard-coded authentication credentials with Azure SDK for Java.
diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp
index efcf6b15abf..231140a287e 100644
--- a/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp
+++ b/java/ql/src/Security/CWE/CWE-798/HardcodedCredentialsApiCall.qhelp
@@ -32,28 +32,6 @@
Instead, the user name and password could be supplied through environment variables,
which can be set externally without hard-coding credentials in the source code.
-
-
- The following code example connects to AWS using a hard-coded access key ID and secret key:
-
-
-
-
-
- Instead, the access key ID and secret key could be supplied through environment variables,
- which can be set externally without hard-coding credentials in the source code.
-
-
-
- The following code example connects to Azure using a hard-coded user name and password or client secret:
-
-
-
-
-
- Instead, the username and password or client secret could be supplied through environment variables,
- which can be set externally without hard-coding credentials in the source code.
-
@@ -61,14 +39,6 @@
OWASP:
Use of hard-coded password.
-
-Microsoft:
-Azure authentication with user credentials.
-
-
-Amazon:
-Working with AWS Credentials.
-
From 9ef58e378c14ec721d320f9e0e678207ce456d49 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 14 May 2021 11:01:25 +0000
Subject: [PATCH 030/741] Remove the sample Java file in the src folder
---
.../CWE-798/HardcodedAzureCredentials.java | 52 -------------------
1 file changed, 52 deletions(-)
delete mode 100644 java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java
diff --git a/java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java b/java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java
deleted file mode 100644
index a8b52aa43a1..00000000000
--- a/java/ql/src/Security/CWE/CWE-798/HardcodedAzureCredentials.java
+++ /dev/null
@@ -1,52 +0,0 @@
-public class HardcodedAzureCredentials {
- private final String clientId = "81734019-15a3-50t8-3253-5abe78abc3a1";
- private final String username = "username@example.onmicrosoft.com";
- private final String clientSecret = "1n1.qAc~3Q-1t38aF79Xzv5AUEfR5-ct3_";
- private final String tenantId = "22f367ce-535x-357w-2179-a33517mn166h";
-
- //BAD: hard-coded username/password credentials
- public void testHardcodedUsernamePassword(String input) {
- UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
- .clientId(clientId)
- .username(username)
- .password(clientSecret)
- .build();
-
- SecretClient client = new SecretClientBuilder()
- .vaultUrl("https://myKeyVault.vault.azure.net")
- .credential(usernamePasswordCredential)
- .buildClient();
- }
-
- //GOOD: username/password credentials stored as environment variables
- public void testEnvironmentUsernamePassword(String input) {
- UsernamePasswordCredential usernamePasswordCredential = new UsernamePasswordCredentialBuilder()
- .clientId(clientId)
- .username(System.getenv("myUsername"))
- .password(System.getenv("mySuperSecurePass"))
- .build();
-
- SecretClient client = new SecretClientBuilder()
- .vaultUrl("https://myKeyVault.vault.azure.net")
- .credential(usernamePasswordCredential)
- .buildClient();
- }
-
- //BAD: hard-coded client secret
- public void testHardcodedClientSecret(String input) {
- ClientSecretCredential defaultCredential = new ClientSecretCredentialBuilder()
- .clientId(clientId)
- .clientSecret(clientSecret)
- .tenantId(tenantId)
- .build();
- }
-
- //GOOD: client secret stored as environment variables
- public void testEnvironmentClientSecret(String input) {
- ClientSecretCredential defaultCredential = new ClientSecretCredentialBuilder()
- .clientId(clientId)
- .clientSecret(System.getenv("myClientSecret"))
- .tenantId(tenantId)
- .build();
- }
-}
\ No newline at end of file
From 1a072f3bb96281bd10f467697775836236fd3a5f Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 14 May 2021 20:38:23 +0000
Subject: [PATCH 031/741] Move APIs from predicates flagged auto-generated to
the other section
---
java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll b/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll
index 928a3562ec6..afbefb7b878 100644
--- a/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll
+++ b/java/ql/src/Security/CWE/CWE-798/SensitiveApi.qll
@@ -129,9 +129,7 @@ private predicate javaApiCallablePasswordParam(string s) {
s = "sun.tools.jconsole.ProxyClient;ProxyClient(String, int, String, String);3" or
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, int, String, String);3" or
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, String, String);2" or
- s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, int, String, String);3" or
- s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);1" or
- s = "com.azure.identity.UsernamePasswordCredentialBuilder;password(String);0"
+ s = "sun.tools.jconsole.ProxyClient;getCacheKey(String, int, String, String);3"
}
/**
@@ -202,9 +200,7 @@ private predicate javaApiCallableUsernameParam(string s) {
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, String, String);1" or
s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, String);1" or
s = "sun.tools.jconsole.ProxyClient;getProxyClient(String, int, String, String);2" or
- s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, int, String);2" or
- s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);0" or
- s = "com.azure.identity.UsernamePasswordCredentialBuilder;username(String);0"
+ s = "sun.tools.jconsole.ProxyClient;getConnectionName(String, int, String);2"
}
/**
@@ -513,6 +509,9 @@ private predicate otherApiCallableCredentialParam(string s) {
"org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection extends GrantedAuthority>);0" or
s =
"org.springframework.security.core.userdetails.User;User(String, String, boolean, boolean, boolean, boolean, Collection extends GrantedAuthority>);1" or
+ s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);0" or
+ s = "com.amazonaws.auth.BasicAWSCredentials;BasicAWSCredentials(String, String);1" or
+ s = "com.azure.identity.UsernamePasswordCredentialBuilder;username(String);0" or
+ s = "com.azure.identity.UsernamePasswordCredentialBuilder;password(String);0" or
s = "com.azure.identity.ClientSecretCredentialBuilder;clientSecret(String);0"
-
}
From 7af198434863fac4364bb9ad5e44896239ec44ad Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Mon, 17 May 2021 11:35:35 +0000
Subject: [PATCH 032/741] Update the change note
---
.../2021-05-12-hardcoded-azure-credentials-in-api-call.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md b/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md
index 48cbc7c4760..a5b55ba1b6d 100644
--- a/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md
+++ b/java/change-notes/2021-05-12-hardcoded-azure-credentials-in-api-call.md
@@ -1,3 +1,3 @@
lgtm,codescanning
* The query "Hard-coded credential in API call" (`java/hardcoded-credential-api-call`)
- now recognizes hard-coded authentication credentials with Azure SDK for Java.
+ now recognizes hard-coded authentication credentials passed to the Azure SDK for Java.
From 092fbd60d9b433dc005519c76945d5df8549244b Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Tue, 22 Jun 2021 15:31:17 +0200
Subject: [PATCH 033/741] 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 034/741] 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 035/741] 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 036/741] 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.
-
-
-
-
-
-
-
-
-MSDN Library: SQL Injection.
-
-
-
-
-
-
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 037/741] 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 038/741] 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 039/741] 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 040/741] 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 041/741] 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 042/741] 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 043/741] 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 044/741] 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 045/741] 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 046/741] 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 047/741] 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 048/741] 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 049/741] 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 050/741] 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 051/741] 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 052/741] 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 053/741] 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 054/741] 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 055/741] 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 9b6ae9029f51637c43d9cb283a99d937c9c908a6 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 13:09:18 +0200
Subject: [PATCH 056/741] Introduce query for capture JDK API usage
---
java/ql/src/Telemetry/APIUsage.qll | 20 ++++++++++++++++++++
java/ql/src/Telemetry/JDKUsage.ql | 15 +++++++++++++++
2 files changed, 35 insertions(+)
create mode 100644 java/ql/src/Telemetry/APIUsage.qll
create mode 100644 java/ql/src/Telemetry/JDKUsage.ql
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
new file mode 100644
index 00000000000..405e5d0ec17
--- /dev/null
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -0,0 +1,20 @@
+import java
+
+private string jarName(CompilationUnit cu) {
+ result = cu.getParentContainer().toString().regexpCapture(".*/(.*\\.jar)/?.*", 1)
+}
+
+predicate isJavaRuntime(Callable call) {
+ jarName(call.getCompilationUnit()) = "rt.jar" or
+ call.getCompilationUnit().getParentContainer().toString().substring(0, 14) = "/modules/java."
+}
+
+// TODO Is this heuristic too broad?
+predicate isInterestingAPI(Callable call) {
+ call.getNumberOfParameters() > 0 and
+ not (
+ call.getReturnType() instanceof VoidType or
+ call.getReturnType() instanceof PrimitiveType or
+ call.getReturnType() instanceof BoxedType
+ )
+}
diff --git a/java/ql/src/Telemetry/JDKUsage.ql b/java/ql/src/Telemetry/JDKUsage.ql
new file mode 100644
index 00000000000..b163db667be
--- /dev/null
+++ b/java/ql/src/Telemetry/JDKUsage.ql
@@ -0,0 +1,15 @@
+/**
+ * @name JDK API Usage
+ * @description A list of JDK APIs used in the source code.
+ * @id java/telemetry/jdk-apis
+ */
+
+import java
+import APIUsage
+
+from Callable call, CompilationUnit cu
+where
+ cu = call.getCompilationUnit() and
+ isJavaRuntime(call) and
+ isInterestingAPI(call)
+select cu, call as API, count(Call c | c.getCallee() = call) as calls order by calls desc
From 18e3763f9098391afb85e68b5f7c75b6dd186e4a Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 13:12:23 +0200
Subject: [PATCH 057/741] Expose whether APIs are already supported
---
java/ql/src/Telemetry/APIUsage.qll | 24 ++++++++++++++++++++++++
java/ql/src/Telemetry/JDKUsage.ql | 3 ++-
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index 405e5d0ec17..cde33411b63 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -1,4 +1,6 @@
import java
+private import semmle.code.java.dataflow.FlowSteps
+private import semmle.code.java.dataflow.ExternalFlow
private string jarName(CompilationUnit cu) {
result = cu.getParentContainer().toString().regexpCapture(".*/(.*\\.jar)/?.*", 1)
@@ -18,3 +20,25 @@ predicate isInterestingAPI(Callable call) {
call.getReturnType() instanceof BoxedType
)
}
+
+// TODO [bm] Fails to detect Collection flow yet (e.g. Map#put)
+string supportKind(Callable api) {
+ if api instanceof TaintPreservingCallable
+ then result = "taint-preserving"
+ else
+ if
+ summaryModel(api.getCompilationUnit().getPackage().toString(),
+ api.getDeclaringType().toString(), _, api.getName(), _, _, _, _, _)
+ then result = "summary"
+ else
+ if
+ sinkModel(api.getCompilationUnit().getPackage().toString(),
+ api.getDeclaringType().toString(), _, api.getName(), _, _, _, _)
+ then result = "sink"
+ else
+ if
+ sourceModel(api.getCompilationUnit().getPackage().toString(),
+ api.getDeclaringType().toString(), _, api.getName(), _, _, _, _)
+ then result = "source"
+ else result = "?"
+}
diff --git a/java/ql/src/Telemetry/JDKUsage.ql b/java/ql/src/Telemetry/JDKUsage.ql
index b163db667be..ecedc0a1d2a 100644
--- a/java/ql/src/Telemetry/JDKUsage.ql
+++ b/java/ql/src/Telemetry/JDKUsage.ql
@@ -12,4 +12,5 @@ where
cu = call.getCompilationUnit() and
isJavaRuntime(call) and
isInterestingAPI(call)
-select cu, call as API, count(Call c | c.getCallee() = call) as calls order by calls desc
+select cu, call as API, supportKind(call) as Kind, count(Call c | c.getCallee() = call) as calls
+ order by calls desc
From 32f52ac30d5eac2cbcaf30b566929337893f0e64 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 13:15:38 +0200
Subject: [PATCH 058/741] Improve column names
---
java/ql/src/Telemetry/JDKUsage.ql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/java/ql/src/Telemetry/JDKUsage.ql b/java/ql/src/Telemetry/JDKUsage.ql
index ecedc0a1d2a..7d07a1d2b70 100644
--- a/java/ql/src/Telemetry/JDKUsage.ql
+++ b/java/ql/src/Telemetry/JDKUsage.ql
@@ -12,5 +12,5 @@ where
cu = call.getCompilationUnit() and
isJavaRuntime(call) and
isInterestingAPI(call)
-select cu, call as API, supportKind(call) as Kind, count(Call c | c.getCallee() = call) as calls
- order by calls desc
+select cu as Class, call as API, supportKind(call) as Kind,
+ count(Call c | c.getCallee() = call) as Usages order by Usages desc
From b9f6b60c4d736ae3a0c68e4dd215c5528030c814 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 13:19:40 +0200
Subject: [PATCH 059/741] Introduce query to capture external libraries
---
java/ql/src/Telemetry/APIUsage.qll | 2 +-
java/ql/src/Telemetry/ExternalAPI.qll | 40 +++++++++++++++++++
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 16 ++++++++
3 files changed, 57 insertions(+), 1 deletion(-)
create mode 100644 java/ql/src/Telemetry/ExternalAPI.qll
create mode 100644 java/ql/src/Telemetry/ExternalLibraryUsage.ql
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index cde33411b63..816a18a0852 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -2,7 +2,7 @@ import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
-private string jarName(CompilationUnit cu) {
+string jarName(CompilationUnit cu) {
result = cu.getParentContainer().toString().regexpCapture(".*/(.*\\.jar)/?.*", 1)
}
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
new file mode 100644
index 00000000000..db83ea6f8e4
--- /dev/null
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -0,0 +1,40 @@
+import java
+import APIUsage
+private import experimental.semmle.code.java.Logging
+
+class ExternalAPI extends Callable {
+ ExternalAPI() {
+ not this.fromSource() and
+ not this.getDeclaringType().getPackage().getName().matches("java.%") and
+ not isJavaRuntime(this)
+ }
+
+ string jarName() { result = jarName(this.getCompilationUnit()) }
+
+ string simpleName() {
+ result = getDeclaringType().getSourceDeclaration() + "#" + this.getStringSignature()
+ }
+}
+
+// TODO [bm]: Shall we move this into LoggingCall or a LoggingSetup predicate?
+predicate loggingRelated(Call call) {
+ call instanceof LoggingCall or
+ call.getCallee().getName() = "getLogger" or // getLogger is not a LoggingCall
+ call.getCallee().getName() = "isDebugEnabled" // org.slf4j.Logger#isDebugEnabled is not a LoggingCall
+}
+
+class TestLibrary extends RefType {
+ TestLibrary() {
+ getPackage()
+ .getName()
+ .matches([
+ "org.junit%", "junit.%", "org.mockito%", "org.assertj%",
+ "com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%",
+ "org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%",
+ "org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%",
+ "org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions",
+ "org.openqa.selenium%", "com.gargoylesoftware.htmlunit%",
+ "org.jboss.arquillian.testng%", "org.testng%"
+ ])
+ }
+}
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
new file mode 100644
index 00000000000..b47091462a8
--- /dev/null
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -0,0 +1,16 @@
+/**
+ * @name External libraries
+ * @description A list of external libraries used in the code
+ * @id java/telemetry/external-libs
+ */
+
+import java
+import ExternalAPI
+
+from ExternalAPI api
+where not api.getDeclaringType() instanceof TestLibrary
+// TODO [bm]: the count is not aggregated and we have the same jar with multiple usages, e.g.
+// 1 protobuf-java-3.17.3.jar 373
+// 2 protobuf-java-3.17.3.jar 48
+select api.jarName() as jarname, count(Call c | c.getCallee() = api) as Usages
+order by Usages desc
\ No newline at end of file
From 07303ccbb3d155442456fea18dc9436575afd006 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 13:20:02 +0200
Subject: [PATCH 060/741] Fix formatting
---
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index b47091462a8..50ba5ae4e5b 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -12,5 +12,4 @@ where not api.getDeclaringType() instanceof TestLibrary
// TODO [bm]: the count is not aggregated and we have the same jar with multiple usages, e.g.
// 1 protobuf-java-3.17.3.jar 373
// 2 protobuf-java-3.17.3.jar 48
-select api.jarName() as jarname, count(Call c | c.getCallee() = api) as Usages
-order by Usages desc
\ No newline at end of file
+select api.jarName() as jarname, count(Call c | c.getCallee() = api) as Usages order by Usages desc
From d9285e78c0c8e309c7dc29ebd8dcb875bf9d8560 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 13:23:57 +0200
Subject: [PATCH 061/741] Add query to collect external API calls
---
java/ql/src/Telemetry/ExternalAPIUsages.ql | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
create mode 100644 java/ql/src/Telemetry/ExternalAPIUsages.ql
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
new file mode 100644
index 00000000000..78bdbe95d9d
--- /dev/null
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -0,0 +1,21 @@
+/**
+ * @name Usage of APIs coming from external libraries
+ * @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
+ * @id java/telemetry/external-libs
+ */
+
+import java
+import ExternalAPI
+import semmle.code.java.GeneratedFiles
+
+from ExternalAPI api
+where
+ not api.getDeclaringType() instanceof TestLibrary and
+ isInterestingAPI(api)
+select api.simpleName() as API,
+ count(Call c |
+ c.getCallee() = api and
+ not c.getFile() instanceof GeneratedFile and
+ not loggingRelated(c)
+ ) as Usages, supportKind(api) as Kind, api.getReturnType() as ReturnType,
+ api.getDeclaringType().getPackage() as Package order by Usages desc
From 722889e8818e9a1d212a259e8d704037e6914802 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 28 Jul 2021 15:13:17 +0200
Subject: [PATCH 062/741] Make id unique
---
java/ql/src/Telemetry/ExternalAPIUsages.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
index 78bdbe95d9d..fc6fce55383 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -1,7 +1,7 @@
/**
* @name Usage of APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
- * @id java/telemetry/external-libs
+ * @id java/telemetry/external-api
*/
import java
From 0c04c9a2c22d2c35a2c8111caa173d68ce3eca43 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 11:32:46 +0200
Subject: [PATCH 063/741] Fix aggregation of jar usages
---
java/ql/src/Telemetry/APIUsage.qll | 6 +-----
java/ql/src/Telemetry/ExternalAPI.qll | 2 --
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 18 ++++++++++++------
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index 816a18a0852..809fd6878fd 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -2,12 +2,8 @@ import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
-string jarName(CompilationUnit cu) {
- result = cu.getParentContainer().toString().regexpCapture(".*/(.*\\.jar)/?.*", 1)
-}
-
predicate isJavaRuntime(Callable call) {
- jarName(call.getCompilationUnit()) = "rt.jar" or
+ call.getCompilationUnit().getParentContainer*().getStem() = "rt" and
call.getCompilationUnit().getParentContainer().toString().substring(0, 14) = "/modules/java."
}
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index db83ea6f8e4..c5734c62785 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -9,8 +9,6 @@ class ExternalAPI extends Callable {
not isJavaRuntime(this)
}
- string jarName() { result = jarName(this.getCompilationUnit()) }
-
string simpleName() {
result = getDeclaringType().getSourceDeclaration() + "#" + this.getStringSignature()
}
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index 50ba5ae4e5b..39023cb8874 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -1,15 +1,21 @@
/**
* @name External libraries
* @description A list of external libraries used in the code
+ * @kind diagnostic
* @id java/telemetry/external-libs
*/
import java
import ExternalAPI
-from ExternalAPI api
-where not api.getDeclaringType() instanceof TestLibrary
-// TODO [bm]: the count is not aggregated and we have the same jar with multiple usages, e.g.
-// 1 protobuf-java-3.17.3.jar 373
-// 2 protobuf-java-3.17.3.jar 48
-select api.jarName() as jarname, count(Call c | c.getCallee() = api) as Usages order by Usages desc
+from int Usages, JarFile jar
+where
+ jar = any(ExternalAPI api).getCompilationUnit().getParentContainer*() and
+ Usages =
+ strictcount(Call c, ExternalAPI a |
+ c.getCallee() = a and
+ not c.getFile() instanceof GeneratedFile and
+ a.getCompilationUnit().getParentContainer*() = jar and
+ not a.getDeclaringType() instanceof TestLibrary
+ )
+select jar.getFile().getStem() + "." + jar.getFile().getExtension(), Usages order by Usages desc
From 2064915d3b2d8f81fe423ced2297adf695621544 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 11:52:01 +0200
Subject: [PATCH 064/741] Fold JDK API query into external API query
---
java/ql/src/Telemetry/APIUsage.qll | 5 -----
java/ql/src/Telemetry/ExternalAPI.qll | 11 +----------
java/ql/src/Telemetry/ExternalAPIUsages.ql | 3 +--
java/ql/src/Telemetry/JDKUsage.ql | 16 ----------------
4 files changed, 2 insertions(+), 33 deletions(-)
delete mode 100644 java/ql/src/Telemetry/JDKUsage.ql
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index 809fd6878fd..e7430fc540a 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -2,11 +2,6 @@ import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
-predicate isJavaRuntime(Callable call) {
- call.getCompilationUnit().getParentContainer*().getStem() = "rt" and
- call.getCompilationUnit().getParentContainer().toString().substring(0, 14) = "/modules/java."
-}
-
// TODO Is this heuristic too broad?
predicate isInterestingAPI(Callable call) {
call.getNumberOfParameters() > 0 and
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index c5734c62785..622ab2392ab 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -4,9 +4,7 @@ private import experimental.semmle.code.java.Logging
class ExternalAPI extends Callable {
ExternalAPI() {
- not this.fromSource() and
- not this.getDeclaringType().getPackage().getName().matches("java.%") and
- not isJavaRuntime(this)
+ not this.fromSource()
}
string simpleName() {
@@ -14,13 +12,6 @@ class ExternalAPI extends Callable {
}
}
-// TODO [bm]: Shall we move this into LoggingCall or a LoggingSetup predicate?
-predicate loggingRelated(Call call) {
- call instanceof LoggingCall or
- call.getCallee().getName() = "getLogger" or // getLogger is not a LoggingCall
- call.getCallee().getName() = "isDebugEnabled" // org.slf4j.Logger#isDebugEnabled is not a LoggingCall
-}
-
class TestLibrary extends RefType {
TestLibrary() {
getPackage()
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
index fc6fce55383..935bcd3197c 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -15,7 +15,6 @@ where
select api.simpleName() as API,
count(Call c |
c.getCallee() = api and
- not c.getFile() instanceof GeneratedFile and
- not loggingRelated(c)
+ not c.getFile() instanceof GeneratedFile
) as Usages, supportKind(api) as Kind, api.getReturnType() as ReturnType,
api.getDeclaringType().getPackage() as Package order by Usages desc
diff --git a/java/ql/src/Telemetry/JDKUsage.ql b/java/ql/src/Telemetry/JDKUsage.ql
deleted file mode 100644
index 7d07a1d2b70..00000000000
--- a/java/ql/src/Telemetry/JDKUsage.ql
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
- * @name JDK API Usage
- * @description A list of JDK APIs used in the source code.
- * @id java/telemetry/jdk-apis
- */
-
-import java
-import APIUsage
-
-from Callable call, CompilationUnit cu
-where
- cu = call.getCompilationUnit() and
- isJavaRuntime(call) and
- isInterestingAPI(call)
-select cu as Class, call as API, supportKind(call) as Kind,
- count(Call c | c.getCallee() = call) as Usages order by Usages desc
From aab633eced5748b6c76fa8eac355136542603b44 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 11:52:16 +0200
Subject: [PATCH 065/741] Reformat
---
java/ql/src/Telemetry/ExternalAPI.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index 622ab2392ab..b20b84e496e 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -3,9 +3,7 @@ import APIUsage
private import experimental.semmle.code.java.Logging
class ExternalAPI extends Callable {
- ExternalAPI() {
- not this.fromSource()
- }
+ ExternalAPI() { not this.fromSource() }
string simpleName() {
result = getDeclaringType().getSourceDeclaration() + "#" + this.getStringSignature()
From 33656342595b0a1533465f83471ecf6c17c89af6 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 14:42:45 +0200
Subject: [PATCH 066/741] Expose csv parameter format predicate
---
java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
index 9c8aaa6ce65..889a3901236 100644
--- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
@@ -614,9 +614,13 @@ private string paramsStringPart(Callable c, int i) {
i = 2 * c.getNumberOfParameters() and result = ")"
}
-private string paramsString(Callable c) {
- result = concat(int i | | paramsStringPart(c, i) order by i)
-}
+/**
+ * Gets a parenthesized string containing all parameter types of this callable, separated by a comma.
+ *
+ * Returns the empty string if the callable has no parameters.
+ * Parameter types are represented by their type erasure.
+ */
+string paramsString(Callable c) { result = concat(int i | | paramsStringPart(c, i) order by i) }
private Element interpretElement0(
string namespace, string type, boolean subtypes, string name, string signature
From 8595ae71f79797ee36bd108d0b544227cc61c346 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 15:45:30 +0200
Subject: [PATCH 067/741] Simplify api coverage detection
Fixes a bug that doesn't take super types into account
when computing the usage of a specific API.
---
java/ql/src/Telemetry/APIUsage.qll | 31 +++++++++++-------------------
1 file changed, 11 insertions(+), 20 deletions(-)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index e7430fc540a..e2d4a4db068 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -2,34 +2,25 @@ import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
-// TODO Is this heuristic too broad?
-predicate isInterestingAPI(Callable call) {
- call.getNumberOfParameters() > 0 and
- not (
- call.getReturnType() instanceof VoidType or
- call.getReturnType() instanceof PrimitiveType or
- call.getReturnType() instanceof BoxedType
- )
-}
-
-// TODO [bm] Fails to detect Collection flow yet (e.g. Map#put)
string supportKind(Callable api) {
if api instanceof TaintPreservingCallable
then result = "taint-preserving"
else
- if
- summaryModel(api.getCompilationUnit().getPackage().toString(),
- api.getDeclaringType().toString(), _, api.getName(), _, _, _, _, _)
+ if summaryModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _, _)
then result = "summary"
else
- if
- sinkModel(api.getCompilationUnit().getPackage().toString(),
- api.getDeclaringType().toString(), _, api.getName(), _, _, _, _)
+ if sinkModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
then result = "sink"
else
- if
- sourceModel(api.getCompilationUnit().getPackage().toString(),
- api.getDeclaringType().toString(), _, api.getName(), _, _, _, _)
+ if sourceModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
then result = "source"
else result = "?"
}
+
+private string packageName(Callable api) {
+ result = api.getCompilationUnit().getPackage().toString()
+}
+
+private string typeName(Callable api) {
+ result = api.getDeclaringType().getAnAncestor().getSourceDeclaration().toString()
+}
From fda394858bf72b8c607e4ddb50c61ac0da0a10bb Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 15:52:45 +0200
Subject: [PATCH 068/741] Turn external API query into diagnostics query
* Expose (partial) CSV model for the API
* Rework and simplify predicates
---
java/ql/src/Telemetry/ExternalAPI.qll | 25 ++++++++++++++-----
java/ql/src/Telemetry/ExternalAPIUsages.ql | 14 +++++++----
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 2 +-
3 files changed, 29 insertions(+), 12 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index b20b84e496e..c1787ee1f39 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -1,16 +1,29 @@
-import java
-import APIUsage
-private import experimental.semmle.code.java.Logging
+private import java
+private import APIUsage
+private import semmle.code.java.dataflow.ExternalFlow
class ExternalAPI extends Callable {
ExternalAPI() { not this.fromSource() }
- string simpleName() {
- result = getDeclaringType().getSourceDeclaration() + "#" + this.getStringSignature()
+ predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
+
+ predicate isInteresting() {
+ getNumberOfParameters() > 0 and
+ not (
+ getReturnType() instanceof VoidType or
+ getReturnType() instanceof PrimitiveType or
+ getReturnType() instanceof BoxedType
+ )
+ }
+
+ string asCSV(ExternalAPI api) {
+ result =
+ api.getDeclaringType().getPackage() + ";?;" + api.getDeclaringType().getSourceDeclaration() +
+ ";" + api.getName() + ";" + paramsString(api)
}
}
-class TestLibrary extends RefType {
+private class TestLibrary extends RefType {
TestLibrary() {
getPackage()
.getName()
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
index 935bcd3197c..a95116ddcd9 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -2,19 +2,23 @@
* @name Usage of APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
* @id java/telemetry/external-api
+ * @kind diagnostic
*/
import java
+import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
+// TODO [bm]: decide whether to drop the order by or
+// turn Usage into string for diagnostic kind
+// https://github.slack.com/archives/C01JJP3EF8E/p1627910071013000
from ExternalAPI api
where
- not api.getDeclaringType() instanceof TestLibrary and
- isInterestingAPI(api)
-select api.simpleName() as API,
+ not api.isTestLibrary() and
+ api.isInteresting()
+select api.asCSV(api) as csv,
count(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
- ) as Usages, supportKind(api) as Kind, api.getReturnType() as ReturnType,
- api.getDeclaringType().getPackage() as Package order by Usages desc
+ ) as Usages, supportKind(api) as Kind order by Usages desc
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index 39023cb8874..cbf060dec6c 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -16,6 +16,6 @@ where
c.getCallee() = a and
not c.getFile() instanceof GeneratedFile and
a.getCompilationUnit().getParentContainer*() = jar and
- not a.getDeclaringType() instanceof TestLibrary
+ not a.isTestLibrary()
)
select jar.getFile().getStem() + "." + jar.getFile().getExtension(), Usages order by Usages desc
From 60c70036676257a535442a802270759dfd91ce70 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 2 Aug 2021 16:13:48 +0200
Subject: [PATCH 069/741] Optimize return type check
---
java/ql/src/Telemetry/ExternalAPI.qll | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index c1787ee1f39..b665bbb7fec 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -9,10 +9,10 @@ class ExternalAPI extends Callable {
predicate isInteresting() {
getNumberOfParameters() > 0 and
- not (
- getReturnType() instanceof VoidType or
- getReturnType() instanceof PrimitiveType or
- getReturnType() instanceof BoxedType
+ exists(Type retType | retType = getReturnType() |
+ not retType instanceof VoidType and
+ not retType instanceof PrimitiveType and
+ not retType instanceof BoxedType
)
}
From e07516585a5faa60697951e23a3258da911e0501 Mon Sep 17 00:00:00 2001
From: Jordy Zomer
Date: Tue, 3 Aug 2021 19:08:47 +0200
Subject: [PATCH 070/741] 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 071/741] 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 072/741] 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 073/741] 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 074/741] 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 5b55a83aaa8161ca8c78e4981be6642d26a22428 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 10 Aug 2021 11:37:19 +0200
Subject: [PATCH 075/741] Use basename for jars
---
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index cbf060dec6c..fea32243771 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -18,4 +18,4 @@ where
a.getCompilationUnit().getParentContainer*() = jar and
not a.isTestLibrary()
)
-select jar.getFile().getStem() + "." + jar.getFile().getExtension(), Usages order by Usages desc
+select jar.getFile().getBaseName(), Usages order by Usages desc
From c48586ff8035a1ba803036537f0c20eb28fb6991 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 10 Aug 2021 11:38:01 +0200
Subject: [PATCH 076/741] Implement coverage tracking using dataflow nodes
---
java/ql/src/Telemetry/APIUsage.qll | 36 ++++++++++++++++++++++++------
1 file changed, 29 insertions(+), 7 deletions(-)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index e2d4a4db068..478c99958c7 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -1,26 +1,48 @@
import java
private import semmle.code.java.dataflow.FlowSteps
private import semmle.code.java.dataflow.ExternalFlow
+private import semmle.code.java.dataflow.FlowSummary
+private import semmle.code.java.dataflow.DataFlow
+private import semmle.code.java.dataflow.TaintTracking
+private import semmle.code.java.dataflow.FlowSources
string supportKind(Callable api) {
if api instanceof TaintPreservingCallable
then result = "taint-preserving"
else
- if summaryModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _, _)
+ if summaryCall(api)
then result = "summary"
else
- if sinkModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
+ if sink(api)
then result = "sink"
else
- if sourceModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
+ if source(api)
then result = "source"
else result = "?"
}
-private string packageName(Callable api) {
- result = api.getCompilationUnit().getPackage().toString()
+predicate summaryCall(Callable api) {
+ api instanceof SummarizedCallable
+ or
+ exists(Call call, DataFlow::Node arg |
+ call.getCallee() = api and
+ [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
+ TaintTracking::localAdditionalTaintStep(arg, _)
+ )
}
-private string typeName(Callable api) {
- result = api.getDeclaringType().getAnAncestor().getSourceDeclaration().toString()
+predicate sink(Callable api) {
+ exists(Call call, DataFlow::Node arg |
+ call.getCallee() = api and
+ [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
+ sinkNode(arg, _)
+ )
+}
+
+predicate source(Callable api) {
+ exists(Call call, DataFlow::Node arg |
+ call.getCallee() = api and
+ [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
+ arg instanceof RemoteFlowSource
+ )
}
From 26d426907116636039df76eb1b2620e843a0fb51 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 10 Aug 2021 12:02:56 +0200
Subject: [PATCH 077/741] Use FlowSources for coverage tracking
---
java/ql/src/Telemetry/APIUsage.qll | 33 ++++++++++--------------------
1 file changed, 11 insertions(+), 22 deletions(-)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index 478c99958c7..9c9670a1b16 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -1,9 +1,4 @@
import java
-private import semmle.code.java.dataflow.FlowSteps
-private import semmle.code.java.dataflow.ExternalFlow
-private import semmle.code.java.dataflow.FlowSummary
-private import semmle.code.java.dataflow.DataFlow
-private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.dataflow.FlowSources
string supportKind(Callable api) {
@@ -22,27 +17,21 @@ string supportKind(Callable api) {
}
predicate summaryCall(Callable api) {
- api instanceof SummarizedCallable
- or
- exists(Call call, DataFlow::Node arg |
- call.getCallee() = api and
- [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
- TaintTracking::localAdditionalTaintStep(arg, _)
- )
+ summaryModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _, _)
}
predicate sink(Callable api) {
- exists(Call call, DataFlow::Node arg |
- call.getCallee() = api and
- [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
- sinkNode(arg, _)
- )
+ sinkModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
}
predicate source(Callable api) {
- exists(Call call, DataFlow::Node arg |
- call.getCallee() = api and
- [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
- arg instanceof RemoteFlowSource
- )
+ sourceModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
+}
+
+private string packageName(Callable api) {
+ result = api.getCompilationUnit().getPackage().toString()
+}
+
+private string typeName(Callable api) {
+ result = api.getDeclaringType().getAnAncestor().getSourceDeclaration().toString()
}
From 8127f63b1eed60c25d97154d72a8183c4c0b0d4f Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 10 Aug 2021 12:05:16 +0200
Subject: [PATCH 078/741] Only include APIs without support
---
java/ql/src/Telemetry/ExternalAPI.qll | 2 ++
java/ql/src/Telemetry/ExternalAPIUsages.ql | 9 ++++-----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index b665bbb7fec..7f78e63a013 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -21,6 +21,8 @@ class ExternalAPI extends Callable {
api.getDeclaringType().getPackage() + ";?;" + api.getDeclaringType().getSourceDeclaration() +
";" + api.getName() + ";" + paramsString(api)
}
+
+ predicate isSupported() { not supportKind(this) = "?" }
}
private class TestLibrary extends RefType {
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
index a95116ddcd9..e1a38cf549a 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -2,7 +2,8 @@
* @name Usage of APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
* @id java/telemetry/external-api
- * @kind diagnostic
+ * @kind metric
+ * @metricType callable
*/
import java
@@ -10,15 +11,13 @@ import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
-// TODO [bm]: decide whether to drop the order by or
-// turn Usage into string for diagnostic kind
-// https://github.slack.com/archives/C01JJP3EF8E/p1627910071013000
from ExternalAPI api
where
not api.isTestLibrary() and
+ not api.isSupported() and
api.isInteresting()
select api.asCSV(api) as csv,
count(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
- ) as Usages, supportKind(api) as Kind order by Usages desc
+ ) as Usages order by Usages desc
From ec7f4d18e187c92f881d7ffa571342bfa38fbfa5 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 11 Aug 2021 15:31:33 +0200
Subject: [PATCH 079/741] Avoid duplicates and support modular runtime
---
java/ql/src/Telemetry/ExternalAPI.qll | 8 ++++++++
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 11 ++++++-----
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index 7f78e63a013..af36d974557 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -23,6 +23,14 @@ class ExternalAPI extends Callable {
}
predicate isSupported() { not supportKind(this) = "?" }
+
+ string jarContainer() {
+ result = containerAsJar(any(ExternalAPI api).getCompilationUnit().getParentContainer*())
+ }
+
+ private string containerAsJar(Container container) {
+ if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
+ }
}
private class TestLibrary extends RefType {
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index fea32243771..58af3d8c8af 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -1,21 +1,22 @@
/**
* @name External libraries
* @description A list of external libraries used in the code
- * @kind diagnostic
+ * @kind metric
+ * @metricType callable
* @id java/telemetry/external-libs
*/
import java
import ExternalAPI
-from int Usages, JarFile jar
+from int Usages, string jarname
where
- jar = any(ExternalAPI api).getCompilationUnit().getParentContainer*() and
+ jarname = any(ExternalAPI api).jarContainer() and
Usages =
strictcount(Call c, ExternalAPI a |
c.getCallee() = a and
not c.getFile() instanceof GeneratedFile and
- a.getCompilationUnit().getParentContainer*() = jar and
+ a.jarContainer() = jarname and
not a.isTestLibrary()
)
-select jar.getFile().getBaseName(), Usages order by Usages desc
+select jarname, Usages order by Usages desc
From 6287e6d8e976f85fb4fa1edc6050c9b20107d91c Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 11 Aug 2021 15:31:56 +0200
Subject: [PATCH 080/741] Filter unused API callsites
---
java/ql/src/Telemetry/ExternalAPIUsages.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
index e1a38cf549a..24b7bc2a355 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -17,7 +17,7 @@ where
not api.isSupported() and
api.isInteresting()
select api.asCSV(api) as csv,
- count(Call c |
+ strictcount(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
) as Usages order by Usages desc
From 26ffe6c03d905c3073fe6886da20fa633a6178e3 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 11 Aug 2021 15:32:09 +0200
Subject: [PATCH 081/741] Add tests for telemetry queries
---
.../ExternalAPIUsage.expected | 1 +
.../ExternalAPIUsage/ExternalAPIUsage.qlref | 1 +
.../Telemetry/ExternalAPIUsage/Test.java | 21 ++++++++++++++++++
.../ExternalLibraryUsage.class | Bin 0 -> 453 bytes
.../ExternalLibraryUsage.expected | 1 +
.../ExternalLibraryUsage.qlref | 1 +
.../Telemetry/ExternalLibraryUsage/Test.java | 9 ++++++++
7 files changed, 34 insertions(+)
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalAPIUsage/Test.java
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.class
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.qlref
create mode 100644 java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/Test.java
diff --git a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected
new file mode 100644
index 00000000000..4ca6e36b95d
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected
@@ -0,0 +1 @@
+| java.time;?;Duration;ofMillis;(long) | 1 |
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref
new file mode 100644
index 00000000000..db2c0b168b0
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref
@@ -0,0 +1 @@
+Telemetry/ExternalAPIUsages.ql
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/Test.java b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/Test.java
new file mode 100644
index 00000000000..c2497894e1c
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/Test.java
@@ -0,0 +1,21 @@
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class ExternalApiUsage {
+ public static void main(String[] args) {
+ List> foo = new ArrayList(); // already supported
+ Map map = new HashMap<>();
+ map.put("foo", new Object());
+
+ Duration d = java.time.Duration.ofMillis(1000); // not supported
+
+ long l = "foo".length(); // not interesting
+
+ System.out.println(d);
+ System.out.println(map);
+ System.out.println(foo);
+ }
+}
diff --git a/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.class b/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.class
new file mode 100644
index 0000000000000000000000000000000000000000..aedd54176ac237196ddc031fa73d2c67fa610e99
GIT binary patch
literal 453
zcmY*VTT8<*7(MB_b*pu&?&5Xg<5aM|`7jYg^r@(@DTq($3?ri#NmFq@OCKC4_yhb=
z;@1tC3ncmS<(!;z^8NGi1>hJvHY~UnR%{q>D_BL{MgvU?EemT5r4t!TeacYi?AR*iX!XXa77_VRGP-Ea+{whdA#0ip3LbU;3@o-8tfw3lQm$s_U#yu5#lkL1^nLj-
zXi^kVQmWdtGun&$6R=k*7?x-)O`9gc94u3vLyw4>lig1k_Y)NSH<$ foo = new ArrayList(); // Java Runtime
+ System.out.println(foo);
+ }
+}
From 8aba0b04bce7f26b4b1cd5c45c510cb3d0d6cff8 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 11 Aug 2021 16:07:24 +0200
Subject: [PATCH 082/741] Add QLDoc for all shared libraries
---
java/ql/src/Telemetry/APIUsage.qll | 11 ++++++++---
java/ql/src/Telemetry/ExternalAPI.qll | 14 ++++++++++++++
2 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index 9c9670a1b16..424d6cb1852 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -1,6 +1,11 @@
+/** Provides classes and predicates related to support coverage of external libraries. */
+
import java
private import semmle.code.java.dataflow.FlowSources
+/**
+ * Gets the coverage support for the given `Callable`. If the `Callable` is not supported, returns "?".
+ */
string supportKind(Callable api) {
if api instanceof TaintPreservingCallable
then result = "taint-preserving"
@@ -16,15 +21,15 @@ string supportKind(Callable api) {
else result = "?"
}
-predicate summaryCall(Callable api) {
+private predicate summaryCall(Callable api) {
summaryModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _, _)
}
-predicate sink(Callable api) {
+private predicate sink(Callable api) {
sinkModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
}
-predicate source(Callable api) {
+private predicate source(Callable api) {
sourceModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
}
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index af36d974557..e90679c4dc1 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -1,12 +1,19 @@
+/** Provides classes and predicates related to handling APIs from external libraries. */
+
private import java
private import APIUsage
private import semmle.code.java.dataflow.ExternalFlow
+/**
+ * An external API from either the Java Standard Library or a 3rd party library.
+ */
class ExternalAPI extends Callable {
ExternalAPI() { not this.fromSource() }
+ /** Holds true if this API is part of a common testing library or framework */
predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
+ /** Holds true if this API has inputs or outputs that are interesting to support by CodeQL. */
predicate isInteresting() {
getNumberOfParameters() > 0 and
exists(Type retType | retType = getReturnType() |
@@ -16,14 +23,21 @@ class ExternalAPI extends Callable {
)
}
+ /**
+ * Gets information about the external API in the form expected by the CSV modeling framework.
+ */
string asCSV(ExternalAPI api) {
result =
api.getDeclaringType().getPackage() + ";?;" + api.getDeclaringType().getSourceDeclaration() +
";" + api.getName() + ";" + paramsString(api)
}
+ /** Holds true if this API is not yet supported by existing CodeQL libraries */
predicate isSupported() { not supportKind(this) = "?" }
+ /**
+ * Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
+ */
string jarContainer() {
result = containerAsJar(any(ExternalAPI api).getCompilationUnit().getParentContainer*())
}
From 89f4a35273db3f94bc30cd0a395ce34d6be91f84 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 15:40:53 +0200
Subject: [PATCH 083/741] Remove filter to see all unsupported APIs
---
java/ql/src/Telemetry/ExternalAPI.qll | 10 ----------
java/ql/src/Telemetry/ExternalAPIUsages.ql | 3 +--
2 files changed, 1 insertion(+), 12 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index e90679c4dc1..bfdb3a6df4f 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -13,16 +13,6 @@ class ExternalAPI extends Callable {
/** Holds true if this API is part of a common testing library or framework */
predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
- /** Holds true if this API has inputs or outputs that are interesting to support by CodeQL. */
- predicate isInteresting() {
- getNumberOfParameters() > 0 and
- exists(Type retType | retType = getReturnType() |
- not retType instanceof VoidType and
- not retType instanceof PrimitiveType and
- not retType instanceof BoxedType
- )
- }
-
/**
* Gets information about the external API in the form expected by the CSV modeling framework.
*/
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/ExternalAPIUsages.ql
index 24b7bc2a355..4f5fb53d759 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/ExternalAPIUsages.ql
@@ -14,8 +14,7 @@ import semmle.code.java.GeneratedFiles
from ExternalAPI api
where
not api.isTestLibrary() and
- not api.isSupported() and
- api.isInteresting()
+ not api.isSupported()
select api.asCSV(api) as csv,
strictcount(Call c |
c.getCallee() = api and
From 87ef540b52f309251f731f9941127cc764162505 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 16:38:32 +0200
Subject: [PATCH 084/741] Split out queries showing supported APIs
---
java/ql/src/Telemetry/APIUsage.qll | 6 +++++
.../src/Telemetry/SupportedExternalSinks.ql | 22 +++++++++++++++++++
.../src/Telemetry/SupportedExternalSources.ql | 22 +++++++++++++++++++
.../src/Telemetry/SupportedExternalTaint.ql | 22 +++++++++++++++++++
...PIUsages.ql => UnsupportedExternalAPIs.ql} | 2 +-
5 files changed, 73 insertions(+), 1 deletion(-)
create mode 100644 java/ql/src/Telemetry/SupportedExternalSinks.ql
create mode 100644 java/ql/src/Telemetry/SupportedExternalSources.ql
create mode 100644 java/ql/src/Telemetry/SupportedExternalTaint.ql
rename java/ql/src/Telemetry/{ExternalAPIUsages.ql => UnsupportedExternalAPIs.ql} (91%)
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
index 424d6cb1852..23d7a6ad49f 100644
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ b/java/ql/src/Telemetry/APIUsage.qll
@@ -31,6 +31,12 @@ private predicate sink(Callable api) {
private predicate source(Callable api) {
sourceModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
+ or
+ exists(Call call, DataFlow::Node arg |
+ call.getCallee() = api and
+ [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
+ arg instanceof RemoteFlowSource
+ )
}
private string packageName(Callable api) {
diff --git a/java/ql/src/Telemetry/SupportedExternalSinks.ql b/java/ql/src/Telemetry/SupportedExternalSinks.ql
new file mode 100644
index 00000000000..adebe3f4533
--- /dev/null
+++ b/java/ql/src/Telemetry/SupportedExternalSinks.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Supported sinks in external libraries
+ * @description A list of 3rd party APIs detected as sinks. Excludes test and generated code.
+ * @id java/telemetry/supported-external-api-sinks
+ * @kind metric
+ * @metricType callable
+ */
+
+import java
+import APIUsage
+import ExternalAPI
+import semmle.code.java.GeneratedFiles
+
+from ExternalAPI api
+where
+ not api.isTestLibrary() and
+ supportKind(api) = "sink"
+select api.asCSV(api) as csv,
+ strictcount(Call c |
+ c.getCallee() = api and
+ not c.getFile() instanceof GeneratedFile
+ ) as Usages order by Usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalSources.ql b/java/ql/src/Telemetry/SupportedExternalSources.ql
new file mode 100644
index 00000000000..d8fd21b8440
--- /dev/null
+++ b/java/ql/src/Telemetry/SupportedExternalSources.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Supported sources in external libraries
+ * @description A list of 3rd party APIs detected as sources. Excludes test and generated code.
+ * @id java/telemetry/supported-external-api-sources
+ * @kind metric
+ * @metricType callable
+ */
+
+import java
+import APIUsage
+import ExternalAPI
+import semmle.code.java.GeneratedFiles
+
+from ExternalAPI api
+where
+ not api.isTestLibrary() and
+ supportKind(api) = "source"
+select api.asCSV(api) as csv,
+ strictcount(Call c |
+ c.getCallee() = api and
+ not c.getFile() instanceof GeneratedFile
+ ) as Usages order by Usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalTaint.ql b/java/ql/src/Telemetry/SupportedExternalTaint.ql
new file mode 100644
index 00000000000..a9da59823ad
--- /dev/null
+++ b/java/ql/src/Telemetry/SupportedExternalTaint.ql
@@ -0,0 +1,22 @@
+/**
+ * @name Supported sinks in external libraries
+ * @description A list of 3rd party APIs detected as sinks. Excludes test and generated code.
+ * @id java/telemetry/supported-external-api-taint
+ * @kind metric
+ * @metricType callable
+ */
+
+import java
+import APIUsage
+import ExternalAPI
+import semmle.code.java.GeneratedFiles
+
+from ExternalAPI api
+where
+ not api.isTestLibrary() and
+ supportKind(api) = ["summary", "taint-preserving"]
+select api.asCSV(api) as csv,
+ strictcount(Call c |
+ c.getCallee() = api and
+ not c.getFile() instanceof GeneratedFile
+ ) as Usages order by Usages desc
diff --git a/java/ql/src/Telemetry/ExternalAPIUsages.ql b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
similarity index 91%
rename from java/ql/src/Telemetry/ExternalAPIUsages.ql
rename to java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
index 4f5fb53d759..2af53c5b3e4 100644
--- a/java/ql/src/Telemetry/ExternalAPIUsages.ql
+++ b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
@@ -1,7 +1,7 @@
/**
* @name Usage of APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
- * @id java/telemetry/external-api
+ * @id java/telemetry/unsupported-external-api
* @kind metric
* @metricType callable
*/
From 1d3bcdf522186bbf40cc863e000c84578076f804 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 21:55:00 +0200
Subject: [PATCH 085/741] Align tests with new query structure
---
.../ExternalAPIUsage/ExternalAPIUsage.expected | 1 -
.../Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref | 1 -
.../ExternalLibraryUsage.expected | 2 +-
.../SupportedExternalSinks.expected | 2 ++
.../SupportedExternalSinks.java | 11 +++++++++++
.../SupportedExternalSinks.qlref | 1 +
.../SupportedExternalSources.expected | 1 +
.../SupportedExternalSources.java | 9 +++++++++
.../SupportedExternalSources.qlref | 1 +
.../SupportedExternalTaint.expected | 2 ++
.../SupportedExternalTaint.java | 7 +++++++
.../SupportedExternalTaint.qlref | 1 +
.../Test.java | 0
.../UnsupportedExternalAPIs.expected | 6 ++++++
.../UnsupportedExternalAPIs.qlref | 1 +
15 files changed, 43 insertions(+), 3 deletions(-)
delete mode 100644 java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected
delete mode 100644 java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.java
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.qlref
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.java
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.qlref
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.java
create mode 100644 java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.qlref
rename java/ql/test/query-tests/Telemetry/{ExternalAPIUsage => UnsupportedExternalAPIs}/Test.java (100%)
create mode 100644 java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
create mode 100644 java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.qlref
diff --git a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected
deleted file mode 100644
index 4ca6e36b95d..00000000000
--- a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.expected
+++ /dev/null
@@ -1 +0,0 @@
-| java.time;?;Duration;ofMillis;(long) | 1 |
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref b/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref
deleted file mode 100644
index db2c0b168b0..00000000000
--- a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/ExternalAPIUsage.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Telemetry/ExternalAPIUsages.ql
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected b/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
index d5f8d4b7002..dbba1054b8c 100644
--- a/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
+++ b/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
@@ -1 +1 @@
-rt.jar | 3 |
\ No newline at end of file
+| rt.jar | 3 |
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
new file mode 100644
index 00000000000..3d595aa5353
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
@@ -0,0 +1,2 @@
+| java.io;?;FileWriter;FileWriter;(File) | 1 |
+| java.net;?;URL;openStream;() | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.java b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.java
new file mode 100644
index 00000000000..affaf5314c6
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.java
@@ -0,0 +1,11 @@
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.URL;
+
+class SupportedExternalSinks {
+ public static void main(String[] args) throws Exception {
+ new FileWriter(new File("foo"));
+ new URL("http://foo").openStream();
+ }
+}
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.qlref b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.qlref
new file mode 100644
index 00000000000..e8bd57fad50
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.qlref
@@ -0,0 +1 @@
+Telemetry/SupportedExternalSinks.ql
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected
new file mode 100644
index 00000000000..26d2efed5e5
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected
@@ -0,0 +1 @@
+| java.net;?;URLConnection;getInputStream;() | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.java b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.java
new file mode 100644
index 00000000000..a9640bce62d
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.java
@@ -0,0 +1,9 @@
+import java.io.InputStream;
+import java.net.URL;
+
+class SupportedExternalSources {
+ public static void main(String[] args) throws Exception {
+ URL github = new URL("https://www.github.com/");
+ InputStream stream = github.openConnection().getInputStream();
+ }
+}
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.qlref b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.qlref
new file mode 100644
index 00000000000..06b22c87004
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.qlref
@@ -0,0 +1 @@
+Telemetry/SupportedExternalSources.ql
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected
new file mode 100644
index 00000000000..18be8556005
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected
@@ -0,0 +1,2 @@
+| java.lang;?;StringBuilder;append;(String) | 1 |
+| java.lang;?;StringBuilder;toString;() | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.java b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.java
new file mode 100644
index 00000000000..ed9952ab0a1
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.java
@@ -0,0 +1,7 @@
+class SupportedExternalTaint {
+ public static void main(String[] args) throws Exception {
+ StringBuilder builder = new StringBuilder();
+ builder.append("foo");
+ builder.toString();
+ }
+}
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.qlref b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.qlref
new file mode 100644
index 00000000000..1a32b35f60d
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.qlref
@@ -0,0 +1 @@
+Telemetry/SupportedExternalTaint.ql
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/ExternalAPIUsage/Test.java b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/Test.java
similarity index 100%
rename from java/ql/test/query-tests/Telemetry/ExternalAPIUsage/Test.java
rename to java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/Test.java
diff --git a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
new file mode 100644
index 00000000000..22cac276be6
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
@@ -0,0 +1,6 @@
+| java.io;?;PrintStream;println;(Object) | 3 |
+| java.lang;?;Object;Object;() | 2 |
+| java.lang;?;String;length;() | 1 |
+| java.time;?;Duration;ofMillis;(long) | 1 |
+| java.util;?;ArrayList;ArrayList<>;() | 1 |
+| java.util;?;HashMap;HashMap;() | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.qlref b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.qlref
new file mode 100644
index 00000000000..1d25dceeec3
--- /dev/null
+++ b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.qlref
@@ -0,0 +1 @@
+Telemetry/UnsupportedExternalAPIs.ql
\ No newline at end of file
From 035f7b57e918aac1315ffdc7581b2b797aad27a0 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 17 Aug 2021 16:25:49 +0200
Subject: [PATCH 086/741] Improve query name
---
java/ql/src/Telemetry/UnsupportedExternalAPIs.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
index 2af53c5b3e4..4a97e139a81 100644
--- a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
+++ b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
@@ -1,5 +1,5 @@
/**
- * @name Usage of APIs coming from external libraries
+ * @name Usage of unsupported APIs coming from external libraries
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
* @id java/telemetry/unsupported-external-api
* @kind metric
From 99e19e6d593566156fc7b98444c2cfcf98297f4c Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 17 Aug 2021 16:26:08 +0200
Subject: [PATCH 087/741] Fix predicate to only match the current API
---
java/ql/src/Telemetry/ExternalAPI.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index bfdb3a6df4f..89bf49c2d07 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -28,9 +28,7 @@ class ExternalAPI extends Callable {
/**
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
*/
- string jarContainer() {
- result = containerAsJar(any(ExternalAPI api).getCompilationUnit().getParentContainer*())
- }
+ string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) }
private string containerAsJar(Container container) {
if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
From babec9bf7972558cf7fee278d152e06d624eea35 Mon Sep 17 00:00:00 2001
From: james
Date: Wed, 18 Aug 2021 11:26:51 +0100
Subject: [PATCH 088/741] 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 089/741] 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 090/741] 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 091/741] 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 092/741] 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 093/741] 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 094/741] 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 095/741] 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 096/741] 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 097/741] 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 098/741] 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 099/741] 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 100/741] 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 101/741] 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 102/741] 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 103/741] 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 104/741] 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 105/741] 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 106/741] 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 285c659541158bda3449f774482adff7b1d83954 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 25 Aug 2021 12:54:15 +0200
Subject: [PATCH 107/741] add `src` as a potential unsafe DOM property name for
`js/xss-through-dom`
---
.../security/dataflow/XssThroughDomCustomizations.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll b/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll
index efedee1477d..d9222337d34 100644
--- a/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll
+++ b/javascript/ql/lib/semmle/javascript/security/dataflow/XssThroughDomCustomizations.qll
@@ -30,7 +30,7 @@ module XssThroughDom {
/**
* Gets a DOM property name that could store user-controlled data.
*/
- string unsafeDomPropertyName() { result = ["innerText", "textContent", "value", "name"] }
+ string unsafeDomPropertyName() { result = ["innerText", "textContent", "value", "name", "src"] }
/**
* A source for text from the DOM from a JQuery method call.
From 1b6e1dbd13f7b0a0e5c5c27398c94c97cbcde218 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 25 Aug 2021 12:54:42 +0200
Subject: [PATCH 108/741] include property writes in super-classes when reading
a property in a sub-class
---
javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
index c9061b5c5b9..b5d1290a516 100644
--- a/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
+++ b/javascript/ql/lib/semmle/javascript/dataflow/DataFlow.qll
@@ -1591,7 +1591,7 @@ module DataFlow {
*/
predicate localFieldStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(ClassNode cls, string prop |
- pred = cls.getAReceiverNode().getAPropertyWrite(prop).getRhs() or
+ pred = cls.getADirectSuperClass*().getAReceiverNode().getAPropertyWrite(prop).getRhs() or
pred = cls.getInstanceMethod(prop)
|
succ = cls.getAReceiverNode().getAPropertyRead(prop)
From 81742528a2c5167500a7529937cf6b4f53def70b Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 25 Aug 2021 12:55:06 +0200
Subject: [PATCH 109/741] add test
---
.../CWE-079/XssThroughDom/XssThroughDom.expected | 9 +++++++++
.../CWE-079/XssThroughDom/xss-through-dom.js | 15 ++++++++++++++-
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected
index 5d088481711..032446f4935 100644
--- a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/XssThroughDom.expected
@@ -118,6 +118,10 @@ nodes
| xss-through-dom.js:96:17:96:47 | $("#foo ... ].value |
| xss-through-dom.js:96:17:96:47 | $("#foo ... ].value |
| xss-through-dom.js:96:17:96:47 | $("#foo ... ].value |
+| xss-through-dom.js:109:31:109:70 | "" |
+| xss-through-dom.js:109:31:109:70 | "" |
+| xss-through-dom.js:109:45:109:55 | this.el.src |
+| xss-through-dom.js:109:45:109:55 | this.el.src |
edges
| forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values |
| forms.js:8:23:8:28 | values | forms.js:9:31:9:36 | values |
@@ -186,6 +190,10 @@ edges
| xss-through-dom.js:87:36:87:39 | text | xss-through-dom.js:87:16:87:40 | new ans ... s(text) |
| xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value |
| xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value |
+| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "" |
+| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "" |
+| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "" |
+| xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "" |
#select
| forms.js:9:31:9:40 | values.foo | forms.js:8:23:8:28 | values | forms.js:9:31:9:40 | values.foo | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:8:23:8:28 | values | DOM text |
| forms.js:12:31:12:40 | values.bar | forms.js:11:24:11:29 | values | forms.js:12:31:12:40 | values.bar | $@ is reinterpreted as HTML without escaping meta-characters. | forms.js:11:24:11:29 | values | DOM text |
@@ -219,3 +227,4 @@ edges
| xss-through-dom.js:87:16:87:40 | new ans ... s(text) | xss-through-dom.js:84:15:84:30 | $("text").text() | xss-through-dom.js:87:16:87:40 | new ans ... s(text) | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:84:15:84:30 | $("text").text() | DOM text |
| xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:93:16:93:46 | $("#foo ... ].value | DOM text |
| xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:96:17:96:47 | $("#foo ... ].value | DOM text |
+| xss-through-dom.js:109:31:109:70 | "" | xss-through-dom.js:109:45:109:55 | this.el.src | xss-through-dom.js:109:31:109:70 | "" | $@ is reinterpreted as HTML without escaping meta-characters. | xss-through-dom.js:109:45:109:55 | this.el.src | DOM text |
diff --git a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js
index d7f6d6a8208..b6e23c11c02 100644
--- a/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js
+++ b/javascript/ql/test/query-tests/Security/CWE-079/XssThroughDom/xss-through-dom.js
@@ -95,4 +95,17 @@
for (var i = 0; i < foo.length; i++) {
$("#id").html($("#foo").find(".bla")[i].value); // NOT OK.
}
-})();
\ No newline at end of file
+})();
+
+class Super {
+ constructor() {
+ this.el = $("#id").get(0);
+ }
+}
+
+class Sub extends Super {
+ constructor() {
+ super();
+ $("#id").get(0).innerHTML = "foo"; // NOT OK. Attack: ``
+ }
+}
\ No newline at end of file
From a762373ad6565c5af8cfa987b6c105a414ada26f Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 30 Aug 2021 11:04:27 +0200
Subject: [PATCH 110/741] 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 111/741] 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 112/741] 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 113/741] 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 114/741] 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 115/741] 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 e0d978fd585031b0165735276112fb8e56f7abd4 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 11 Aug 2021 17:35:09 +0200
Subject: [PATCH 116/741] Migrate String constructor to model
---
.../ql/lib/semmle/code/java/dataflow/FlowSteps.qll | 7 +------
java/ql/src/semmle/code/java/Strings.qll | 14 ++++++++++++++
2 files changed, 15 insertions(+), 6 deletions(-)
create mode 100644 java/ql/src/semmle/code/java/Strings.qll
diff --git a/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll b/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
index 5c58658cffd..f0bcb0d6a89 100644
--- a/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
@@ -10,6 +10,7 @@ private import semmle.code.java.dataflow.DataFlow
* ensuring that they are visible to the taint tracking library.
*/
private module Frameworks {
+ private import semmle.code.java.Strings
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.SQLite
@@ -108,12 +109,6 @@ private class StringTaintPreservingMethod extends TaintPreservingCallable {
}
}
-private class StringTaintPreservingConstructor extends Constructor, TaintPreservingCallable {
- StringTaintPreservingConstructor() { this.getDeclaringType() instanceof TypeString }
-
- override predicate returnsTaintFrom(int arg) { arg = 0 }
-}
-
private class NumberTaintPreservingCallable extends TaintPreservingCallable {
int argument;
diff --git a/java/ql/src/semmle/code/java/Strings.qll b/java/ql/src/semmle/code/java/Strings.qll
new file mode 100644
index 00000000000..452e85c5de0
--- /dev/null
+++ b/java/ql/src/semmle/code/java/Strings.qll
@@ -0,0 +1,14 @@
+/** Definitions of taint steps in String and String-related classes of the JDK */
+
+import java
+private import semmle.code.java.dataflow.ExternalFlow
+
+private class StringSummaryCsv extends SummaryModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ //`namespace; type; subtypes; name; signature; ext; input; output; kind`
+ "java.lang;String;false;String;;;Argument[0];Argument[-1];taint"
+ ]
+ }
+}
From 5df5805d366367347f3d2648a367ec923da32b1f Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 10:41:27 +0200
Subject: [PATCH 117/741] Convert strings to summary model
---
.../code/java/dataflow/ExternalFlow.qll | 1 +
.../semmle/code/java/dataflow/FlowSteps.qll | 68 -------------------
java/ql/src/semmle/code/java/Strings.qll | 14 ----
.../semmle/code/java/frameworks/Strings.qll | 42 ++++++++++++
4 files changed, 43 insertions(+), 82 deletions(-)
delete mode 100644 java/ql/src/semmle/code/java/Strings.qll
create mode 100644 java/ql/src/semmle/code/java/frameworks/Strings.qll
diff --git a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
index de6143154f4..0ac045c800f 100644
--- a/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/ExternalFlow.qll
@@ -89,6 +89,7 @@ private module Frameworks {
private import semmle.code.java.frameworks.JsonJava
private import semmle.code.java.frameworks.Objects
private import semmle.code.java.frameworks.Optional
+ private import semmle.code.java.frameworks.Strings
private import semmle.code.java.frameworks.spring.SpringCache
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.spring.SpringUtil
diff --git a/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll b/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
index f0bcb0d6a89..0017582c0d9 100644
--- a/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/FlowSteps.qll
@@ -10,7 +10,6 @@ private import semmle.code.java.dataflow.DataFlow
* ensuring that they are visible to the taint tracking library.
*/
private module Frameworks {
- private import semmle.code.java.Strings
private import semmle.code.java.frameworks.jackson.JacksonSerializability
private import semmle.code.java.frameworks.android.Intent
private import semmle.code.java.frameworks.android.SQLite
@@ -85,30 +84,6 @@ abstract class TaintPreservingCallable extends Callable {
predicate transfersTaint(int src, int sink) { none() }
}
-private class StringTaintPreservingMethod extends TaintPreservingCallable {
- StringTaintPreservingMethod() {
- this.getDeclaringType() instanceof TypeString and
- (
- this.hasName([
- "concat", "copyValueOf", "endsWith", "format", "formatted", "getBytes", "indent",
- "intern", "join", "repeat", "split", "strip", "stripIndent", "stripLeading",
- "stripTrailing", "substring", "toCharArray", "toLowerCase", "toString", "toUpperCase",
- "trim"
- ])
- or
- this.hasName("valueOf") and this.getParameterType(0) instanceof Array
- )
- }
-
- override predicate returnsTaintFrom(int arg) {
- arg = -1 and not this.isStatic()
- or
- this.hasName(["concat", "copyValueOf", "valueOf"]) and arg = 0
- or
- this.hasName(["format", "formatted", "join"]) and arg = [0 .. getNumberOfParameters()]
- }
-}
-
private class NumberTaintPreservingCallable extends TaintPreservingCallable {
int argument;
@@ -128,46 +103,3 @@ private class NumberTaintPreservingCallable extends TaintPreservingCallable {
override predicate returnsTaintFrom(int arg) { arg = argument }
}
-
-/** Holds for the types `StringBuilder`, `StringBuffer`, and `StringWriter`. */
-private predicate stringBuilderType(RefType t) {
- t instanceof StringBuildingType or
- t.hasQualifiedName("java.io", "StringWriter")
-}
-
-private class StringBuilderTaintPreservingCallable extends TaintPreservingCallable {
- StringBuilderTaintPreservingCallable() {
- exists(Method m |
- this.(Method).overrides*(m) and
- stringBuilderType(m.getDeclaringType()) and
- m.hasName(["append", "insert", "replace", "toString", "write"])
- )
- or
- this.(Constructor).getParameterType(0) instanceof RefType and
- stringBuilderType(this.getDeclaringType())
- }
-
- override predicate returnsTaintFrom(int arg) {
- arg = -1 and
- not this instanceof Constructor
- or
- this instanceof Constructor and arg = 0
- or
- this.hasName("append") and arg = 0
- or
- this.hasName("insert") and arg = 1
- or
- this.hasName("replace") and arg = 2
- }
-
- override predicate transfersTaint(int src, int sink) {
- returnsTaintFrom(src) and
- sink = -1 and
- src != -1 and
- not this instanceof Constructor
- or
- this.hasName("write") and
- src = 0 and
- sink = -1
- }
-}
diff --git a/java/ql/src/semmle/code/java/Strings.qll b/java/ql/src/semmle/code/java/Strings.qll
deleted file mode 100644
index 452e85c5de0..00000000000
--- a/java/ql/src/semmle/code/java/Strings.qll
+++ /dev/null
@@ -1,14 +0,0 @@
-/** Definitions of taint steps in String and String-related classes of the JDK */
-
-import java
-private import semmle.code.java.dataflow.ExternalFlow
-
-private class StringSummaryCsv extends SummaryModelCsv {
- override predicate row(string row) {
- row =
- [
- //`namespace; type; subtypes; name; signature; ext; input; output; kind`
- "java.lang;String;false;String;;;Argument[0];Argument[-1];taint"
- ]
- }
-}
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/src/semmle/code/java/frameworks/Strings.qll
new file mode 100644
index 00000000000..f4959a25989
--- /dev/null
+++ b/java/ql/src/semmle/code/java/frameworks/Strings.qll
@@ -0,0 +1,42 @@
+/** Definitions of taint steps in String and String-related classes of the JDK */
+
+import java
+private import semmle.code.java.dataflow.ExternalFlow
+
+private class StringSummaryCsv extends SummaryModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ //`namespace; type; subtypes; name; signature; ext; input; output; kind`
+ "java.lang;String;false;concat;(String);;Argument[0];ReturnValue;taint",
+ "java.lang;String;false;copyValueOf;;;Argument[0];ReturnValue;taint",
+ "java.lang;String;false;endsWith;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;format;(Locale,String,Object[]);;Argument[1];ReturnValue;taint",
+ "java.lang;String;false;format;(Locale,String,Object[]);;ArrayElement of Argument[2];ReturnValue;taint",
+ "java.lang;String;false;format;(String,Object[]);;Argument[0];ReturnValue;taint",
+ "java.lang;String;false;format;(String,Object[]);;ArrayElement of Argument[1];ReturnValue;taint",
+ "java.lang;String;false;formatted;(Object[]);;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;formatted;(Object[]);;ArrayElement of Argument[0];ReturnValue;taint",
+ "java.lang;String;false;getBytes;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;indent;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;intern;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;join;;;Argument[0..1];ReturnValue;taint",
+ "java.lang;String;false;repeat;(int);;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;split;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;String;;;Argument[0];Argument[-1];value",
+ "java.lang;String;false;strip;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;stripIndent;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;stripLeading;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;stripTrailing;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;substring;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;toCharArray;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;toLowerCase;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;toString;;;Argument[-1];ReturnValue;value",
+ "java.lang;String;false;toUpperCase;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;trim;;;Argument[-1];ReturnValue;taint",
+ "java.lang;String;false;valueOf;(char);;Argument[0];ReturnValue;taint",
+ "java.lang;String;false;valueOf;(char[],int,int);;Argument[0];ReturnValue;taint",
+ "java.lang;String;false;valueOf;(char[]);;Argument[0];ReturnValue;taint"
+ ]
+ }
+}
From dab626270da3f3429ac280901af0d2c6d1a45e12 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 10:41:43 +0200
Subject: [PATCH 118/741] Convert Objects API to csv model
---
.../semmle/code/java/frameworks/Objects.qll | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 java/ql/src/semmle/code/java/frameworks/Objects.qll
diff --git a/java/ql/src/semmle/code/java/frameworks/Objects.qll b/java/ql/src/semmle/code/java/frameworks/Objects.qll
new file mode 100644
index 00000000000..60d9d3afac5
--- /dev/null
+++ b/java/ql/src/semmle/code/java/frameworks/Objects.qll
@@ -0,0 +1,19 @@
+/** Definitions of taint steps in Objects class of the JDK */
+
+import java
+private import semmle.code.java.dataflow.ExternalFlow
+
+private class ObjectsSummaryCsv extends SummaryModelCsv {
+ override predicate row(string row) {
+ row =
+ [
+ //`namespace; type; subtypes; name; signature; ext; input; output; kind`
+ "java.util;Objects;false;requireNonNull;;;Argument[0];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElse;;;Argument[0];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElse;;;Argument[1];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElseGet;;;Argument[0];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElseGet;;;Argument[1];ReturnValue;value",
+ "java.util;Objects;false;toString;;;Argument[1];ReturnValue;value"
+ ]
+ }
+}
From b7e608abc93bdd7c90131b01b6a3707447367d5a Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 14:38:03 +0200
Subject: [PATCH 119/741] Model string builder APIs
---
.../ql/src/semmle/code/java/frameworks/Strings.qll | 14 +++++++++++++-
.../dataflow/taint-format/test.expected | 13 +++++++++++++
2 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/src/semmle/code/java/frameworks/Strings.qll
index f4959a25989..247a360c308 100644
--- a/java/ql/src/semmle/code/java/frameworks/Strings.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Strings.qll
@@ -36,7 +36,19 @@ private class StringSummaryCsv extends SummaryModelCsv {
"java.lang;String;false;trim;;;Argument[-1];ReturnValue;taint",
"java.lang;String;false;valueOf;(char);;Argument[0];ReturnValue;taint",
"java.lang;String;false;valueOf;(char[],int,int);;Argument[0];ReturnValue;taint",
- "java.lang;String;false;valueOf;(char[]);;Argument[0];ReturnValue;taint"
+ "java.lang;String;false;valueOf;(char[]);;Argument[0];ReturnValue;taint",
+ "java.io;StringWriter;true;append;;;Argument[0];Argument[-1];taint",
+ "java.io;StringWriter;true;append;;;Argument[0];ReturnValue;taint",
+ "java.io;StringWriter;true;write;;;Argument[0];Argument[-1];taint",
+ "java.lang;AbstractStringBuilder;true;AbstractStringBuilder;(String);;Argument[0];Argument[-1];taint",
+ "java.lang;AbstractStringBuilder;true;append;;;Argument[0];Argument[-1];taint",
+ "java.lang;AbstractStringBuilder;true;append;;;Argument[-1];ReturnValue;taint",
+ "java.lang;AbstractStringBuilder;true;insert;;;Argument[1];Argument[-1];taint",
+ "java.lang;AbstractStringBuilder;true;insert;;;Argument[-1];ReturnValue;taint",
+ "java.lang;AbstractStringBuilder;true;toString;;;Argument[-1];ReturnValue;taint",
+ "java.lang;StringBuffer;true;StringBuffer;(CharSequence);;Argument[0];Argument[-1];taint",
+ "java.lang;StringBuffer;true;StringBuffer;(String);;Argument[0];Argument[-1];taint",
+ "java.lang;StringBuilder;true;StringBuilder;;;Argument[0];Argument[-1];taint"
]
}
}
diff --git a/java/ql/test/library-tests/dataflow/taint-format/test.expected b/java/ql/test/library-tests/dataflow/taint-format/test.expected
index ddc0f36d753..5ac6d5050dc 100644
--- a/java/ql/test/library-tests/dataflow/taint-format/test.expected
+++ b/java/ql/test/library-tests/dataflow/taint-format/test.expected
@@ -10,6 +10,13 @@
| A.java:10:22:10:28 | taint(...) | A.java:17:9:17:105 | format(...) |
| A.java:10:22:10:28 | taint(...) | A.java:17:9:17:105 | new ..[] { .. } |
| A.java:10:22:10:28 | taint(...) | A.java:17:102:17:104 | bad |
+| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] read: [] of argument 0 in formatted |
+| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] read: [] of argument 1 in format |
+| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] to write: return (return) in format |
+| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | [summary] to write: return (return) in formatted |
+| A.java:10:22:10:28 | taint(...) | file:///modules/java.base/java/lang/String.class:0:0:0:0 | parameter this |
+| A.java:10:22:10:28 | taint(...) | file://:0:0:0:0 | p0 |
+| A.java:10:22:10:28 | taint(...) | file://:0:0:0:0 | p1 |
| A.java:21:22:21:28 | taint(...) | A.java:21:22:21:28 | taint(...) |
| A.java:21:22:21:28 | taint(...) | A.java:25:9:25:9 | f [post update] |
| A.java:21:22:21:28 | taint(...) | A.java:25:9:25:27 | format(...) |
@@ -26,6 +33,8 @@
| A.java:30:22:30:28 | taint(...) | A.java:35:24:35:26 | bad |
| A.java:30:22:30:28 | taint(...) | A.java:36:9:36:10 | sb |
| A.java:30:22:30:28 | taint(...) | A.java:36:9:36:21 | toString(...) |
+| A.java:30:22:30:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | [summary] to write: return (return) in toString |
+| A.java:30:22:30:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | parameter this |
| A.java:40:22:40:28 | taint(...) | A.java:40:22:40:28 | taint(...) |
| A.java:40:22:40:28 | taint(...) | A.java:43:9:43:10 | sb [post update] |
| A.java:40:22:40:28 | taint(...) | A.java:43:9:43:22 | append(...) |
@@ -34,3 +43,7 @@
| A.java:40:22:40:28 | taint(...) | A.java:45:9:45:38 | format(...) |
| A.java:40:22:40:28 | taint(...) | A.java:45:9:45:49 | toString(...) |
| A.java:40:22:40:28 | taint(...) | A.java:45:23:45:24 | sb |
+| A.java:40:22:40:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | [summary] to write: argument -1 in append |
+| A.java:40:22:40:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | [summary] to write: return (return) in append |
+| A.java:40:22:40:28 | taint(...) | file:///modules/java.base/java/lang/StringBuilder.class:0:0:0:0 | parameter this |
+| A.java:40:22:40:28 | taint(...) | file://:0:0:0:0 | p0 |
From 3928ffd30d93eaedd88dadd3fe632312b7d62986 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 15:23:03 +0200
Subject: [PATCH 120/741] Support CharSequence#subSequence
---
java/ql/src/semmle/code/java/frameworks/Strings.qll | 3 ++-
.../test/library-tests/dataflow/taint/CharSeq.java | 13 +++++++++++++
.../test/library-tests/dataflow/taint/test.expected | 2 ++
3 files changed, 17 insertions(+), 1 deletion(-)
create mode 100644 java/ql/test/library-tests/dataflow/taint/CharSeq.java
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/src/semmle/code/java/frameworks/Strings.qll
index 247a360c308..c1f7a4c29e1 100644
--- a/java/ql/src/semmle/code/java/frameworks/Strings.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Strings.qll
@@ -48,7 +48,8 @@ private class StringSummaryCsv extends SummaryModelCsv {
"java.lang;AbstractStringBuilder;true;toString;;;Argument[-1];ReturnValue;taint",
"java.lang;StringBuffer;true;StringBuffer;(CharSequence);;Argument[0];Argument[-1];taint",
"java.lang;StringBuffer;true;StringBuffer;(String);;Argument[0];Argument[-1];taint",
- "java.lang;StringBuilder;true;StringBuilder;;;Argument[0];Argument[-1];taint"
+ "java.lang;StringBuilder;true;StringBuilder;;;Argument[0];Argument[-1];taint",
+ "java.lang;CharSequence;true;subSequence;;;Argument[-1];ReturnValue;taint"
]
}
}
diff --git a/java/ql/test/library-tests/dataflow/taint/CharSeq.java b/java/ql/test/library-tests/dataflow/taint/CharSeq.java
new file mode 100644
index 00000000000..d006dc900e5
--- /dev/null
+++ b/java/ql/test/library-tests/dataflow/taint/CharSeq.java
@@ -0,0 +1,13 @@
+public class CharSeq {
+ public static String taint() { return "tainted"; }
+
+ public static void sink(Object o) { }
+
+ void test1() {
+ CharSequence seq = taint().subSequence(0,1);
+ sink(seq);
+
+ CharSequence seqFromSeq = seq.subSequence(0, 1);
+ sink(seqFromSeq);
+ }
+ }
\ No newline at end of file
diff --git a/java/ql/test/library-tests/dataflow/taint/test.expected b/java/ql/test/library-tests/dataflow/taint/test.expected
index c17f3bc0399..a0ab3b4358b 100644
--- a/java/ql/test/library-tests/dataflow/taint/test.expected
+++ b/java/ql/test/library-tests/dataflow/taint/test.expected
@@ -37,6 +37,8 @@
| B.java:15:21:15:27 | taint(...) | B.java:143:10:143:44 | toURL(...) |
| B.java:15:21:15:27 | taint(...) | B.java:146:10:146:37 | toPath(...) |
| B.java:15:21:15:27 | taint(...) | B.java:149:10:149:46 | toFile(...) |
+| CharSeq.java:7:26:7:32 | taint(...) | CharSeq.java:8:12:8:14 | seq |
+| CharSeq.java:7:26:7:32 | taint(...) | CharSeq.java:11:12:11:21 | seqFromSeq |
| MethodFlow.java:7:22:7:28 | taint(...) | MethodFlow.java:8:10:8:16 | tainted |
| MethodFlow.java:9:31:9:37 | taint(...) | MethodFlow.java:10:10:10:17 | tainted2 |
| MethodFlow.java:11:35:11:41 | taint(...) | MethodFlow.java:12:10:12:17 | tainted3 |
From 7be179cf6cef4a986b94ea9325f9266fe863a03f Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 16 Aug 2021 16:00:32 +0200
Subject: [PATCH 121/741] Mark String constructor as propagating taint
---
java/ql/src/semmle/code/java/frameworks/Strings.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/src/semmle/code/java/frameworks/Strings.qll
index c1f7a4c29e1..c90a649a6cd 100644
--- a/java/ql/src/semmle/code/java/frameworks/Strings.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Strings.qll
@@ -23,7 +23,7 @@ private class StringSummaryCsv extends SummaryModelCsv {
"java.lang;String;false;join;;;Argument[0..1];ReturnValue;taint",
"java.lang;String;false;repeat;(int);;Argument[-1];ReturnValue;taint",
"java.lang;String;false;split;;;Argument[-1];ReturnValue;taint",
- "java.lang;String;false;String;;;Argument[0];Argument[-1];value",
+ "java.lang;String;false;String;;;Argument[0];Argument[-1];taint",
"java.lang;String;false;strip;;;Argument[-1];ReturnValue;taint",
"java.lang;String;false;stripIndent;;;Argument[-1];ReturnValue;taint",
"java.lang;String;false;stripLeading;;;Argument[-1];ReturnValue;taint",
From 93bc8aa7b27b3dde2ac1054ff5531efcca26996f Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 17 Aug 2021 10:29:34 +0200
Subject: [PATCH 122/741] Fix tests to take `trim` into account
---
.../JakartaExpressionInjection.expected | 6 +++-
.../security/CWE-094/JythonInjection.expected | 4 ++-
.../security/CWE-094/ScriptInjection.expected | 4 ++-
.../security/CWE-094/SpelInjection.expected | 36 +++++++++++++++----
.../semmle/examples/SqlTaintedLocal.expected | 9 +++++
...alidationOfArrayConstructionLocal.expected | 8 +++--
...properValidationOfArrayIndexLocal.expected | 6 +++-
.../tests/ArithmeticTaintedLocal.expected | 26 +++++++++-----
.../tests/NumericCastTaintedLocal.expected | 6 +++-
9 files changed, 83 insertions(+), 22 deletions(-)
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
index b4f4e1f696b..dab245c67ae 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JakartaExpressionInjection.expected
@@ -1,6 +1,8 @@
edges
| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] |
-| JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] | JakartaExpressionInjection.java:25:31:25:40 | expression : String |
+| JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] | JakartaExpressionInjection.java:24:48:24:52 | bytes : byte[] |
+| JakartaExpressionInjection.java:24:37:24:59 | new String(...) : String | JakartaExpressionInjection.java:25:31:25:40 | expression : String |
+| JakartaExpressionInjection.java:24:48:24:52 | bytes : byte[] | JakartaExpressionInjection.java:24:37:24:59 | new String(...) : String |
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:32:24:32:33 | expression : String |
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:40:24:40:33 | expression : String |
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | JakartaExpressionInjection.java:48:24:48:33 | expression : String |
@@ -20,6 +22,8 @@ edges
nodes
| JakartaExpressionInjection.java:23:25:23:47 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| JakartaExpressionInjection.java:23:54:23:58 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| JakartaExpressionInjection.java:24:37:24:59 | new String(...) : String | semmle.label | new String(...) : String |
+| JakartaExpressionInjection.java:24:48:24:52 | bytes : byte[] | semmle.label | bytes : byte[] |
| JakartaExpressionInjection.java:25:31:25:40 | expression : String | semmle.label | expression : String |
| JakartaExpressionInjection.java:32:24:32:33 | expression : String | semmle.label | expression : String |
| JakartaExpressionInjection.java:34:28:34:37 | expression | semmle.label | expression |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected
index 4f66cc83fbd..e067fb506d9 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected
@@ -2,7 +2,8 @@ edges
| JythonInjection.java:28:23:28:50 | getParameter(...) : String | JythonInjection.java:36:30:36:33 | code |
| JythonInjection.java:53:23:53:50 | getParameter(...) : String | JythonInjection.java:58:44:58:47 | code |
| JythonInjection.java:73:23:73:50 | getParameter(...) : String | JythonInjection.java:81:35:81:38 | code |
-| JythonInjection.java:97:23:97:50 | getParameter(...) : String | JythonInjection.java:106:61:106:75 | getBytes(...) |
+| JythonInjection.java:97:23:97:50 | getParameter(...) : String | JythonInjection.java:106:61:106:64 | code : String |
+| JythonInjection.java:106:61:106:64 | code : String | JythonInjection.java:106:61:106:75 | getBytes(...) |
nodes
| JythonInjection.java:28:23:28:50 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| JythonInjection.java:36:30:36:33 | code | semmle.label | code |
@@ -11,6 +12,7 @@ nodes
| JythonInjection.java:73:23:73:50 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| JythonInjection.java:81:35:81:38 | code | semmle.label | code |
| JythonInjection.java:97:23:97:50 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| JythonInjection.java:106:61:106:64 | code : String | semmle.label | code : String |
| JythonInjection.java:106:61:106:75 | getBytes(...) | semmle.label | getBytes(...) |
| JythonInjection.java:131:40:131:63 | getInputStream(...) | semmle.label | getInputStream(...) |
#select
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected
index 5f1d250e9a2..ec3a44de26f 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected
@@ -1,7 +1,8 @@
edges
| RhinoServlet.java:28:23:28:50 | getParameter(...) : String | RhinoServlet.java:32:55:32:58 | code |
| RhinoServlet.java:81:23:81:50 | getParameter(...) : String | RhinoServlet.java:83:54:83:57 | code |
-| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | RhinoServlet.java:89:74:89:88 | getBytes(...) |
+| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | RhinoServlet.java:89:74:89:77 | code : String |
+| RhinoServlet.java:89:74:89:77 | code : String | RhinoServlet.java:89:74:89:88 | getBytes(...) |
| ScriptEngineTest.java:20:44:20:55 | input : String | ScriptEngineTest.java:24:37:24:41 | input |
| ScriptEngineTest.java:27:51:27:62 | input : String | ScriptEngineTest.java:31:31:31:35 | input |
| ScriptEngineTest.java:35:58:35:69 | input : String | ScriptEngineTest.java:39:31:39:35 | input |
@@ -26,6 +27,7 @@ nodes
| RhinoServlet.java:81:23:81:50 | getParameter(...) : String | semmle.label | getParameter(...) : String |
| RhinoServlet.java:83:54:83:57 | code | semmle.label | code |
| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | semmle.label | getParameter(...) : String |
+| RhinoServlet.java:89:74:89:77 | code : String | semmle.label | code : String |
| RhinoServlet.java:89:74:89:88 | getBytes(...) | semmle.label | getBytes(...) |
| ScriptEngineTest.java:20:44:20:55 | input : String | semmle.label | input : String |
| ScriptEngineTest.java:24:37:24:41 | input | semmle.label | input |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/SpelInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/SpelInjection.expected
index 83d1725237a..67b31d0b6b8 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-094/SpelInjection.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-094/SpelInjection.expected
@@ -1,46 +1,70 @@
edges
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:18:13:18:14 | in : InputStream |
| SpelInjection.java:18:13:18:14 | in : InputStream | SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] |
-| SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] | SpelInjection.java:23:5:23:14 | expression |
+| SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] | SpelInjection.java:19:31:19:35 | bytes : byte[] |
+| SpelInjection.java:19:20:19:42 | new String(...) : String | SpelInjection.java:23:5:23:14 | expression |
+| SpelInjection.java:19:31:19:35 | bytes : byte[] | SpelInjection.java:19:20:19:42 | new String(...) : String |
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | SpelInjection.java:30:13:30:14 | in : InputStream |
| SpelInjection.java:30:13:30:14 | in : InputStream | SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] |
-| SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] | SpelInjection.java:34:5:34:14 | expression |
+| SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] | SpelInjection.java:31:31:31:35 | bytes : byte[] |
+| SpelInjection.java:31:20:31:42 | new String(...) : String | SpelInjection.java:34:5:34:14 | expression |
+| SpelInjection.java:31:31:31:35 | bytes : byte[] | SpelInjection.java:31:20:31:42 | new String(...) : String |
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | SpelInjection.java:41:13:41:14 | in : InputStream |
| SpelInjection.java:41:13:41:14 | in : InputStream | SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] |
-| SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] | SpelInjection.java:48:5:48:14 | expression |
+| SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] | SpelInjection.java:42:31:42:35 | bytes : byte[] |
+| SpelInjection.java:42:20:42:42 | new String(...) : String | SpelInjection.java:48:5:48:14 | expression |
+| SpelInjection.java:42:31:42:35 | bytes : byte[] | SpelInjection.java:42:20:42:42 | new String(...) : String |
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | SpelInjection.java:55:13:55:14 | in : InputStream |
| SpelInjection.java:55:13:55:14 | in : InputStream | SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] |
-| SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] | SpelInjection.java:59:5:59:14 | expression |
+| SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] | SpelInjection.java:56:31:56:35 | bytes : byte[] |
+| SpelInjection.java:56:20:56:42 | new String(...) : String | SpelInjection.java:59:5:59:14 | expression |
+| SpelInjection.java:56:31:56:35 | bytes : byte[] | SpelInjection.java:56:20:56:42 | new String(...) : String |
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | SpelInjection.java:66:13:66:14 | in : InputStream |
| SpelInjection.java:66:13:66:14 | in : InputStream | SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] |
-| SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] | SpelInjection.java:70:5:70:14 | expression |
+| SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] | SpelInjection.java:67:31:67:35 | bytes : byte[] |
+| SpelInjection.java:67:20:67:42 | new String(...) : String | SpelInjection.java:70:5:70:14 | expression |
+| SpelInjection.java:67:31:67:35 | bytes : byte[] | SpelInjection.java:67:20:67:42 | new String(...) : String |
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | SpelInjection.java:77:13:77:14 | in : InputStream |
| SpelInjection.java:77:13:77:14 | in : InputStream | SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] |
-| SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] | SpelInjection.java:83:5:83:14 | expression |
+| SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] | SpelInjection.java:78:31:78:35 | bytes : byte[] |
+| SpelInjection.java:78:20:78:42 | new String(...) : String | SpelInjection.java:83:5:83:14 | expression |
+| SpelInjection.java:78:31:78:35 | bytes : byte[] | SpelInjection.java:78:20:78:42 | new String(...) : String |
nodes
| SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:18:13:18:14 | in : InputStream | semmle.label | in : InputStream |
| SpelInjection.java:18:21:18:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| SpelInjection.java:19:20:19:42 | new String(...) : String | semmle.label | new String(...) : String |
+| SpelInjection.java:19:31:19:35 | bytes : byte[] | semmle.label | bytes : byte[] |
| SpelInjection.java:23:5:23:14 | expression | semmle.label | expression |
| SpelInjection.java:27:22:27:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:30:13:30:14 | in : InputStream | semmle.label | in : InputStream |
| SpelInjection.java:30:21:30:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| SpelInjection.java:31:20:31:42 | new String(...) : String | semmle.label | new String(...) : String |
+| SpelInjection.java:31:31:31:35 | bytes : byte[] | semmle.label | bytes : byte[] |
| SpelInjection.java:34:5:34:14 | expression | semmle.label | expression |
| SpelInjection.java:38:22:38:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:41:13:41:14 | in : InputStream | semmle.label | in : InputStream |
| SpelInjection.java:41:21:41:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| SpelInjection.java:42:20:42:42 | new String(...) : String | semmle.label | new String(...) : String |
+| SpelInjection.java:42:31:42:35 | bytes : byte[] | semmle.label | bytes : byte[] |
| SpelInjection.java:48:5:48:14 | expression | semmle.label | expression |
| SpelInjection.java:52:22:52:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:55:13:55:14 | in : InputStream | semmle.label | in : InputStream |
| SpelInjection.java:55:21:55:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| SpelInjection.java:56:20:56:42 | new String(...) : String | semmle.label | new String(...) : String |
+| SpelInjection.java:56:31:56:35 | bytes : byte[] | semmle.label | bytes : byte[] |
| SpelInjection.java:59:5:59:14 | expression | semmle.label | expression |
| SpelInjection.java:63:22:63:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:66:13:66:14 | in : InputStream | semmle.label | in : InputStream |
| SpelInjection.java:66:21:66:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| SpelInjection.java:67:20:67:42 | new String(...) : String | semmle.label | new String(...) : String |
+| SpelInjection.java:67:31:67:35 | bytes : byte[] | semmle.label | bytes : byte[] |
| SpelInjection.java:70:5:70:14 | expression | semmle.label | expression |
| SpelInjection.java:74:22:74:44 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SpelInjection.java:77:13:77:14 | in : InputStream | semmle.label | in : InputStream |
| SpelInjection.java:77:21:77:25 | bytes [post update] : byte[] | semmle.label | bytes [post update] : byte[] |
+| SpelInjection.java:78:20:78:42 | new String(...) : String | semmle.label | new String(...) : String |
+| SpelInjection.java:78:31:78:35 | bytes : byte[] | semmle.label | bytes : byte[] |
| SpelInjection.java:83:5:83:14 | expression | semmle.label | expression |
#select
| SpelInjection.java:23:5:23:14 | expression | SpelInjection.java:15:22:15:44 | getInputStream(...) : InputStream | SpelInjection.java:23:5:23:14 | expression | SpEL injection from $@. | SpelInjection.java:15:22:15:44 | getInputStream(...) | this user input |
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
index 4ecdc32c204..f024571cc60 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
@@ -4,9 +4,14 @@ edges
| Test.java:29:30:29:42 | args : String[] | Test.java:36:47:36:52 | query1 |
| Test.java:29:30:29:42 | args : String[] | Test.java:42:57:42:62 | query2 |
| Test.java:29:30:29:42 | args : String[] | Test.java:50:62:50:67 | query3 |
+| Test.java:29:30:29:42 | args : String[] | Test.java:58:19:58:26 | category : String |
| Test.java:29:30:29:42 | args : String[] | Test.java:62:47:62:61 | querySbToString |
| Test.java:29:30:29:42 | args : String[] | Test.java:70:40:70:44 | query |
| Test.java:29:30:29:42 | args : String[] | Test.java:78:46:78:50 | query |
+| Test.java:58:4:58:10 | querySb [post update] : StringBuilder | Test.java:60:29:60:35 | querySb : StringBuilder |
+| Test.java:58:19:58:26 | category : String | Test.java:58:4:58:10 | querySb [post update] : StringBuilder |
+| Test.java:60:29:60:35 | querySb : StringBuilder | Test.java:60:29:60:46 | toString(...) : String |
+| Test.java:60:29:60:46 | toString(...) : String | Test.java:62:47:62:61 | querySbToString |
| Test.java:183:33:183:45 | args : String[] | Test.java:209:47:209:68 | queryWithUserTableName |
| Test.java:213:26:213:38 | args : String[] | Test.java:214:11:214:14 | args : String[] |
| Test.java:213:26:213:38 | args : String[] | Test.java:218:14:218:17 | args : String[] |
@@ -20,6 +25,10 @@ nodes
| Test.java:36:47:36:52 | query1 | semmle.label | query1 |
| Test.java:42:57:42:62 | query2 | semmle.label | query2 |
| Test.java:50:62:50:67 | query3 | semmle.label | query3 |
+| Test.java:58:4:58:10 | querySb [post update] : StringBuilder | semmle.label | querySb [post update] : StringBuilder |
+| Test.java:58:19:58:26 | category : String | semmle.label | category : String |
+| Test.java:60:29:60:35 | querySb : StringBuilder | semmle.label | querySb : StringBuilder |
+| Test.java:60:29:60:46 | toString(...) : String | semmle.label | toString(...) : String |
| Test.java:62:47:62:61 | querySbToString | semmle.label | querySbToString |
| Test.java:70:40:70:44 | query | semmle.label | query |
| Test.java:78:46:78:50 | query | semmle.label | query |
diff --git a/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionLocal.expected b/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionLocal.expected
index 4778b39b05c..1ea08a5d4c3 100644
--- a/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayConstructionLocal.expected
@@ -1,8 +1,12 @@
edges
-| Test.java:76:27:76:60 | getProperty(...) : String | Test.java:80:31:80:34 | size |
-| Test.java:76:27:76:60 | getProperty(...) : String | Test.java:86:34:86:37 | size |
+| Test.java:76:27:76:60 | getProperty(...) : String | Test.java:78:37:78:48 | userProperty : String |
+| Test.java:78:37:78:48 | userProperty : String | Test.java:78:37:78:55 | trim(...) : String |
+| Test.java:78:37:78:55 | trim(...) : String | Test.java:80:31:80:34 | size |
+| Test.java:78:37:78:55 | trim(...) : String | Test.java:86:34:86:37 | size |
nodes
| Test.java:76:27:76:60 | getProperty(...) : String | semmle.label | getProperty(...) : String |
+| Test.java:78:37:78:48 | userProperty : String | semmle.label | userProperty : String |
+| Test.java:78:37:78:55 | trim(...) : String | semmle.label | trim(...) : String |
| Test.java:80:31:80:34 | size | semmle.label | size |
| Test.java:86:34:86:37 | size | semmle.label | size |
#select
diff --git a/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexLocal.expected b/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexLocal.expected
index dcd18ff8cbc..4997e7d656d 100644
--- a/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-129/semmle/tests/ImproperValidationOfArrayIndexLocal.expected
@@ -1,7 +1,11 @@
edges
-| Test.java:14:27:14:60 | getProperty(...) : String | Test.java:19:34:19:38 | index |
+| Test.java:14:27:14:60 | getProperty(...) : String | Test.java:16:38:16:49 | userProperty : String |
+| Test.java:16:38:16:49 | userProperty : String | Test.java:16:38:16:56 | trim(...) : String |
+| Test.java:16:38:16:56 | trim(...) : String | Test.java:19:34:19:38 | index |
nodes
| Test.java:14:27:14:60 | getProperty(...) : String | semmle.label | getProperty(...) : String |
+| Test.java:16:38:16:49 | userProperty : String | semmle.label | userProperty : String |
+| Test.java:16:38:16:56 | trim(...) : String | semmle.label | trim(...) : String |
| Test.java:19:34:19:38 | index | semmle.label | index |
#select
| Test.java:19:34:19:38 | index | Test.java:14:27:14:60 | getProperty(...) : String | Test.java:19:34:19:38 | index | $@ flows to here and is used as an index causing an ArrayIndexOutOfBoundsException. | Test.java:14:27:14:60 | getProperty(...) | User-provided value |
diff --git a/java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTaintedLocal.expected
index 637b605a06f..1f5541aa7c2 100644
--- a/java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTaintedLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-190/semmle/tests/ArithmeticTaintedLocal.expected
@@ -9,15 +9,19 @@ edges
| ArithmeticTainted.java:18:40:18:56 | readerInputStream : InputStreamReader | ArithmeticTainted.java:18:21:18:57 | new BufferedReader(...) : BufferedReader |
| ArithmeticTainted.java:19:26:19:39 | readerBuffered : BufferedReader | ArithmeticTainted.java:19:26:19:50 | readLine(...) : String |
| ArithmeticTainted.java:19:26:19:39 | readerBuffered : BufferedReader | ArithmeticTainted.java:19:26:19:50 | readLine(...) : String |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:32:17:32:20 | data |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:40:17:40:20 | data |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:50:17:50:20 | data |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:64:20:64:23 | data : Number |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:95:37:95:40 | data |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:118:9:118:12 | data : Number |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:119:10:119:13 | data : Number |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:120:10:120:13 | data : Number |
-| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:121:10:121:13 | data : Number |
+| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:21:29:21:40 | stringNumber : String |
+| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | ArithmeticTainted.java:21:29:21:40 | stringNumber : String |
+| ArithmeticTainted.java:21:29:21:40 | stringNumber : String | ArithmeticTainted.java:21:29:21:47 | trim(...) : String |
+| ArithmeticTainted.java:21:29:21:40 | stringNumber : String | ArithmeticTainted.java:21:29:21:47 | trim(...) : String |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:32:17:32:20 | data |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:40:17:40:20 | data |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:50:17:50:20 | data |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:64:20:64:23 | data : Number |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:95:37:95:40 | data |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:118:9:118:12 | data : Number |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:119:10:119:13 | data : Number |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:120:10:120:13 | data : Number |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | ArithmeticTainted.java:121:10:121:13 | data : Number |
| ArithmeticTainted.java:64:4:64:10 | tainted [post update] [dat] : Number | ArithmeticTainted.java:66:18:66:24 | tainted [dat] : Number |
| ArithmeticTainted.java:64:20:64:23 | data : Number | ArithmeticTainted.java:64:4:64:10 | tainted [post update] [dat] : Number |
| ArithmeticTainted.java:66:18:66:24 | tainted [dat] : Number | ArithmeticTainted.java:66:18:66:34 | getData(...) : Number |
@@ -43,6 +47,10 @@ nodes
| ArithmeticTainted.java:19:26:19:39 | readerBuffered : BufferedReader | semmle.label | readerBuffered : BufferedReader |
| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | semmle.label | readLine(...) : String |
| ArithmeticTainted.java:19:26:19:50 | readLine(...) : String | semmle.label | readLine(...) : String |
+| ArithmeticTainted.java:21:29:21:40 | stringNumber : String | semmle.label | stringNumber : String |
+| ArithmeticTainted.java:21:29:21:40 | stringNumber : String | semmle.label | stringNumber : String |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | semmle.label | trim(...) : String |
+| ArithmeticTainted.java:21:29:21:47 | trim(...) : String | semmle.label | trim(...) : String |
| ArithmeticTainted.java:32:17:32:20 | data | semmle.label | data |
| ArithmeticTainted.java:40:17:40:20 | data | semmle.label | data |
| ArithmeticTainted.java:50:17:50:20 | data | semmle.label | data |
diff --git a/java/ql/test/query-tests/security/CWE-681/semmle/tests/NumericCastTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-681/semmle/tests/NumericCastTaintedLocal.expected
index 90e1b421f97..c939746e67a 100644
--- a/java/ql/test/query-tests/security/CWE-681/semmle/tests/NumericCastTaintedLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-681/semmle/tests/NumericCastTaintedLocal.expected
@@ -3,13 +3,17 @@ edges
| Test.java:11:6:11:46 | new InputStreamReader(...) : InputStreamReader | Test.java:10:36:11:47 | new BufferedReader(...) : BufferedReader |
| Test.java:11:28:11:36 | System.in : InputStream | Test.java:11:6:11:46 | new InputStreamReader(...) : InputStreamReader |
| Test.java:12:26:12:39 | readerBuffered : BufferedReader | Test.java:12:26:12:50 | readLine(...) : String |
-| Test.java:12:26:12:50 | readLine(...) : String | Test.java:21:22:21:25 | data |
+| Test.java:12:26:12:50 | readLine(...) : String | Test.java:14:27:14:38 | stringNumber : String |
+| Test.java:14:27:14:38 | stringNumber : String | Test.java:14:27:14:45 | trim(...) : String |
+| Test.java:14:27:14:45 | trim(...) : String | Test.java:21:22:21:25 | data |
nodes
| Test.java:10:36:11:47 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader |
| Test.java:11:6:11:46 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
| Test.java:11:28:11:36 | System.in : InputStream | semmle.label | System.in : InputStream |
| Test.java:12:26:12:39 | readerBuffered : BufferedReader | semmle.label | readerBuffered : BufferedReader |
| Test.java:12:26:12:50 | readLine(...) : String | semmle.label | readLine(...) : String |
+| Test.java:14:27:14:38 | stringNumber : String | semmle.label | stringNumber : String |
+| Test.java:14:27:14:45 | trim(...) : String | semmle.label | trim(...) : String |
| Test.java:21:22:21:25 | data | semmle.label | data |
#select
| Test.java:21:17:21:25 | (...)... | Test.java:11:28:11:36 | System.in : InputStream | Test.java:21:22:21:25 | data | $@ flows to here and is cast to a narrower type, potentially causing truncation. | Test.java:11:28:11:36 | System.in | User-provided value |
From d178fe4e5dc5d8cbc51332b3276ba646c2ecf92e Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Tue, 17 Aug 2021 14:56:25 +0200
Subject: [PATCH 123/741] Fix failing tests
---
...ientSuppliedIpUsedInSecurityCheck.expected | 5 +--
.../security/CWE-759/HashWithoutSalt.expected | 8 +++--
.../security/CWE-090/LdapInjection.expected | 8 +++--
.../query-tests/security/CWE-611/XXE.expected | 32 ++++++++++++++-----
4 files changed, 39 insertions(+), 14 deletions(-)
diff --git a/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
index 160654628fe..2f2f135cb05 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-348/ClientSuppliedIpUsedInSecurityCheck.expected
@@ -1,8 +1,8 @@
edges
| ClientSuppliedIpUsedInSecurityCheck.java:16:21:16:33 | getClientIP(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:17:37:17:38 | ip |
| ClientSuppliedIpUsedInSecurityCheck.java:24:21:24:33 | getClientIP(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:25:33:25:34 | ip |
-| ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:34 | split(...) : String[] |
-| ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String |
+| ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:23 | xfHeader : String |
+| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:23 | xfHeader : String | ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:34 | split(...) : String[] |
| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:34 | split(...) : String[] | ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String |
| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | ClientSuppliedIpUsedInSecurityCheck.java:16:21:16:33 | getClientIP(...) : String |
| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | ClientSuppliedIpUsedInSecurityCheck.java:24:21:24:33 | getClientIP(...) : String |
@@ -12,6 +12,7 @@ nodes
| ClientSuppliedIpUsedInSecurityCheck.java:24:21:24:33 | getClientIP(...) : String | semmle.label | getClientIP(...) : String |
| ClientSuppliedIpUsedInSecurityCheck.java:25:33:25:34 | ip | semmle.label | ip |
| ClientSuppliedIpUsedInSecurityCheck.java:43:27:43:62 | getHeader(...) : String | semmle.label | getHeader(...) : String |
+| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:23 | xfHeader : String | semmle.label | xfHeader : String |
| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:34 | split(...) : String[] | semmle.label | split(...) : String[] |
| ClientSuppliedIpUsedInSecurityCheck.java:47:16:47:37 | ...[...] : String | semmle.label | ...[...] : String |
#select
diff --git a/java/ql/test/experimental/query-tests/security/CWE-759/HashWithoutSalt.expected b/java/ql/test/experimental/query-tests/security/CWE-759/HashWithoutSalt.expected
index b7a5ea8ee4a..1e232a68d77 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-759/HashWithoutSalt.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-759/HashWithoutSalt.expected
@@ -1,16 +1,20 @@
edges
| HashWithoutSalt.java:10:36:10:43 | password : String | HashWithoutSalt.java:10:36:10:54 | getBytes(...) |
| HashWithoutSalt.java:25:13:25:20 | password : String | HashWithoutSalt.java:25:13:25:31 | getBytes(...) |
-| HashWithoutSalt.java:93:22:93:29 | password : String | HashWithoutSalt.java:94:17:94:25 | passBytes |
-| HashWithoutSalt.java:111:22:111:29 | password : String | HashWithoutSalt.java:112:18:112:26 | passBytes |
+| HashWithoutSalt.java:93:22:93:29 | password : String | HashWithoutSalt.java:93:22:93:40 | getBytes(...) : byte[] |
+| HashWithoutSalt.java:93:22:93:40 | getBytes(...) : byte[] | HashWithoutSalt.java:94:17:94:25 | passBytes |
+| HashWithoutSalt.java:111:22:111:29 | password : String | HashWithoutSalt.java:111:22:111:40 | getBytes(...) : byte[] |
+| HashWithoutSalt.java:111:22:111:40 | getBytes(...) : byte[] | HashWithoutSalt.java:112:18:112:26 | passBytes |
nodes
| HashWithoutSalt.java:10:36:10:43 | password : String | semmle.label | password : String |
| HashWithoutSalt.java:10:36:10:54 | getBytes(...) | semmle.label | getBytes(...) |
| HashWithoutSalt.java:25:13:25:20 | password : String | semmle.label | password : String |
| HashWithoutSalt.java:25:13:25:31 | getBytes(...) | semmle.label | getBytes(...) |
| HashWithoutSalt.java:93:22:93:29 | password : String | semmle.label | password : String |
+| HashWithoutSalt.java:93:22:93:40 | getBytes(...) : byte[] | semmle.label | getBytes(...) : byte[] |
| HashWithoutSalt.java:94:17:94:25 | passBytes | semmle.label | passBytes |
| HashWithoutSalt.java:111:22:111:29 | password : String | semmle.label | password : String |
+| HashWithoutSalt.java:111:22:111:40 | getBytes(...) : byte[] | semmle.label | getBytes(...) : byte[] |
| HashWithoutSalt.java:112:18:112:26 | passBytes | semmle.label | passBytes |
#select
| HashWithoutSalt.java:10:36:10:54 | getBytes(...) | HashWithoutSalt.java:10:36:10:43 | password : String | HashWithoutSalt.java:10:36:10:54 | getBytes(...) | $@ is hashed without a salt. | HashWithoutSalt.java:10:36:10:43 | password : String | The password |
diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected
index 34e6033320c..e5843fea508 100644
--- a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected
+++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected
@@ -28,7 +28,8 @@ edges
| LdapInjection.java:147:76:147:109 | uBadSRDNAsync : String | LdapInjection.java:151:19:151:19 | s |
| LdapInjection.java:155:31:155:70 | uBadFilterCreateNOT : String | LdapInjection.java:156:58:156:115 | createNOTFilter(...) |
| LdapInjection.java:160:31:160:75 | uBadFilterCreateToString : String | LdapInjection.java:161:58:161:107 | toString(...) |
-| LdapInjection.java:165:32:165:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:168:58:168:69 | toString(...) |
+| LdapInjection.java:165:32:165:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:168:58:168:58 | b : StringBuilder |
+| LdapInjection.java:168:58:168:58 | b : StringBuilder | LdapInjection.java:168:58:168:69 | toString(...) |
| LdapInjection.java:172:32:172:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:176:14:176:26 | duplicate(...) |
| LdapInjection.java:180:32:180:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:184:14:184:26 | duplicate(...) |
| LdapInjection.java:188:32:188:74 | uBadSearchRequestSetDN : String | LdapInjection.java:192:14:192:14 | s |
@@ -49,7 +50,8 @@ edges
| LdapInjection.java:276:31:276:68 | sBadLdapQueryBase : String | LdapInjection.java:277:12:277:66 | base(...) |
| LdapInjection.java:281:31:281:71 | sBadLdapQueryComplex : String | LdapInjection.java:282:24:282:98 | is(...) |
| LdapInjection.java:286:31:286:69 | sBadFilterToString : String | LdapInjection.java:287:18:287:83 | toString(...) |
-| LdapInjection.java:291:31:291:67 | sBadFilterEncode : String | LdapInjection.java:294:18:294:29 | toString(...) |
+| LdapInjection.java:291:31:291:67 | sBadFilterEncode : String | LdapInjection.java:294:18:294:18 | s : StringBuffer |
+| LdapInjection.java:294:18:294:18 | s : StringBuffer | LdapInjection.java:294:18:294:29 | toString(...) |
| LdapInjection.java:314:30:314:54 | aBad : String | LdapInjection.java:316:36:316:55 | ... + ... |
| LdapInjection.java:314:57:314:83 | aBadDN : String | LdapInjection.java:316:14:316:33 | ... + ... |
| LdapInjection.java:320:30:320:54 | aBad : String | LdapInjection.java:322:65:322:84 | ... + ... |
@@ -113,6 +115,7 @@ nodes
| LdapInjection.java:160:31:160:75 | uBadFilterCreateToString : String | semmle.label | uBadFilterCreateToString : String |
| LdapInjection.java:161:58:161:107 | toString(...) | semmle.label | toString(...) |
| LdapInjection.java:165:32:165:82 | uBadFilterCreateToStringBuffer : String | semmle.label | uBadFilterCreateToStringBuffer : String |
+| LdapInjection.java:168:58:168:58 | b : StringBuilder | semmle.label | b : StringBuilder |
| LdapInjection.java:168:58:168:69 | toString(...) | semmle.label | toString(...) |
| LdapInjection.java:172:32:172:78 | uBadSearchRequestDuplicate : String | semmle.label | uBadSearchRequestDuplicate : String |
| LdapInjection.java:176:14:176:26 | duplicate(...) | semmle.label | duplicate(...) |
@@ -155,6 +158,7 @@ nodes
| LdapInjection.java:286:31:286:69 | sBadFilterToString : String | semmle.label | sBadFilterToString : String |
| LdapInjection.java:287:18:287:83 | toString(...) | semmle.label | toString(...) |
| LdapInjection.java:291:31:291:67 | sBadFilterEncode : String | semmle.label | sBadFilterEncode : String |
+| LdapInjection.java:294:18:294:18 | s : StringBuffer | semmle.label | s : StringBuffer |
| LdapInjection.java:294:18:294:29 | toString(...) | semmle.label | toString(...) |
| LdapInjection.java:314:30:314:54 | aBad : String | semmle.label | aBad : String |
| LdapInjection.java:314:57:314:83 | aBadDN : String | semmle.label | aBadDN : String |
diff --git a/java/ql/test/query-tests/security/CWE-611/XXE.expected b/java/ql/test/query-tests/security/CWE-611/XXE.expected
index e2fcffee125..09d6fe323e4 100644
--- a/java/ql/test/query-tests/security/CWE-611/XXE.expected
+++ b/java/ql/test/query-tests/security/CWE-611/XXE.expected
@@ -18,29 +18,37 @@ edges
| SchemaTests.java:45:56:45:76 | getInputStream(...) : InputStream | SchemaTests.java:45:39:45:77 | new StreamSource(...) |
| SimpleXMLTests.java:24:63:24:83 | getInputStream(...) : InputStream | SimpleXMLTests.java:24:41:24:84 | new InputStreamReader(...) |
| SimpleXMLTests.java:30:5:30:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:30:32:30:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:30:32:30:32 | b [post update] : byte[] | SimpleXMLTests.java:31:41:31:53 | new String(...) |
+| SimpleXMLTests.java:30:32:30:32 | b [post update] : byte[] | SimpleXMLTests.java:31:52:31:52 | b : byte[] |
+| SimpleXMLTests.java:31:52:31:52 | b : byte[] | SimpleXMLTests.java:31:41:31:53 | new String(...) |
| SimpleXMLTests.java:37:5:37:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:37:32:37:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:37:32:37:32 | b [post update] : byte[] | SimpleXMLTests.java:38:41:38:53 | new String(...) |
+| SimpleXMLTests.java:37:32:37:32 | b [post update] : byte[] | SimpleXMLTests.java:38:52:38:52 | b : byte[] |
+| SimpleXMLTests.java:38:52:38:52 | b : byte[] | SimpleXMLTests.java:38:41:38:53 | new String(...) |
| SimpleXMLTests.java:43:63:43:83 | getInputStream(...) : InputStream | SimpleXMLTests.java:43:41:43:84 | new InputStreamReader(...) |
| SimpleXMLTests.java:68:59:68:79 | getInputStream(...) : InputStream | SimpleXMLTests.java:68:37:68:80 | new InputStreamReader(...) |
| SimpleXMLTests.java:73:59:73:79 | getInputStream(...) : InputStream | SimpleXMLTests.java:73:37:73:80 | new InputStreamReader(...) |
| SimpleXMLTests.java:78:48:78:68 | getInputStream(...) : InputStream | SimpleXMLTests.java:78:26:78:69 | new InputStreamReader(...) |
| SimpleXMLTests.java:83:48:83:68 | getInputStream(...) : InputStream | SimpleXMLTests.java:83:26:83:69 | new InputStreamReader(...) |
| SimpleXMLTests.java:89:5:89:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:89:32:89:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:89:32:89:32 | b [post update] : byte[] | SimpleXMLTests.java:90:37:90:49 | new String(...) |
+| SimpleXMLTests.java:89:32:89:32 | b [post update] : byte[] | SimpleXMLTests.java:90:48:90:48 | b : byte[] |
+| SimpleXMLTests.java:90:48:90:48 | b : byte[] | SimpleXMLTests.java:90:37:90:49 | new String(...) |
| SimpleXMLTests.java:96:5:96:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:96:32:96:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:96:32:96:32 | b [post update] : byte[] | SimpleXMLTests.java:97:37:97:49 | new String(...) |
+| SimpleXMLTests.java:96:32:96:32 | b [post update] : byte[] | SimpleXMLTests.java:97:48:97:48 | b : byte[] |
+| SimpleXMLTests.java:97:48:97:48 | b : byte[] | SimpleXMLTests.java:97:37:97:49 | new String(...) |
| SimpleXMLTests.java:103:5:103:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:103:32:103:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:103:32:103:32 | b [post update] : byte[] | SimpleXMLTests.java:104:26:104:38 | new String(...) |
+| SimpleXMLTests.java:103:32:103:32 | b [post update] : byte[] | SimpleXMLTests.java:104:37:104:37 | b : byte[] |
+| SimpleXMLTests.java:104:37:104:37 | b : byte[] | SimpleXMLTests.java:104:26:104:38 | new String(...) |
| SimpleXMLTests.java:110:5:110:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:110:32:110:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:110:32:110:32 | b [post update] : byte[] | SimpleXMLTests.java:111:26:111:38 | new String(...) |
+| SimpleXMLTests.java:110:32:110:32 | b [post update] : byte[] | SimpleXMLTests.java:111:37:111:37 | b : byte[] |
+| SimpleXMLTests.java:111:37:111:37 | b : byte[] | SimpleXMLTests.java:111:26:111:38 | new String(...) |
| SimpleXMLTests.java:119:44:119:64 | getInputStream(...) : InputStream | SimpleXMLTests.java:119:22:119:65 | new InputStreamReader(...) |
| SimpleXMLTests.java:129:44:129:64 | getInputStream(...) : InputStream | SimpleXMLTests.java:129:22:129:65 | new InputStreamReader(...) |
| SimpleXMLTests.java:139:44:139:64 | getInputStream(...) : InputStream | SimpleXMLTests.java:139:22:139:65 | new InputStreamReader(...) |
| SimpleXMLTests.java:145:5:145:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:145:32:145:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:145:32:145:32 | b [post update] : byte[] | SimpleXMLTests.java:146:22:146:34 | new String(...) |
+| SimpleXMLTests.java:145:32:145:32 | b [post update] : byte[] | SimpleXMLTests.java:146:33:146:33 | b : byte[] |
+| SimpleXMLTests.java:146:33:146:33 | b : byte[] | SimpleXMLTests.java:146:22:146:34 | new String(...) |
| SimpleXMLTests.java:152:5:152:25 | getInputStream(...) : InputStream | SimpleXMLTests.java:152:32:152:32 | b [post update] : byte[] |
-| SimpleXMLTests.java:152:32:152:32 | b [post update] : byte[] | SimpleXMLTests.java:153:22:153:34 | new String(...) |
+| SimpleXMLTests.java:152:32:152:32 | b [post update] : byte[] | SimpleXMLTests.java:153:33:153:33 | b : byte[] |
+| SimpleXMLTests.java:153:33:153:33 | b : byte[] | SimpleXMLTests.java:153:22:153:34 | new String(...) |
| TransformerTests.java:20:44:20:64 | getInputStream(...) : InputStream | TransformerTests.java:20:27:20:65 | new StreamSource(...) |
| TransformerTests.java:21:40:21:60 | getInputStream(...) : InputStream | TransformerTests.java:21:23:21:61 | new StreamSource(...) |
| TransformerTests.java:71:44:71:64 | getInputStream(...) : InputStream | TransformerTests.java:71:27:71:65 | new StreamSource(...) |
@@ -123,9 +131,11 @@ nodes
| SimpleXMLTests.java:30:5:30:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:30:32:30:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:31:41:31:53 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:31:52:31:52 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:37:5:37:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:37:32:37:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:38:41:38:53 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:38:52:38:52 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:43:41:43:84 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) |
| SimpleXMLTests.java:43:63:43:83 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:48:37:48:57 | getInputStream(...) | semmle.label | getInputStream(...) |
@@ -143,15 +153,19 @@ nodes
| SimpleXMLTests.java:89:5:89:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:89:32:89:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:90:37:90:49 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:90:48:90:48 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:96:5:96:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:96:32:96:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:97:37:97:49 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:97:48:97:48 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:103:5:103:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:103:32:103:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:104:26:104:38 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:104:37:104:37 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:110:5:110:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:110:32:110:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:111:26:111:38 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:111:37:111:37 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:115:22:115:42 | getInputStream(...) | semmle.label | getInputStream(...) |
| SimpleXMLTests.java:119:22:119:65 | new InputStreamReader(...) | semmle.label | new InputStreamReader(...) |
| SimpleXMLTests.java:119:44:119:64 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
@@ -164,9 +178,11 @@ nodes
| SimpleXMLTests.java:145:5:145:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:145:32:145:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:146:22:146:34 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:146:33:146:33 | b : byte[] | semmle.label | b : byte[] |
| SimpleXMLTests.java:152:5:152:25 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| SimpleXMLTests.java:152:32:152:32 | b [post update] : byte[] | semmle.label | b [post update] : byte[] |
| SimpleXMLTests.java:153:22:153:34 | new String(...) | semmle.label | new String(...) |
+| SimpleXMLTests.java:153:33:153:33 | b : byte[] | semmle.label | b : byte[] |
| TransformerTests.java:20:27:20:65 | new StreamSource(...) | semmle.label | new StreamSource(...) |
| TransformerTests.java:20:44:20:64 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
| TransformerTests.java:21:23:21:61 | new StreamSource(...) | semmle.label | new StreamSource(...) |
From 7ddf7ff211c6b9f2e1ca41ee3812f8e58caf7277 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 18 Aug 2021 13:56:16 +0200
Subject: [PATCH 124/741] Track taint from concatenated string
---
.../semmle/code/java/frameworks/Strings.qll | 1 +
.../test/library-tests/dataflow/taint/B.java | 3 ++
.../dataflow/taint/test.expected | 45 ++++++++++---------
3 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/src/semmle/code/java/frameworks/Strings.qll
index c90a649a6cd..a9597a689e6 100644
--- a/java/ql/src/semmle/code/java/frameworks/Strings.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Strings.qll
@@ -9,6 +9,7 @@ private class StringSummaryCsv extends SummaryModelCsv {
[
//`namespace; type; subtypes; name; signature; ext; input; output; kind`
"java.lang;String;false;concat;(String);;Argument[0];ReturnValue;taint",
+ "java.lang;String;false;concat;(String);;Argument[-1];ReturnValue;taint",
"java.lang;String;false;copyValueOf;;;Argument[0];ReturnValue;taint",
"java.lang;String;false;endsWith;;;Argument[-1];ReturnValue;taint",
"java.lang;String;false;format;(Locale,String,Object[]);;Argument[1];ReturnValue;taint",
diff --git a/java/ql/test/library-tests/dataflow/taint/B.java b/java/ql/test/library-tests/dataflow/taint/B.java
index 155832897eb..b3564c7898b 100644
--- a/java/ql/test/library-tests/dataflow/taint/B.java
+++ b/java/ql/test/library-tests/dataflow/taint/B.java
@@ -46,6 +46,9 @@ public class B {
// tainted - tokenized string
String token = new StringTokenizer(badEscape).nextToken();
sink(token);
+ // tainted - fluent concatenation
+ String fluentConcat = "".concat("str").concat(token).concat("bar");
+ sink(fluentConcat);
// not tainted
String safe = notTainty(complex);
diff --git a/java/ql/test/library-tests/dataflow/taint/test.expected b/java/ql/test/library-tests/dataflow/taint/test.expected
index a0ab3b4358b..78e5dceb5f1 100644
--- a/java/ql/test/library-tests/dataflow/taint/test.expected
+++ b/java/ql/test/library-tests/dataflow/taint/test.expected
@@ -14,29 +14,30 @@
| B.java:15:21:15:27 | taint(...) | B.java:42:10:42:25 | valueOfSubstring |
| B.java:15:21:15:27 | taint(...) | B.java:45:10:45:18 | badEscape |
| B.java:15:21:15:27 | taint(...) | B.java:48:10:48:14 | token |
-| B.java:15:21:15:27 | taint(...) | B.java:65:10:65:13 | cond |
-| B.java:15:21:15:27 | taint(...) | B.java:68:10:68:14 | logic |
-| B.java:15:21:15:27 | taint(...) | B.java:70:10:70:39 | endsWith(...) |
-| B.java:15:21:15:27 | taint(...) | B.java:73:10:73:14 | logic |
+| B.java:15:21:15:27 | taint(...) | B.java:51:10:51:21 | fluentConcat |
+| B.java:15:21:15:27 | taint(...) | B.java:68:10:68:13 | cond |
+| B.java:15:21:15:27 | taint(...) | B.java:71:10:71:14 | logic |
+| B.java:15:21:15:27 | taint(...) | B.java:73:10:73:39 | endsWith(...) |
| B.java:15:21:15:27 | taint(...) | B.java:76:10:76:14 | logic |
-| B.java:15:21:15:27 | taint(...) | B.java:84:10:84:16 | trimmed |
-| B.java:15:21:15:27 | taint(...) | B.java:86:10:86:14 | split |
-| B.java:15:21:15:27 | taint(...) | B.java:88:10:88:14 | lower |
-| B.java:15:21:15:27 | taint(...) | B.java:90:10:90:14 | upper |
-| B.java:15:21:15:27 | taint(...) | B.java:92:10:92:14 | bytes |
-| B.java:15:21:15:27 | taint(...) | B.java:94:10:94:17 | toString |
-| B.java:15:21:15:27 | taint(...) | B.java:96:10:96:13 | subs |
-| B.java:15:21:15:27 | taint(...) | B.java:98:10:98:13 | repl |
-| B.java:15:21:15:27 | taint(...) | B.java:100:10:100:16 | replAll |
-| B.java:15:21:15:27 | taint(...) | B.java:102:10:102:18 | replFirst |
-| B.java:15:21:15:27 | taint(...) | B.java:115:12:115:25 | serializedData |
-| B.java:15:21:15:27 | taint(...) | B.java:127:12:127:27 | deserializedData |
-| B.java:15:21:15:27 | taint(...) | B.java:136:10:136:21 | taintedArray |
-| B.java:15:21:15:27 | taint(...) | B.java:138:10:138:22 | taintedArray2 |
-| B.java:15:21:15:27 | taint(...) | B.java:140:10:140:22 | taintedArray3 |
-| B.java:15:21:15:27 | taint(...) | B.java:143:10:143:44 | toURL(...) |
-| B.java:15:21:15:27 | taint(...) | B.java:146:10:146:37 | toPath(...) |
-| B.java:15:21:15:27 | taint(...) | B.java:149:10:149:46 | toFile(...) |
+| B.java:15:21:15:27 | taint(...) | B.java:79:10:79:14 | logic |
+| B.java:15:21:15:27 | taint(...) | B.java:87:10:87:16 | trimmed |
+| B.java:15:21:15:27 | taint(...) | B.java:89:10:89:14 | split |
+| B.java:15:21:15:27 | taint(...) | B.java:91:10:91:14 | lower |
+| B.java:15:21:15:27 | taint(...) | B.java:93:10:93:14 | upper |
+| B.java:15:21:15:27 | taint(...) | B.java:95:10:95:14 | bytes |
+| B.java:15:21:15:27 | taint(...) | B.java:97:10:97:17 | toString |
+| B.java:15:21:15:27 | taint(...) | B.java:99:10:99:13 | subs |
+| B.java:15:21:15:27 | taint(...) | B.java:101:10:101:13 | repl |
+| B.java:15:21:15:27 | taint(...) | B.java:103:10:103:16 | replAll |
+| B.java:15:21:15:27 | taint(...) | B.java:105:10:105:18 | replFirst |
+| B.java:15:21:15:27 | taint(...) | B.java:118:12:118:25 | serializedData |
+| B.java:15:21:15:27 | taint(...) | B.java:130:12:130:27 | deserializedData |
+| B.java:15:21:15:27 | taint(...) | B.java:139:10:139:21 | taintedArray |
+| B.java:15:21:15:27 | taint(...) | B.java:141:10:141:22 | taintedArray2 |
+| B.java:15:21:15:27 | taint(...) | B.java:143:10:143:22 | taintedArray3 |
+| B.java:15:21:15:27 | taint(...) | B.java:146:10:146:44 | toURL(...) |
+| B.java:15:21:15:27 | taint(...) | B.java:149:10:149:37 | toPath(...) |
+| B.java:15:21:15:27 | taint(...) | B.java:152:10:152:46 | toFile(...) |
| CharSeq.java:7:26:7:32 | taint(...) | CharSeq.java:8:12:8:14 | seq |
| CharSeq.java:7:26:7:32 | taint(...) | CharSeq.java:11:12:11:21 | seqFromSeq |
| MethodFlow.java:7:22:7:28 | taint(...) | MethodFlow.java:8:10:8:16 | tainted |
From 190bf90bc898f3111cd7331aec48b37a886da1f4 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Mon, 23 Aug 2021 15:18:24 +0200
Subject: [PATCH 125/741] Replace stringbuilder step with model
---
.../code/java/dataflow/internal/TaintTrackingUtil.qll | 11 -----------
java/ql/src/semmle/code/java/frameworks/Strings.qll | 5 ++++-
.../security/CWE-601/SpringUrlRedirect.expected | 10 ++++++++--
3 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
index 8de8fc02bcb..55e22b8a164 100644
--- a/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
+++ b/java/ql/lib/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
@@ -147,8 +147,6 @@ private predicate localAdditionalTaintExprStep(Expr src, Expr sink) {
or
comparisonStep(src, sink)
or
- stringBuilderStep(src, sink)
- or
serializationStep(src, sink)
or
formatStep(src, sink)
@@ -392,15 +390,6 @@ private predicate comparisonStep(Expr tracked, Expr sink) {
)
}
-/** Flow through a `StringBuilder`. */
-private predicate stringBuilderStep(Expr tracked, Expr sink) {
- exists(StringBuilderVar sbvar, MethodAccess input, int arg |
- input = sbvar.getAnInput(arg) and
- tracked = input.getArgument(arg) and
- sink = sbvar.getToStringCall()
- )
-}
-
/** Flow through data serialization. */
private predicate serializationStep(Expr tracked, Expr sink) {
exists(ObjectOutputStreamVar v, VariableAssign def |
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/src/semmle/code/java/frameworks/Strings.qll
index a9597a689e6..9774b8f8df6 100644
--- a/java/ql/src/semmle/code/java/frameworks/Strings.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Strings.qll
@@ -43,9 +43,12 @@ private class StringSummaryCsv extends SummaryModelCsv {
"java.io;StringWriter;true;write;;;Argument[0];Argument[-1];taint",
"java.lang;AbstractStringBuilder;true;AbstractStringBuilder;(String);;Argument[0];Argument[-1];taint",
"java.lang;AbstractStringBuilder;true;append;;;Argument[0];Argument[-1];taint",
- "java.lang;AbstractStringBuilder;true;append;;;Argument[-1];ReturnValue;taint",
+ "java.lang;AbstractStringBuilder;true;append;;;Argument[-1];ReturnValue;value",
+ "java.lang;AbstractStringBuilder;true;append;;;Argument[0];ReturnValue;taint",
"java.lang;AbstractStringBuilder;true;insert;;;Argument[1];Argument[-1];taint",
"java.lang;AbstractStringBuilder;true;insert;;;Argument[-1];ReturnValue;taint",
+ "java.lang;AbstractStringBuilder;true;replace;;;Argument[2];ReturnValue;taint",
+ "java.lang;AbstractStringBuilder;true;replace;;;Argument[2];Argument[-1];taint",
"java.lang;AbstractStringBuilder;true;toString;;;Argument[-1];ReturnValue;taint",
"java.lang;StringBuffer;true;StringBuffer;(CharSequence);;Argument[0];Argument[-1];taint",
"java.lang;StringBuffer;true;StringBuffer;(String);;Argument[0];Argument[-1];taint",
diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected
index 61213583d8d..44403c667d4 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected
@@ -5,8 +5,11 @@ edges
| SpringUrlRedirect.java:36:30:36:47 | redirectUrl : String | SpringUrlRedirect.java:37:47:37:57 | redirectUrl |
| SpringUrlRedirect.java:41:24:41:41 | redirectUrl : String | SpringUrlRedirect.java:44:29:44:39 | redirectUrl |
| SpringUrlRedirect.java:49:24:49:41 | redirectUrl : String | SpringUrlRedirect.java:52:30:52:40 | redirectUrl |
-| SpringUrlRedirect.java:57:24:57:41 | redirectUrl : String | SpringUrlRedirect.java:58:30:58:66 | format(...) |
-| SpringUrlRedirect.java:62:24:62:41 | redirectUrl : String | SpringUrlRedirect.java:63:30:63:76 | format(...) |
+| SpringUrlRedirect.java:57:24:57:41 | redirectUrl : String | SpringUrlRedirect.java:58:55:58:65 | redirectUrl : String |
+| SpringUrlRedirect.java:58:30:58:66 | new ..[] { .. } [[]] : String | SpringUrlRedirect.java:58:30:58:66 | format(...) |
+| SpringUrlRedirect.java:58:55:58:65 | redirectUrl : String | SpringUrlRedirect.java:58:30:58:66 | new ..[] { .. } [[]] : String |
+| SpringUrlRedirect.java:62:24:62:41 | redirectUrl : String | SpringUrlRedirect.java:63:44:63:68 | ... + ... : String |
+| SpringUrlRedirect.java:63:44:63:68 | ... + ... : String | SpringUrlRedirect.java:63:30:63:76 | format(...) |
| SpringUrlRedirect.java:89:38:89:55 | redirectUrl : String | SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String |
| SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | SpringUrlRedirect.java:91:27:91:49 | create(...) |
| SpringUrlRedirect.java:96:39:96:56 | redirectUrl : String | SpringUrlRedirect.java:98:44:98:54 | redirectUrl : String |
@@ -45,8 +48,11 @@ nodes
| SpringUrlRedirect.java:52:30:52:40 | redirectUrl | semmle.label | redirectUrl |
| SpringUrlRedirect.java:57:24:57:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:58:30:58:66 | format(...) | semmle.label | format(...) |
+| SpringUrlRedirect.java:58:30:58:66 | new ..[] { .. } [[]] : String | semmle.label | new ..[] { .. } [[]] : String |
+| SpringUrlRedirect.java:58:55:58:65 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:62:24:62:41 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:63:30:63:76 | format(...) | semmle.label | format(...) |
+| SpringUrlRedirect.java:63:44:63:68 | ... + ... : String | semmle.label | ... + ... : String |
| SpringUrlRedirect.java:89:38:89:55 | redirectUrl : String | semmle.label | redirectUrl : String |
| SpringUrlRedirect.java:91:27:91:49 | create(...) | semmle.label | create(...) |
| SpringUrlRedirect.java:91:38:91:48 | redirectUrl : String | semmle.label | redirectUrl : String |
From c1d34d7d6f48076133a99937abe21ec013c4f15c Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 1 Sep 2021 15:55:39 +0200
Subject: [PATCH 126/741] Move Strings to lib
---
.../semmle/code/java/frameworks/Objects.qll | 4 +++-
.../semmle/code/java/frameworks/Strings.qll | 0
.../semmle/code/java/frameworks/Objects.qll | 19 -------------------
3 files changed, 3 insertions(+), 20 deletions(-)
rename java/ql/{src => lib}/semmle/code/java/frameworks/Strings.qll (100%)
delete mode 100644 java/ql/src/semmle/code/java/frameworks/Objects.qll
diff --git a/java/ql/lib/semmle/code/java/frameworks/Objects.qll b/java/ql/lib/semmle/code/java/frameworks/Objects.qll
index acb0aa6e1cf..60d9d3afac5 100644
--- a/java/ql/lib/semmle/code/java/frameworks/Objects.qll
+++ b/java/ql/lib/semmle/code/java/frameworks/Objects.qll
@@ -9,8 +9,10 @@ private class ObjectsSummaryCsv extends SummaryModelCsv {
[
//`namespace; type; subtypes; name; signature; ext; input; output; kind`
"java.util;Objects;false;requireNonNull;;;Argument[0];ReturnValue;value",
- "java.util;Objects;false;requireNonNullElse;;;Argument[0..1];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElse;;;Argument[0];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElse;;;Argument[1];ReturnValue;value",
"java.util;Objects;false;requireNonNullElseGet;;;Argument[0];ReturnValue;value",
+ "java.util;Objects;false;requireNonNullElseGet;;;Argument[1];ReturnValue;value",
"java.util;Objects;false;toString;;;Argument[1];ReturnValue;value"
]
}
diff --git a/java/ql/src/semmle/code/java/frameworks/Strings.qll b/java/ql/lib/semmle/code/java/frameworks/Strings.qll
similarity index 100%
rename from java/ql/src/semmle/code/java/frameworks/Strings.qll
rename to java/ql/lib/semmle/code/java/frameworks/Strings.qll
diff --git a/java/ql/src/semmle/code/java/frameworks/Objects.qll b/java/ql/src/semmle/code/java/frameworks/Objects.qll
deleted file mode 100644
index 60d9d3afac5..00000000000
--- a/java/ql/src/semmle/code/java/frameworks/Objects.qll
+++ /dev/null
@@ -1,19 +0,0 @@
-/** Definitions of taint steps in Objects class of the JDK */
-
-import java
-private import semmle.code.java.dataflow.ExternalFlow
-
-private class ObjectsSummaryCsv extends SummaryModelCsv {
- override predicate row(string row) {
- row =
- [
- //`namespace; type; subtypes; name; signature; ext; input; output; kind`
- "java.util;Objects;false;requireNonNull;;;Argument[0];ReturnValue;value",
- "java.util;Objects;false;requireNonNullElse;;;Argument[0];ReturnValue;value",
- "java.util;Objects;false;requireNonNullElse;;;Argument[1];ReturnValue;value",
- "java.util;Objects;false;requireNonNullElseGet;;;Argument[0];ReturnValue;value",
- "java.util;Objects;false;requireNonNullElseGet;;;Argument[1];ReturnValue;value",
- "java.util;Objects;false;toString;;;Argument[1];ReturnValue;value"
- ]
- }
-}
From ee8958ba038a3baf9513c9ac9b73626f2b9038a7 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Wed, 1 Sep 2021 15:55:59 +0200
Subject: [PATCH 127/741] Fix nodes for local taint test
---
.../security/CWE-089/semmle/examples/SqlTaintedLocal.expected | 1 -
1 file changed, 1 deletion(-)
diff --git a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
index f024571cc60..bdf11aa3f57 100644
--- a/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
+++ b/java/ql/test/query-tests/security/CWE-089/semmle/examples/SqlTaintedLocal.expected
@@ -5,7 +5,6 @@ edges
| Test.java:29:30:29:42 | args : String[] | Test.java:42:57:42:62 | query2 |
| Test.java:29:30:29:42 | args : String[] | Test.java:50:62:50:67 | query3 |
| Test.java:29:30:29:42 | args : String[] | Test.java:58:19:58:26 | category : String |
-| Test.java:29:30:29:42 | args : String[] | Test.java:62:47:62:61 | querySbToString |
| Test.java:29:30:29:42 | args : String[] | Test.java:70:40:70:44 | query |
| Test.java:29:30:29:42 | args : String[] | Test.java:78:46:78:50 | query |
| Test.java:58:4:58:10 | querySb [post update] : StringBuilder | Test.java:60:29:60:35 | querySb : StringBuilder |
From 136c8b5192f43ea226c08a4771e4f3598f3ceb14 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 25 Aug 2021 11:02:45 +0200
Subject: [PATCH 128/741] 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 129/741] 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 130/741] 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.
+
+
+
+
+
+
+ CERT C Coding Standard:
+ FIO46-C. Do not access a closed file.
+
+
+
+
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 131/741] 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 132/741] 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 133/741] 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 134/741] 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 135/741] 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 136/741] 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 137/741] 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 138/741] 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 139/741] 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 140/741] 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.
+
+
+
+
+
+
+Official documentation of the text parameter.
+
+
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 141/741] 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 142/741] 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 143/741] 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 144/741] 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 145/741] 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 146/741] 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 147/741] 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 148/741] 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 149/741] 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 150/741] 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 151/741] 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 152/741] 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 153/741] 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 154/741] 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 155/741] 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 156/741] 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 c110508b4eeae5741a8b4fae6a8fd0d405a371f6 Mon Sep 17 00:00:00 2001
From: Anders Fugmann
Date: Thu, 2 Sep 2021 21:20:33 +0200
Subject: [PATCH 157/741] C++: Add tests to expose potential improvements
available to SimpleRangeAnalysis
---
.../SimpleRangeAnalysis/lowerBound.expected | 31 +++++++++--------
.../SimpleRangeAnalysis/ternaryLower.expected | 4 +--
.../SimpleRangeAnalysis/ternaryUpper.expected | 4 +--
.../rangeanalysis/SimpleRangeAnalysis/test.c | 15 +++++++++
.../SimpleRangeAnalysis/test.cpp | 1 +
.../SimpleRangeAnalysis/upperBound.expected | 33 +++++++++++--------
.../OverflowStatic/OverflowStatic.expected | 1 +
.../Critical/OverflowStatic/test.cpp | 10 ++++++
.../PointlessComparison/PointlessComparison.c | 2 +-
9 files changed, 69 insertions(+), 32 deletions(-)
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected
index 7478060a95b..0f3e0af8653 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/lowerBound.expected
@@ -594,6 +594,9 @@
| test.c:659:9:659:9 | u | 0 |
| test.c:664:12:664:12 | s | -2147483648 |
| test.c:665:7:665:8 | s2 | -4 |
+| test.c:670:7:670:7 | x | -2147483648 |
+| test.c:671:9:671:9 | y | -2147483648 |
+| test.c:675:7:675:7 | y | -2147483648 |
| test.cpp:10:7:10:7 | b | -2147483648 |
| test.cpp:11:5:11:5 | x | -2147483648 |
| test.cpp:13:10:13:10 | x | -2147483648 |
@@ -647,16 +650,18 @@
| test.cpp:97:10:97:10 | i | -2147483648 |
| test.cpp:97:22:97:22 | i | -2147483648 |
| test.cpp:98:5:98:5 | i | -2147483648 |
-| test.cpp:105:7:105:7 | n | -32768 |
-| test.cpp:108:7:108:7 | n | 0 |
-| test.cpp:109:5:109:5 | n | 1 |
-| test.cpp:111:5:111:5 | n | 0 |
-| test.cpp:114:8:114:8 | n | 0 |
-| test.cpp:115:5:115:5 | n | 0 |
-| test.cpp:117:5:117:5 | n | 1 |
-| test.cpp:120:3:120:3 | n | 0 |
-| test.cpp:120:8:120:8 | n | 1 |
-| test.cpp:120:12:120:12 | n | 0 |
-| test.cpp:121:4:121:4 | n | 0 |
-| test.cpp:121:8:121:8 | n | 0 |
-| test.cpp:121:12:121:12 | n | 1 |
+| test.cpp:98:9:98:9 | i | -2147483648 |
+| test.cpp:99:5:99:5 | i | -2147483648 |
+| test.cpp:106:7:106:7 | n | -32768 |
+| test.cpp:109:7:109:7 | n | 0 |
+| test.cpp:110:5:110:5 | n | 1 |
+| test.cpp:112:5:112:5 | n | 0 |
+| test.cpp:115:8:115:8 | n | 0 |
+| test.cpp:116:5:116:5 | n | 0 |
+| test.cpp:118:5:118:5 | n | 1 |
+| test.cpp:121:3:121:3 | n | 0 |
+| test.cpp:121:8:121:8 | n | 1 |
+| test.cpp:121:12:121:12 | n | 0 |
+| test.cpp:122:4:122:4 | n | 0 |
+| test.cpp:122:8:122:8 | n | 0 |
+| test.cpp:122:12:122:12 | n | 1 |
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.expected
index fedd3853ce2..f012490f115 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.expected
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryLower.expected
@@ -15,5 +15,5 @@
| test.c:394:20:394:36 | ... ? ... : ... | 0.0 | 0.0 | 100.0 |
| test.c:606:5:606:14 | ... ? ... : ... | 0.0 | 1.0 | 0.0 |
| test.c:607:5:607:14 | ... ? ... : ... | 0.0 | 0.0 | 1.0 |
-| test.cpp:120:3:120:12 | ... ? ... : ... | 0.0 | 1.0 | 0.0 |
-| test.cpp:121:3:121:12 | ... ? ... : ... | 0.0 | 0.0 | 1.0 |
+| test.cpp:121:3:121:12 | ... ? ... : ... | 0.0 | 1.0 | 0.0 |
+| test.cpp:122:3:122:12 | ... ? ... : ... | 0.0 | 0.0 | 1.0 |
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.expected
index 0b8fe8c1164..8a387c3ae46 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.expected
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/ternaryUpper.expected
@@ -15,5 +15,5 @@
| test.c:394:20:394:36 | ... ? ... : ... | 100.0 | 99.0 | 100.0 |
| test.c:606:5:606:14 | ... ? ... : ... | 32767.0 | 32767.0 | 0.0 |
| test.c:607:5:607:14 | ... ? ... : ... | 32767.0 | 0.0 | 32767.0 |
-| test.cpp:120:3:120:12 | ... ? ... : ... | 32767.0 | 32767.0 | 0.0 |
-| test.cpp:121:3:121:12 | ... ? ... : ... | 32767.0 | 0.0 | 32767.0 |
+| test.cpp:121:3:121:12 | ... ? ... : ... | 32767.0 | 32767.0 | 0.0 |
+| test.cpp:122:3:122:12 | ... ? ... : ... | 32767.0 | 0.0 | 32767.0 |
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c
index 40168c3e697..041ff9e5a86 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.c
@@ -664,3 +664,18 @@ void test_mod(int s) {
int s2 = s % 5;
out(s2); // -4 .. 4
}
+
+void exit(int);
+void guard_with_exit(int x, int y) {
+ if (x) {
+ if (y != 0) {
+ exit(0);
+ }
+ }
+ out(y); // ..
+
+ // This test ensures that we correctly identify
+ // that the upper bound for y is max_int when calling `out(y)`.
+ // The RangeSsa will place guardPhy on `out(y)`, and consequently there is no
+ // frontier phi node at out(y).
+}
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp
index 515633f4f73..e5eb9554966 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/test.cpp
@@ -95,6 +95,7 @@ int ref_to_number(int &i, const int &ci, int &aliased) {
return alias;
for (; i <= 12345; i++) { // test that widening works for references
+ i = i;
i;
}
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected
index ce6bed728eb..202662f8896 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected
@@ -594,6 +594,9 @@
| test.c:659:9:659:9 | u | 4294967295 |
| test.c:664:12:664:12 | s | 2147483647 |
| test.c:665:7:665:8 | s2 | 4 |
+| test.c:670:7:670:7 | x | 2147483647 |
+| test.c:671:9:671:9 | y | 2147483647 |
+| test.c:675:7:675:7 | y | 2147483647 |
| test.cpp:10:7:10:7 | b | 2147483647 |
| test.cpp:11:5:11:5 | x | 2147483647 |
| test.cpp:13:10:13:10 | x | 2147483647 |
@@ -646,17 +649,19 @@
| test.cpp:95:12:95:16 | alias | 2147483647 |
| test.cpp:97:10:97:10 | i | 65535 |
| test.cpp:97:22:97:22 | i | 32767 |
-| test.cpp:98:5:98:5 | i | 32767 |
-| test.cpp:105:7:105:7 | n | 32767 |
-| test.cpp:108:7:108:7 | n | 32767 |
-| test.cpp:109:5:109:5 | n | 32767 |
-| test.cpp:111:5:111:5 | n | 0 |
-| test.cpp:114:8:114:8 | n | 32767 |
-| test.cpp:115:5:115:5 | n | 0 |
-| test.cpp:117:5:117:5 | n | 32767 |
-| test.cpp:120:3:120:3 | n | 32767 |
-| test.cpp:120:8:120:8 | n | 32767 |
-| test.cpp:120:12:120:12 | n | 0 |
-| test.cpp:121:4:121:4 | n | 32767 |
-| test.cpp:121:8:121:8 | n | 0 |
-| test.cpp:121:12:121:12 | n | 32767 |
+| test.cpp:98:5:98:5 | i | 2147483647 |
+| test.cpp:98:9:98:9 | i | 32767 |
+| test.cpp:99:5:99:5 | i | 32767 |
+| test.cpp:106:7:106:7 | n | 32767 |
+| test.cpp:109:7:109:7 | n | 32767 |
+| test.cpp:110:5:110:5 | n | 32767 |
+| test.cpp:112:5:112:5 | n | 0 |
+| test.cpp:115:8:115:8 | n | 32767 |
+| test.cpp:116:5:116:5 | n | 0 |
+| test.cpp:118:5:118:5 | n | 32767 |
+| test.cpp:121:3:121:3 | n | 32767 |
+| test.cpp:121:8:121:8 | n | 32767 |
+| test.cpp:121:12:121:12 | n | 0 |
+| test.cpp:122:4:122:4 | n | 32767 |
+| test.cpp:122:8:122:8 | n | 0 |
+| test.cpp:122:12:122:12 | n | 32767 |
diff --git a/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected b/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected
index 01a2dfc38b3..448e0d7cc05 100644
--- a/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected
+++ b/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected
@@ -14,3 +14,4 @@
| test.cpp:24:27:24:27 | 4 | Potential buffer-overflow: 'buffer1' has size 3 not 4. |
| test.cpp:26:27:26:27 | 4 | Potential buffer-overflow: 'buffer2' has size 3 not 4. |
| test.cpp:40:22:40:27 | amount | Potential buffer-overflow: 'buffer' has size 100 not 101. |
+| test.cpp:55:13:55:21 | access to array | Potential buffer-overflow: counter 'i' <= 9 but 'buffer' has 5 elements. |
diff --git a/cpp/ql/test/query-tests/Critical/OverflowStatic/test.cpp b/cpp/ql/test/query-tests/Critical/OverflowStatic/test.cpp
index be9e14bd841..691936f3dae 100644
--- a/cpp/ql/test/query-tests/Critical/OverflowStatic/test.cpp
+++ b/cpp/ql/test/query-tests/Critical/OverflowStatic/test.cpp
@@ -46,3 +46,13 @@ void f2(char *src)
ptr = &(buffer[1]);
memcpy(ptr, src, 100); // BAD [NOT DETECTED]
}
+
+void f3() {
+ int i;
+ char buffer[5];
+ for (i=0; i<10; i++) {
+ if (i < 5) {
+ buffer[i] = 0; // GOOD [FALSE POSITIVE]
+ }
+ }
+}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Arithmetic/PointlessComparison/PointlessComparison.c b/cpp/ql/test/query-tests/Likely Bugs/Arithmetic/PointlessComparison/PointlessComparison.c
index 9fc257e3eeb..fd1bc655051 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Arithmetic/PointlessComparison/PointlessComparison.c
+++ b/cpp/ql/test/query-tests/Likely Bugs/Arithmetic/PointlessComparison/PointlessComparison.c
@@ -115,7 +115,7 @@ int twoReasons(int a, int b) {
if (a <= 0 && b > 5) {
return a < b;
}
- if (a <= 100 && b > 105) {
+ if (a <= 100 && b > 105) { // BUG [Not detected - this clause is always false]
return a > b;
}
return 0;
From d962fc4ce10aa6d27b0f4c6c3eb9873efb76f580 Mon Sep 17 00:00:00 2001
From: Anders Fugmann
Date: Thu, 2 Sep 2021 21:46:18 +0200
Subject: [PATCH 158/741] C++: Improve predicate upperBound in
SimpleRangeAnalysis
If an expression has an immediate guardPhi node, this is used as a strict upper bound
---
.../2021-08-31-range-analysis-upper-bound.md | 4 ++++
.../code/cpp/rangeanalysis/RangeSSA.qll | 15 ++++++++++---
.../cpp/rangeanalysis/SimpleRangeAnalysis.qll | 22 ++++++++++++++++---
.../SimpleRangeAnalysis/upperBound.expected | 8 +++----
.../OverflowStatic/OverflowStatic.expected | 1 -
5 files changed, 39 insertions(+), 11 deletions(-)
create mode 100644 cpp/change-notes/2021-08-31-range-analysis-upper-bound.md
diff --git a/cpp/change-notes/2021-08-31-range-analysis-upper-bound.md b/cpp/change-notes/2021-08-31-range-analysis-upper-bound.md
new file mode 100644
index 00000000000..f7ea800f719
--- /dev/null
+++ b/cpp/change-notes/2021-08-31-range-analysis-upper-bound.md
@@ -0,0 +1,4 @@
+lgtm,codescanning
+* The `SimpleRangeAnalysis` library includes information from the
+ immediate guard for determining the upper bound of a stack
+ variable for improved accuracy.
diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeSSA.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeSSA.qll
index 93dcf989590..69a397891f9 100644
--- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeSSA.qll
+++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/RangeSSA.qll
@@ -95,10 +95,19 @@ class RangeSsaDefinition extends ControlFlowNodeBase {
/**
* If this definition is a phi node corresponding to a guard,
- * then return the variable and the guard.
+ * then return the variable access and the guard.
*/
- predicate isGuardPhi(VariableAccess v, Expr guard, boolean branch) {
- guard_defn(v, guard, this, branch)
+ predicate isGuardPhi(VariableAccess va, Expr guard, boolean branch) {
+ guard_defn(va, guard, this, branch)
+ }
+
+ /**
+ * If this definition is a phi node corresponding to a guard,
+ * then return the variable guarded, the variable access and the guard.
+ */
+ predicate isGuardPhi(StackVariable v, VariableAccess va, Expr guard, boolean branch) {
+ guard_defn(va, guard, this, branch) and
+ va.getTarget() = v
}
/** Gets the primary location of this definition. */
diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
index 187641559f4..80fdf87ac05 100644
--- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
+++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
@@ -1530,6 +1530,22 @@ private predicate isUnsupportedGuardPhi(Variable v, RangeSsaDefinition phi, Vari
)
}
+/**
+ * Gets the upper bound of the expression, if the expression is guarded.
+ * An upper bound can only be found, if a guard phi node can be found, and the
+ * expression has only one immediate predecessor.
+ */
+private float getGuardedUpperBound(VariableAccess guardedAccess) {
+ exists(
+ RangeSsaDefinition def, StackVariable v, VariableAccess guardVa, Expr guard, boolean branch
+ |
+ def.isGuardPhi(v, guardVa, guard, branch) and
+ exists(unique(BasicBlock b | b = def.(BasicBlock).getAPredecessor())) and
+ guardedAccess = def.getAUse(v) and
+ result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch))
+ )
+}
+
cached
private module SimpleRangeAnalysisCached {
/**
@@ -1565,9 +1581,9 @@ private module SimpleRangeAnalysisCached {
*/
cached
float upperBound(Expr expr) {
- // Combine the upper bounds returned by getTruncatedUpperBounds into a
- // single maximum value.
- result = max(float ub | ub = getTruncatedUpperBounds(expr) | ub)
+ // Combine the upper bounds returned by getTruncatedUpperBounds and
+ // getGuardedUpperBound into a single maximum value
+ result = min([max(getTruncatedUpperBounds(expr)), getGuardedUpperBound(expr)])
}
/**
diff --git a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected
index 202662f8896..11e67670dae 100644
--- a/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected
+++ b/cpp/ql/test/library-tests/rangeanalysis/SimpleRangeAnalysis/upperBound.expected
@@ -584,9 +584,9 @@
| test.c:639:9:639:10 | ss | 2 |
| test.c:645:8:645:8 | s | 2147483647 |
| test.c:645:15:645:15 | s | 127 |
-| test.c:645:23:645:23 | s | 15 |
-| test.c:646:18:646:18 | s | 15 |
-| test.c:646:22:646:22 | s | 15 |
+| test.c:645:23:645:23 | s | 9 |
+| test.c:646:18:646:18 | s | 9 |
+| test.c:646:22:646:22 | s | 9 |
| test.c:647:9:647:14 | result | 127 |
| test.c:653:7:653:7 | i | 0 |
| test.c:654:9:654:9 | i | 2147483647 |
@@ -650,7 +650,7 @@
| test.cpp:97:10:97:10 | i | 65535 |
| test.cpp:97:22:97:22 | i | 32767 |
| test.cpp:98:5:98:5 | i | 2147483647 |
-| test.cpp:98:9:98:9 | i | 32767 |
+| test.cpp:98:9:98:9 | i | 12345 |
| test.cpp:99:5:99:5 | i | 32767 |
| test.cpp:106:7:106:7 | n | 32767 |
| test.cpp:109:7:109:7 | n | 32767 |
diff --git a/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected b/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected
index 448e0d7cc05..01a2dfc38b3 100644
--- a/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected
+++ b/cpp/ql/test/query-tests/Critical/OverflowStatic/OverflowStatic.expected
@@ -14,4 +14,3 @@
| test.cpp:24:27:24:27 | 4 | Potential buffer-overflow: 'buffer1' has size 3 not 4. |
| test.cpp:26:27:26:27 | 4 | Potential buffer-overflow: 'buffer2' has size 3 not 4. |
| test.cpp:40:22:40:27 | amount | Potential buffer-overflow: 'buffer' has size 100 not 101. |
-| test.cpp:55:13:55:21 | access to array | Potential buffer-overflow: counter 'i' <= 9 but 'buffer' has 5 elements. |
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 159/741] 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 160/741] 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 6ede08e3c95ae5c09fbd2cd28e3bc0d86c3d13fb Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 10:53:24 +0200
Subject: [PATCH 161/741] Remove dead code
---
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 1 -
1 file changed, 1 deletion(-)
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index 58af3d8c8af..814f25bac2b 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -11,7 +11,6 @@ import ExternalAPI
from int Usages, string jarname
where
- jarname = any(ExternalAPI api).jarContainer() and
Usages =
strictcount(Call c, ExternalAPI a |
c.getCallee() = a and
From 2edb32f3447dd060bf5f986e3ff05311bf5947ad Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 10:59:35 +0200
Subject: [PATCH 162/741] Fix naming
---
java/ql/src/Telemetry/ExternalAPI.qll | 2 +-
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 6 +++---
java/ql/src/Telemetry/SupportedExternalSinks.ql | 2 +-
java/ql/src/Telemetry/SupportedExternalSources.ql | 2 +-
java/ql/src/Telemetry/SupportedExternalTaint.ql | 2 +-
java/ql/src/Telemetry/UnsupportedExternalAPIs.ql | 2 +-
6 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index 89bf49c2d07..71b76cd56b2 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -16,7 +16,7 @@ class ExternalAPI extends Callable {
/**
* Gets information about the external API in the form expected by the CSV modeling framework.
*/
- string asCSV(ExternalAPI api) {
+ string asCsv(ExternalAPI api) {
result =
api.getDeclaringType().getPackage() + ";?;" + api.getDeclaringType().getSourceDeclaration() +
";" + api.getName() + ";" + paramsString(api)
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index 814f25bac2b..a5785e16917 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -9,13 +9,13 @@
import java
import ExternalAPI
-from int Usages, string jarname
+from int usages, string jarname
where
- Usages =
+ usages =
strictcount(Call c, ExternalAPI a |
c.getCallee() = a and
not c.getFile() instanceof GeneratedFile and
a.jarContainer() = jarname and
not a.isTestLibrary()
)
-select jarname, Usages order by Usages desc
+select jarname, usages order by usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalSinks.ql b/java/ql/src/Telemetry/SupportedExternalSinks.ql
index adebe3f4533..a3d96c82ca9 100644
--- a/java/ql/src/Telemetry/SupportedExternalSinks.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSinks.ql
@@ -15,7 +15,7 @@ from ExternalAPI api
where
not api.isTestLibrary() and
supportKind(api) = "sink"
-select api.asCSV(api) as csv,
+select api.asCsv(api) as csv,
strictcount(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
diff --git a/java/ql/src/Telemetry/SupportedExternalSources.ql b/java/ql/src/Telemetry/SupportedExternalSources.ql
index d8fd21b8440..e5ff28d25a3 100644
--- a/java/ql/src/Telemetry/SupportedExternalSources.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSources.ql
@@ -15,7 +15,7 @@ from ExternalAPI api
where
not api.isTestLibrary() and
supportKind(api) = "source"
-select api.asCSV(api) as csv,
+select api.asCsv(api) as csv,
strictcount(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
diff --git a/java/ql/src/Telemetry/SupportedExternalTaint.ql b/java/ql/src/Telemetry/SupportedExternalTaint.ql
index a9da59823ad..046aa46d53d 100644
--- a/java/ql/src/Telemetry/SupportedExternalTaint.ql
+++ b/java/ql/src/Telemetry/SupportedExternalTaint.ql
@@ -15,7 +15,7 @@ from ExternalAPI api
where
not api.isTestLibrary() and
supportKind(api) = ["summary", "taint-preserving"]
-select api.asCSV(api) as csv,
+select api.asCsv(api) as csv,
strictcount(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
diff --git a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
index 4a97e139a81..5f14c7aeab8 100644
--- a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
+++ b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
@@ -15,7 +15,7 @@ from ExternalAPI api
where
not api.isTestLibrary() and
not api.isSupported()
-select api.asCSV(api) as csv,
+select api.asCsv(api) as csv,
strictcount(Call c |
c.getCallee() = api and
not c.getFile() instanceof GeneratedFile
From d2f833d02cdb38f19cce3452eab1af8e7f712815 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:13:12 +0100
Subject: [PATCH 163/741] 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 164/741] 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 165/741] 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 89ce04dcb943f3e0ebc2e06fc63067bf14ec5a67 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 11:25:59 +0200
Subject: [PATCH 166/741] Pull usage count into where clause
---
java/ql/src/Telemetry/SupportedExternalSources.ql | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/java/ql/src/Telemetry/SupportedExternalSources.ql b/java/ql/src/Telemetry/SupportedExternalSources.ql
index e5ff28d25a3..48208f69adb 100644
--- a/java/ql/src/Telemetry/SupportedExternalSources.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSources.ql
@@ -11,12 +11,13 @@ import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
-from ExternalAPI api
+from ExternalAPI api, int usages
where
not api.isTestLibrary() and
- supportKind(api) = "source"
-select api.asCsv(api) as csv,
- strictcount(Call c |
- c.getCallee() = api and
- not c.getFile() instanceof GeneratedFile
- ) as Usages order by Usages desc
+ supportKind(api) = "source" and
+ usages =
+ strictcount(Call c |
+ c.getCallee() = api and
+ not c.getFile() instanceof GeneratedFile
+ )
+select api.asCsv(api) as csv, usages order by usages desc
From 7d3131ca497f03765c1e587d7eab2c80e80dc8e4 Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 11:32:14 +0200
Subject: [PATCH 167/741] Move usage count into where clause
---
java/ql/src/Telemetry/ExternalAPI.qll | 4 ++--
java/ql/src/Telemetry/SupportedExternalSinks.ql | 15 ++++++++-------
java/ql/src/Telemetry/SupportedExternalSources.ql | 2 +-
java/ql/src/Telemetry/SupportedExternalTaint.ql | 15 ++++++++-------
java/ql/src/Telemetry/UnsupportedExternalAPIs.ql | 15 ++++++++-------
5 files changed, 27 insertions(+), 24 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index 71b76cd56b2..3fee012ee6f 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -10,7 +10,7 @@ private import semmle.code.java.dataflow.ExternalFlow
class ExternalAPI extends Callable {
ExternalAPI() { not this.fromSource() }
- /** Holds true if this API is part of a common testing library or framework */
+ /** Holds if this API is part of a common testing library or framework */
predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
/**
@@ -22,7 +22,7 @@ class ExternalAPI extends Callable {
";" + api.getName() + ";" + paramsString(api)
}
- /** Holds true if this API is not yet supported by existing CodeQL libraries */
+ /** Holds if this API is not yet supported by existing CodeQL libraries */
predicate isSupported() { not supportKind(this) = "?" }
/**
diff --git a/java/ql/src/Telemetry/SupportedExternalSinks.ql b/java/ql/src/Telemetry/SupportedExternalSinks.ql
index a3d96c82ca9..51778acb4d1 100644
--- a/java/ql/src/Telemetry/SupportedExternalSinks.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSinks.ql
@@ -11,12 +11,13 @@ import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
-from ExternalAPI api
+from ExternalAPI api, int usages
where
not api.isTestLibrary() and
- supportKind(api) = "sink"
-select api.asCsv(api) as csv,
- strictcount(Call c |
- c.getCallee() = api and
- not c.getFile() instanceof GeneratedFile
- ) as Usages order by Usages desc
+ supportKind(api) = "sink" and
+ usages =
+ strictcount(Call c |
+ c.getCallee().getSourceDeclaration() = api and
+ not c.getFile() instanceof GeneratedFile
+ )
+select api.asCsv(api) as csv, usages order by usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalSources.ql b/java/ql/src/Telemetry/SupportedExternalSources.ql
index 48208f69adb..7bb76e57602 100644
--- a/java/ql/src/Telemetry/SupportedExternalSources.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSources.ql
@@ -17,7 +17,7 @@ where
supportKind(api) = "source" and
usages =
strictcount(Call c |
- c.getCallee() = api and
+ c.getCallee().getSourceDeclaration() = api and
not c.getFile() instanceof GeneratedFile
)
select api.asCsv(api) as csv, usages order by usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalTaint.ql b/java/ql/src/Telemetry/SupportedExternalTaint.ql
index 046aa46d53d..8de007ace9f 100644
--- a/java/ql/src/Telemetry/SupportedExternalTaint.ql
+++ b/java/ql/src/Telemetry/SupportedExternalTaint.ql
@@ -11,12 +11,13 @@ import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
-from ExternalAPI api
+from ExternalAPI api, int usages
where
not api.isTestLibrary() and
- supportKind(api) = ["summary", "taint-preserving"]
-select api.asCsv(api) as csv,
- strictcount(Call c |
- c.getCallee() = api and
- not c.getFile() instanceof GeneratedFile
- ) as Usages order by Usages desc
+ supportKind(api) = ["summary", "taint-preserving"] and
+ usages =
+ strictcount(Call c |
+ c.getCallee().getSourceDeclaration() = api and
+ not c.getFile() instanceof GeneratedFile
+ )
+select api.asCsv(api) as csv, usages order by usages desc
diff --git a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
index 5f14c7aeab8..cf484929752 100644
--- a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
+++ b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
@@ -11,12 +11,13 @@ import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
-from ExternalAPI api
+from ExternalAPI api, int usages
where
not api.isTestLibrary() and
- not api.isSupported()
-select api.asCsv(api) as csv,
- strictcount(Call c |
- c.getCallee() = api and
- not c.getFile() instanceof GeneratedFile
- ) as Usages order by Usages desc
+ not api.isSupported() and
+ usages =
+ strictcount(Call c |
+ c.getCallee().getSourceDeclaration() = api and
+ not c.getFile() instanceof GeneratedFile
+ )
+select api.asCsv(api) as csv, usages order by usages desc
From 4b02e266fd7fe09883a27cf7251ec66651eb092c Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 11:37:39 +0200
Subject: [PATCH 168/741] Fix test as we support explicit collection types
---
.../UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
index 22cac276be6..89637424297 100644
--- a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
+++ b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
@@ -1,6 +1,4 @@
| java.io;?;PrintStream;println;(Object) | 3 |
| java.lang;?;Object;Object;() | 2 |
| java.lang;?;String;length;() | 1 |
-| java.time;?;Duration;ofMillis;(long) | 1 |
-| java.util;?;ArrayList;ArrayList<>;() | 1 |
-| java.util;?;HashMap;HashMap;() | 1 |
+| java.time;?;Duration;ofMillis;(long) | 1 |
\ No newline at end of file
From 35b0e833708080d736fa77db2e2814f581cf8ca5 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:52:05 +0100
Subject: [PATCH 169/741] 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 9ed14b438e0b86bc4e4c2f0b3429dee4afa00a6c Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 11:53:18 +0200
Subject: [PATCH 170/741] Use readble format for APIs
---
java/ql/src/Telemetry/ExternalAPI.qll | 6 +++---
java/ql/src/Telemetry/SupportedExternalSinks.ql | 2 +-
java/ql/src/Telemetry/SupportedExternalSources.ql | 2 +-
java/ql/src/Telemetry/SupportedExternalTaint.ql | 2 +-
java/ql/src/Telemetry/UnsupportedExternalAPIs.ql | 2 +-
.../SupportedExternalSinks.expected | 4 ++--
.../SupportedExternalSources.expected | 2 +-
.../SupportedExternalTaint.expected | 4 ++--
.../UnsupportedExternalAPIs.expected | 8 ++++----
9 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index 3fee012ee6f..bf3630e7358 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -16,10 +16,10 @@ class ExternalAPI extends Callable {
/**
* Gets information about the external API in the form expected by the CSV modeling framework.
*/
- string asCsv(ExternalAPI api) {
+ string asHumanReadbleString(ExternalAPI api) {
result =
- api.getDeclaringType().getPackage() + ";?;" + api.getDeclaringType().getSourceDeclaration() +
- ";" + api.getName() + ";" + paramsString(api)
+ api.getDeclaringType().getPackage() + "." + api.getDeclaringType().getSourceDeclaration() +
+ "#" + api.getName() + paramsString(api)
}
/** Holds if this API is not yet supported by existing CodeQL libraries */
diff --git a/java/ql/src/Telemetry/SupportedExternalSinks.ql b/java/ql/src/Telemetry/SupportedExternalSinks.ql
index 51778acb4d1..7b8dffc392c 100644
--- a/java/ql/src/Telemetry/SupportedExternalSinks.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSinks.ql
@@ -20,4 +20,4 @@ where
c.getCallee().getSourceDeclaration() = api and
not c.getFile() instanceof GeneratedFile
)
-select api.asCsv(api) as csv, usages order by usages desc
+select api.asHumanReadbleString(api) as apiname, usages order by usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalSources.ql b/java/ql/src/Telemetry/SupportedExternalSources.ql
index 7bb76e57602..2c039bdbece 100644
--- a/java/ql/src/Telemetry/SupportedExternalSources.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSources.ql
@@ -20,4 +20,4 @@ where
c.getCallee().getSourceDeclaration() = api and
not c.getFile() instanceof GeneratedFile
)
-select api.asCsv(api) as csv, usages order by usages desc
+select api.asHumanReadbleString(api) as apiname, usages order by usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalTaint.ql b/java/ql/src/Telemetry/SupportedExternalTaint.ql
index 8de007ace9f..d89deacf724 100644
--- a/java/ql/src/Telemetry/SupportedExternalTaint.ql
+++ b/java/ql/src/Telemetry/SupportedExternalTaint.ql
@@ -20,4 +20,4 @@ where
c.getCallee().getSourceDeclaration() = api and
not c.getFile() instanceof GeneratedFile
)
-select api.asCsv(api) as csv, usages order by usages desc
+select api.asHumanReadbleString(api) as apiname, usages order by usages desc
diff --git a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
index cf484929752..c79bed20fe9 100644
--- a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
+++ b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
@@ -20,4 +20,4 @@ where
c.getCallee().getSourceDeclaration() = api and
not c.getFile() instanceof GeneratedFile
)
-select api.asCsv(api) as csv, usages order by usages desc
+select api.asHumanReadbleString(api) as apiname, usages order by usages desc
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
index 3d595aa5353..6cb849601d5 100644
--- a/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSinks/SupportedExternalSinks.expected
@@ -1,2 +1,2 @@
-| java.io;?;FileWriter;FileWriter;(File) | 1 |
-| java.net;?;URL;openStream;() | 1 |
+| java.io.FileWriter#FileWriter(File) | 1 |
+| java.net.URL#openStream() | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected
index 26d2efed5e5..f83e018ea72 100644
--- a/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalSources/SupportedExternalSources.expected
@@ -1 +1 @@
-| java.net;?;URLConnection;getInputStream;() | 1 |
+| java.net.URLConnection#getInputStream() | 1 |
\ No newline at end of file
diff --git a/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected
index 18be8556005..54f26d49a98 100644
--- a/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected
+++ b/java/ql/test/query-tests/Telemetry/SupportedExternalTaint/SupportedExternalTaint.expected
@@ -1,2 +1,2 @@
-| java.lang;?;StringBuilder;append;(String) | 1 |
-| java.lang;?;StringBuilder;toString;() | 1 |
+| java.lang.StringBuilder#append(String) | 1 |
+| java.lang.StringBuilder#toString() | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
index 89637424297..e89dc4ba532 100644
--- a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
+++ b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
@@ -1,4 +1,4 @@
-| java.io;?;PrintStream;println;(Object) | 3 |
-| java.lang;?;Object;Object;() | 2 |
-| java.lang;?;String;length;() | 1 |
-| java.time;?;Duration;ofMillis;(long) | 1 |
\ No newline at end of file
+| java.io.PrintStream#println(Object) | 3 |
+| java.lang.Object#Object() | 2 |
+| java.lang.String#length() | 1 |
+| java.time.Duration#ofMillis(long) | 1 |
From cd646c819dc7e4349f90fe75c4e91f74b764f5d1 Mon Sep 17 00:00:00 2001
From: Philip Ginsbach
Date: Fri, 3 Sep 2021 10:55:03 +0100
Subject: [PATCH 171/741] 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 ab5c1d6bddd0a9a15b44bad4a4d8350ca707762c Mon Sep 17 00:00:00 2001
From: Benjamin Muskalla
Date: Fri, 3 Sep 2021 13:38:01 +0200
Subject: [PATCH 172/741] Rework filter to exclude simple constructors
---
java/ql/src/Telemetry/APIUsage.qll | 48 -----------------
java/ql/src/Telemetry/ExternalAPI.qll | 53 +++++++++++++++++--
java/ql/src/Telemetry/ExternalLibraryUsage.ql | 2 +-
.../src/Telemetry/SupportedExternalSinks.ql | 5 +-
.../src/Telemetry/SupportedExternalSources.ql | 5 +-
.../src/Telemetry/SupportedExternalTaint.ql | 5 +-
.../src/Telemetry/UnsupportedExternalAPIs.ql | 3 +-
.../ExternalLibraryUsage.expected | 2 +-
.../UnsupportedExternalAPIs.expected | 1 -
9 files changed, 57 insertions(+), 67 deletions(-)
delete mode 100644 java/ql/src/Telemetry/APIUsage.qll
diff --git a/java/ql/src/Telemetry/APIUsage.qll b/java/ql/src/Telemetry/APIUsage.qll
deleted file mode 100644
index 23d7a6ad49f..00000000000
--- a/java/ql/src/Telemetry/APIUsage.qll
+++ /dev/null
@@ -1,48 +0,0 @@
-/** Provides classes and predicates related to support coverage of external libraries. */
-
-import java
-private import semmle.code.java.dataflow.FlowSources
-
-/**
- * Gets the coverage support for the given `Callable`. If the `Callable` is not supported, returns "?".
- */
-string supportKind(Callable api) {
- if api instanceof TaintPreservingCallable
- then result = "taint-preserving"
- else
- if summaryCall(api)
- then result = "summary"
- else
- if sink(api)
- then result = "sink"
- else
- if source(api)
- then result = "source"
- else result = "?"
-}
-
-private predicate summaryCall(Callable api) {
- summaryModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _, _)
-}
-
-private predicate sink(Callable api) {
- sinkModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
-}
-
-private predicate source(Callable api) {
- sourceModel(packageName(api), typeName(api), _, api.getName(), _, _, _, _)
- or
- exists(Call call, DataFlow::Node arg |
- call.getCallee() = api and
- [call.getAnArgument(), call.getQualifier()] = arg.asExpr() and
- arg instanceof RemoteFlowSource
- )
-}
-
-private string packageName(Callable api) {
- result = api.getCompilationUnit().getPackage().toString()
-}
-
-private string typeName(Callable api) {
- result = api.getDeclaringType().getAnAncestor().getSourceDeclaration().toString()
-}
diff --git a/java/ql/src/Telemetry/ExternalAPI.qll b/java/ql/src/Telemetry/ExternalAPI.qll
index bf3630e7358..23c61b01236 100644
--- a/java/ql/src/Telemetry/ExternalAPI.qll
+++ b/java/ql/src/Telemetry/ExternalAPI.qll
@@ -1,8 +1,13 @@
/** Provides classes and predicates related to handling APIs from external libraries. */
private import java
-private import APIUsage
+private import semmle.code.java.dataflow.DataFlow
+private import semmle.code.java.dataflow.DataFlow
private import semmle.code.java.dataflow.ExternalFlow
+private import semmle.code.java.dataflow.FlowSources
+private import semmle.code.java.dataflow.FlowSummary
+private import semmle.code.java.dataflow.internal.DataFlowPrivate
+private import semmle.code.java.dataflow.TaintTracking
/**
* An external API from either the Java Standard Library or a 3rd party library.
@@ -10,8 +15,16 @@ private import semmle.code.java.dataflow.ExternalFlow
class ExternalAPI extends Callable {
ExternalAPI() { not this.fromSource() }
+ /** Holds if this API is a candidate worth supporting */
+ predicate isWorthSupporting() { not isTestLibrary() and not isParameterlessConstructor() }
+
+ /** Holds if this API is is a constructor without parameters */
+ predicate isParameterlessConstructor() {
+ this instanceof Constructor and this.getNumberOfParameters() = 0
+ }
+
/** Holds if this API is part of a common testing library or framework */
- predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
+ private predicate isTestLibrary() { getDeclaringType() instanceof TestLibrary }
/**
* Gets information about the external API in the form expected by the CSV modeling framework.
@@ -22,9 +35,6 @@ class ExternalAPI extends Callable {
"#" + api.getName() + paramsString(api)
}
- /** Holds if this API is not yet supported by existing CodeQL libraries */
- predicate isSupported() { not supportKind(this) = "?" }
-
/**
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
*/
@@ -33,6 +43,39 @@ class ExternalAPI extends Callable {
private string containerAsJar(Container container) {
if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar"
}
+
+ /** Gets a node that is an input to a call to this API. */
+ private DataFlow::Node getAnInput() {
+ exists(Call call | call.getCallee().getSourceDeclaration() = this |
+ result.asExpr().(Argument).getCall() = call or
+ result.(ArgumentNode).getCall() = call
+ )
+ }
+
+ /** Gets a node that is an output from a call to this API. */
+ private DataFlow::Node getAnOutput() {
+ exists(Call call | call.getCallee().getSourceDeclaration() = this |
+ result.asExpr() = call or
+ result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall() = call
+ )
+ }
+
+ /** Holds if this API has a supported summary. */
+ predicate hasSummary() {
+ this instanceof SummarizedCallable or
+ TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
+ }
+
+ /** Holds if this API is a known source. */
+ predicate isSource() {
+ this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
+ }
+
+ /** Holds if this API is a known sink. */
+ predicate isSink() { sinkNode(this.getAnInput(), _) }
+
+ /** Holds if this API is supported by existing CodeQL libraries, that is, it is either a recognized source or sink or has a flow summary. */
+ predicate isSupported() { hasSummary() or isSource() or isSink() }
}
private class TestLibrary extends RefType {
diff --git a/java/ql/src/Telemetry/ExternalLibraryUsage.ql b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
index a5785e16917..ad6bf6a91b1 100644
--- a/java/ql/src/Telemetry/ExternalLibraryUsage.ql
+++ b/java/ql/src/Telemetry/ExternalLibraryUsage.ql
@@ -16,6 +16,6 @@ where
c.getCallee() = a and
not c.getFile() instanceof GeneratedFile and
a.jarContainer() = jarname and
- not a.isTestLibrary()
+ a.isWorthSupporting()
)
select jarname, usages order by usages desc
diff --git a/java/ql/src/Telemetry/SupportedExternalSinks.ql b/java/ql/src/Telemetry/SupportedExternalSinks.ql
index 7b8dffc392c..2d6db91939f 100644
--- a/java/ql/src/Telemetry/SupportedExternalSinks.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSinks.ql
@@ -7,14 +7,13 @@
*/
import java
-import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
from ExternalAPI api, int usages
where
- not api.isTestLibrary() and
- supportKind(api) = "sink" and
+ api.isWorthSupporting() and
+ api.isSink() and
usages =
strictcount(Call c |
c.getCallee().getSourceDeclaration() = api and
diff --git a/java/ql/src/Telemetry/SupportedExternalSources.ql b/java/ql/src/Telemetry/SupportedExternalSources.ql
index 2c039bdbece..4c09aa299da 100644
--- a/java/ql/src/Telemetry/SupportedExternalSources.ql
+++ b/java/ql/src/Telemetry/SupportedExternalSources.ql
@@ -7,14 +7,13 @@
*/
import java
-import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
from ExternalAPI api, int usages
where
- not api.isTestLibrary() and
- supportKind(api) = "source" and
+ api.isWorthSupporting() and
+ api.isSource() and
usages =
strictcount(Call c |
c.getCallee().getSourceDeclaration() = api and
diff --git a/java/ql/src/Telemetry/SupportedExternalTaint.ql b/java/ql/src/Telemetry/SupportedExternalTaint.ql
index d89deacf724..0430f63c537 100644
--- a/java/ql/src/Telemetry/SupportedExternalTaint.ql
+++ b/java/ql/src/Telemetry/SupportedExternalTaint.ql
@@ -7,14 +7,13 @@
*/
import java
-import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
from ExternalAPI api, int usages
where
- not api.isTestLibrary() and
- supportKind(api) = ["summary", "taint-preserving"] and
+ api.isWorthSupporting() and
+ api.hasSummary() and
usages =
strictcount(Call c |
c.getCallee().getSourceDeclaration() = api and
diff --git a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
index c79bed20fe9..09cadfd0f2e 100644
--- a/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
+++ b/java/ql/src/Telemetry/UnsupportedExternalAPIs.ql
@@ -7,13 +7,12 @@
*/
import java
-import APIUsage
import ExternalAPI
import semmle.code.java.GeneratedFiles
from ExternalAPI api, int usages
where
- not api.isTestLibrary() and
+ api.isWorthSupporting() and
not api.isSupported() and
usages =
strictcount(Call c |
diff --git a/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected b/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
index dbba1054b8c..d654e05d3c7 100644
--- a/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
+++ b/java/ql/test/query-tests/Telemetry/ExternalLibraryUsage/ExternalLibraryUsage.expected
@@ -1 +1 @@
-| rt.jar | 3 |
+| rt.jar | 1 |
diff --git a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
index e89dc4ba532..6171ccede3d 100644
--- a/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
+++ b/java/ql/test/query-tests/Telemetry/UnsupportedExternalAPIs/UnsupportedExternalAPIs.expected
@@ -1,4 +1,3 @@
| java.io.PrintStream#println(Object) | 3 |
-| java.lang.Object#Object() | 2 |
| java.lang.String#length() | 1 |
| java.time.Duration#ofMillis(long) | 1 |
From c6eb795e76aaaa728eb3c61d5e0b1eef533c6284 Mon Sep 17 00:00:00 2001
From: yoff
Date: Fri, 3 Sep 2021 14:23:57 +0200
Subject: [PATCH 173/741] 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 174/741] 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 175/741] 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 286c10235824086e94c77be59c2db6631e43c5c7 Mon Sep 17 00:00:00 2001
From: Andrew Eisenberg
Date: Fri, 3 Sep 2021 12:49:38 -0700
Subject: [PATCH 176/741] Update the docs about qlpacks
This is a first pass to fix obvious holes and outdated information, but
we should rethink these docs completely.
---
docs/codeql/codeql-cli/about-ql-packs.rst | 66 +++++++++++++++++------
1 file changed, 51 insertions(+), 15 deletions(-)
diff --git a/docs/codeql/codeql-cli/about-ql-packs.rst b/docs/codeql/codeql-cli/about-ql-packs.rst
index 2cffc9942c0..8074958881a 100644
--- a/docs/codeql/codeql-cli/about-ql-packs.rst
+++ b/docs/codeql/codeql-cli/about-ql-packs.rst
@@ -81,7 +81,7 @@ The following properties are supported in ``qlpack.yml`` files.
- All packs
- A version number for this QL pack. This must be a valid semantic version that meets the `SemVer v2.0.0 specification `__.
* - ``libraryPathDependencies``
- - ``codeql-javascript``
+ - ``codeql/javascript-all``
- Optional
- The names of any QL packs that this QL pack depends on, as a sequence. This gives the pack access to any libraries, database schema, and query suites defined in the dependency.
* - ``suites``
@@ -104,7 +104,18 @@ The following properties are supported in ``qlpack.yml`` files.
- ``.``
- Packs with upgrades
- The path to a directory within the pack that contains upgrade scripts, defined relative to the pack directory. The ``database upgrade`` action uses these scripts to update databases that were created by an older version of an extractor so they're compatible with the current extractor (see `Upgrade scripts for a language <#upgrade-scripts-for-a-language>`__ below.)
+ * - ``dependencies``
+ - .. code-block:: yaml
+ dependencies:
+ codeql/cpp-all: ^0.0.2
+
+ - Packs that define CodeQL package dependencies on other packs
+ - A map from pack references to the semantic version range that is compatible with this pack.
+ * - ``defaultSuiteFile``
+ - ``defaultSuiteFile: cpp-code-scanning.qls``
+ - Packs that export a set of default queries to run
+ - The path to a query suite file containing all of the queries that are run by default when this pack is passed to the ``codeql database analyze` command.
.. _custom-ql-packs:
@@ -138,7 +149,7 @@ and libraries may contain:
libraryPathDependencies: codeql/cpp-all
suites: my-custom-suites
-where ``codeql-cpp`` is the name of the QL pack for C/C++ analysis included in
+where ``codeql/cpp-all`` is the name of the QL pack for C/C++ analysis included in
the CodeQL repository.
.. pull-quote::
@@ -166,38 +177,61 @@ For more information about running tests, see ":doc:`Testing custom queries
Examples of QL packs in the CodeQL repository
---------------------------------------------
-Each of the languages in the CodeQL repository has three main QL packs:
+Each of the languages in the CodeQL repository has four main QL packs:
-- Core QL pack for the language, with the :ref:`database schema `
- used by the language, CodeQL libraries, and queries at ``ql//ql/src``
+- Core library pack for the language, with the :ref:`database schema `
+ used by the language, and CodeQL libraries, and queries at ``ql//ql/lib``
+- Core query pack for the language that includes the default queries for the language, along
+ with their query suites at ``ql//ql/src``
- Tests for the core language libraries and queries pack at ``ql//ql/test``
- Upgrade scripts for the language at ``ql//upgrades``
-Core QL pack
+Core library pack
~~~~~~~~~~~~
-The ``qlpack.yml`` file for a core QL pack uses the following properties:
-``name``, ``version``, ``dbscheme``, and ``suites``.
+The ``qlpack.yml`` file for a core library pack uses the following properties:
+``name``, ``version``, ``dbscheme``.
The ``dbscheme`` property should only be defined in the core QL
pack for a language.
-For example, the ``qlpack.yml`` file for `C/C++ analysis
-`__
+For example, the ``qlpack.yml`` file for `C/C++ analysis libraries
+`__
contains:
.. code-block:: yaml
- name: codeql-cpp
+ name: codeql/cpp-all
version: 0.0.0
dbscheme: semmlecode.cpp.dbscheme
+ dependencies:
+ codeql/cpp-upgrades: "*"
+
+Core query pack
+~~~~~~~~~~~~
+
+The ``qlpack.yml`` file for a core query pack uses the following properties:
+``name``, ``version``, ``suites``, ``defaultSuiteFile``, ``dependencies`` .
+
+For example, the ``qlpack.yml`` file for `C/C++ analysis queries
+`__
+contains:
+
+.. code-block:: yaml
+
+ name: codeql/cpp-queries
+ version: 0.0.0
suites: codeql-suites
+ defaultSuiteFile: codeql-suites/cpp-code-scanning.qls
+ dependencies:
+ codeql/cpp-all: "*"
+ codeql/suite-helpers: "*"
Tests for the core QL pack
~~~~~~~~~~~~~~~~~~~~~~~~~~
The ``qlpack.yml`` file for the tests for the core QL packs use the following
properties: ``name``, ``version``, and ``libraryPathDependencies``.
-The ``libraryPathDependencies`` always specifies the core QL pack.
+The ``dependencies`` always specifies the core QL pack.
For example, the ``qlpack.yml`` file for `C/C++ analysis tests
`__
@@ -205,9 +239,11 @@ contains:
.. code-block:: yaml
- name: codeql-cpp-tests
+ name: codeql/cpp-tests
version: 0.0.0
- libraryPathDependencies: codeql/cpp-all
+ dependencies:
+ codeql/cpp-all: "*"
+ codeql/cpp-queries: "*"
Notice that, unlike the example QL pack for custom tests, this file does not define
an ``extractor`` or ``tests`` property. These properties have been added to
@@ -229,5 +265,5 @@ contains:
.. code-block:: yaml
- name: codeql-cpp-upgrades
+ name: codeql/cpp-upgrades
upgrades: .
From 8b0d5a2e7bc864689ff00e366c8abe23f6de37e7 Mon Sep 17 00:00:00 2001
From: ihsinme
Date: Sun, 5 Sep 2021 22:46:37 +0300
Subject: [PATCH 177/741] 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 e4d22ea6284a17f0a4cc57f6f9c1f5388c191fdb Mon Sep 17 00:00:00 2001
From: Anders Fugmann
Date: Mon, 6 Sep 2021 10:31:32 +0200
Subject: [PATCH 178/741] C++: Add comment on why getGuardedUpperBound must
have exactly one predecessor
---
.../semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
index 80fdf87ac05..a803f4f1941 100644
--- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
+++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll
@@ -1540,6 +1540,13 @@ private float getGuardedUpperBound(VariableAccess guardedAccess) {
RangeSsaDefinition def, StackVariable v, VariableAccess guardVa, Expr guard, boolean branch
|
def.isGuardPhi(v, guardVa, guard, branch) and
+ // If the basic block for the variable access being examined has
+ // more than one predecessor, the guard phi node could originate
+ // from one of the predecessors. This is because the guard phi
+ // node is attached to the block at the end of the edge and not on
+ // the actual edge. It is therefore possible to determine which
+ // edge the guard phi node belongs to. The predicate below ensures
+ // that there is one predecessor, albeit somewhat conservative.
exists(unique(BasicBlock b | b = def.(BasicBlock).getAPredecessor())) and
guardedAccess = def.getAUse(v) and
result = max(float ub | upperBoundFromGuard(guard, guardVa, ub, branch))
From 8d4af3ad81ae8056ac9af4a6e20d53c4f04aeed8 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 12 May 2021 16:49:43 +0200
Subject: [PATCH 179/741] 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 180/741] 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 181/741] 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 182/741] 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 183/741] 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 184/741] 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