From ead687da06543861b465ab110aad13d5a995b15c Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Tue, 21 Jan 2020 15:28:01 +0100
Subject: [PATCH 001/429] Python: Add false positive test example for issue
#2652.
---
.../Variables/unused/UnusedLocalVariable.expected | 1 +
.../Variables/unused/type_annotation_fp.py | 11 +++++++++++
2 files changed, 12 insertions(+)
create mode 100644 python/ql/test/query-tests/Variables/unused/type_annotation_fp.py
diff --git a/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected
index a902cad04cb..339a74432bc 100644
--- a/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected
+++ b/python/ql/test/query-tests/Variables/unused/UnusedLocalVariable.expected
@@ -1,3 +1,4 @@
+| 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. |
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
new file mode 100644
index 00000000000..72293f232e5
--- /dev/null
+++ b/python/ql/test/query-tests/Variables/unused/type_annotation_fp.py
@@ -0,0 +1,11 @@
+# FP Type annotation counts as redefinition
+# See https://github.com/Semmle/ql/issues/2652
+
+def type_annotation(x):
+ foo = 5
+ if x:
+ foo : int
+ do_stuff_with(foo)
+ else:
+ foo : float
+ do_other_stuff_with(foo)
From 4ec78d04f8cc8f869af959556e9537bf651fd7e5 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Mon, 21 Dec 2020 00:15:15 +0000
Subject: [PATCH 002/429] Insecure LDAP authentication
---
.../CWE/CWE-522/InsecureLdapAuth.java | 24 +++
.../CWE/CWE-522/InsecureLdapAuth.qhelp | 32 ++++
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 153 ++++++++++++++++++
.../CWE-522/InsecureLdapAuth.expected | 15 ++
.../security/CWE-522/InsecureLdapAuth.java | 92 +++++++++++
.../security/CWE-522/InsecureLdapAuth.qlref | 1 +
6 files changed, 317 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.qlref
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
new file mode 100644
index 00000000000..33764506a1b
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
@@ -0,0 +1,24 @@
+public class InsecureLdapAuth {
+ /** LDAP authentication */
+ public DirContext ldapAuth(String ldapUserName, String password) {
+ {
+ // BAD: LDAP authentication in cleartext
+ String ldapUrl = "ldap://ad.your-server.com:389";
+ }
+
+ {
+ // GOOD: LDAPS authentication over SSL
+ String ldapUrl = "ldaps://ad.your-server.com:636";
+ }
+
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext dirContext = new InitialDirContext(environment);
+ return dirContext;
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
new file mode 100644
index 00000000000..9b7193bf52f
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
@@ -0,0 +1,32 @@
+
+
+
+
+
When using the Java LDAP API to perform LDAPv3-style extended operations and controls, a context with connection properties including user credentials is started. Transmission of LDAP credentials in cleartext allows remote attackers to obtain sensitive information by sniffing the network.
+
+
+
+
Use LDAPS to send credentials through SSL or use SASL authentication.
+
+
+
+
The following example shows two ways of using LDAP authentication. In the 'BAD' case, the credentials are transmitted in cleartext. In the 'GOOD' case, the credentials are transmitted over SSL.
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
new file mode 100644
index 00000000000..663f4110524
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -0,0 +1,153 @@
+/**
+ * @name Insecure LDAP authentication
+ * @description LDAP authentication with credentials sent in cleartext.
+ * @kind path-problem
+ * @id java/insecure-ldap-auth
+ * @tags security
+ * external/cwe-522
+ * external/cwe-319
+ */
+
+import java
+import semmle.code.java.frameworks.Jndi
+import semmle.code.java.dataflow.TaintTracking
+import DataFlow::PathGraph
+
+/**
+ * Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
+ */
+private 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\\]?(?:[:/?#].*)?"
+}
+
+/**
+ * String of LDAP connections not in private domains.
+ */
+class LdapStringLiteral extends StringLiteral {
+ LdapStringLiteral() {
+ // Match connection strings with the LDAP protocol and without private IP addresses to reduce false positives.
+ exists(string s | this.getRepresentedString() = s |
+ s.regexpMatch("(?i)ldap://[\\[a-zA-Z0-9].*") and
+ not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
+ )
+ }
+}
+
+/** The interface `javax.naming.Context`. */
+class TypeNamingContext extends Interface {
+ TypeNamingContext() { this.hasQualifiedName("javax.naming", "Context") }
+}
+
+/** The class `java.util.Hashtable`. */
+class TypeHashtable extends Class {
+ TypeHashtable() { this.getSourceDeclaration().hasQualifiedName("java.util", "Hashtable") }
+}
+
+/**
+ * Holds if a non-private LDAP string is concatenated from both protocol and host.
+ */
+predicate concatLdapString(Expr protocol, Expr host) {
+ (
+ protocol.(CompileTimeConstantExpr).getStringValue().regexpMatch("(?i)ldap(://)?") or
+ protocol
+ .(VarAccess)
+ .getVariable()
+ .getAnAssignedValue()
+ .(CompileTimeConstantExpr)
+ .getStringValue()
+ .regexpMatch("(?i)ldap(://)?")
+ ) and
+ not exists(string hostString |
+ hostString = host.(CompileTimeConstantExpr).getStringValue() or
+ hostString =
+ host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
+ |
+ hostString.length() = 0 or // Empty host is loopback address
+ hostString.regexpMatch(getPrivateHostRegex())
+ )
+}
+
+/** Gets the leftmost operand in a concatenated string */
+Expr getLeftmostConcatOperand(Expr expr) {
+ if expr instanceof AddExpr
+ then result = getLeftmostConcatOperand(expr.(AddExpr).getLeftOperand())
+ else result = expr
+}
+
+/**
+ * String concatenated with `LdapStringLiteral`.
+ */
+class LdapString extends Expr {
+ LdapString() {
+ this instanceof LdapStringLiteral
+ or
+ concatLdapString(this.(AddExpr).getLeftOperand(),
+ getLeftmostConcatOperand(this.(AddExpr).getRightOperand()))
+ }
+}
+
+/**
+ * Tainted value passed to env `Hashtable` as the provider URL, i.e.
+ * `env.put(Context.PROVIDER_URL, tainted)` or `env.setProperty(Context.PROVIDER_URL, tainted)`.
+ */
+predicate isProviderUrlEnv(MethodAccess ma) {
+ ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
+ (ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
+ (
+ ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url"
+ or
+ exists(Field f |
+ ma.getArgument(0) = f.getAnAccess() and
+ f.hasName("PROVIDER_URL") and
+ f.getDeclaringType() instanceof TypeNamingContext
+ )
+ )
+}
+
+/**
+ * Holds if the value "simple" is passed to env `Hashtable` as the authentication mechanism, i.e.
+ * `env.put(Context.SECURITY_AUTHENTICATION, "simple")` or `env.setProperty(Context.SECURITY_AUTHENTICATION, "simple")`.
+ */
+predicate isSimpleAuthEnv(MethodAccess ma) {
+ ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
+ (ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
+ (
+ ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() =
+ "java.naming.security.authentication"
+ or
+ exists(Field f |
+ ma.getArgument(0) = f.getAnAccess() and
+ f.hasName("SECURITY_AUTHENTICATION") and
+ f.getDeclaringType() instanceof TypeNamingContext
+ )
+ ) and
+ ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "simple"
+}
+
+/**
+ * A taint-tracking configuration for cleartext credentials in LDAP authentication.
+ */
+class LdapAuthFlowConfig extends TaintTracking::Configuration {
+ LdapAuthFlowConfig() { this = "InsecureLdapAuth:LdapAuthFlowConfig" }
+
+ /** Source of non-private LDAP connection string */
+ override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof LdapString }
+
+ /** Sink of provider URL with simple authentication */
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess pma |
+ sink.asExpr() = pma.getArgument(1) and
+ isProviderUrlEnv(pma) and
+ exists(MethodAccess sma |
+ sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
+ isSimpleAuthEnv(sma)
+ )
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, LdapAuthFlowConfig config
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
+ "LDAP connection string"
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
new file mode 100644
index 00000000000..65abcef0a9b
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
@@ -0,0 +1,15 @@
+edges
+| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
+| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
+| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl |
+nodes
+| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
+| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
+| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
+| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | semmle.label | ldapUrl |
+#select
+| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:25:20:25:39 | ... + ... | LDAP connection string |
+| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
new file mode 100644
index 00000000000..236880a1e8e
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
@@ -0,0 +1,92 @@
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import javax.naming.ldap.InitialLdapContext;
+
+public class InsecureLdapAuth {
+ // BAD - Test LDAP authentication in cleartext using `DirContext`.
+ public void testCleartextLdapAuth(String ldapUserName, String password) {
+ String ldapUrl = "ldap://ad.your-server.com:389";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext dirContext = new InitialDirContext(environment);
+ }
+
+ // BAD - Test LDAP authentication in cleartext using `DirContext`.
+ public void testCleartextLdapAuth(String ldapUserName, String password, String serverName) {
+ String ldapUrl = "ldap://"+serverName+":389";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext dirContext = new InitialDirContext(environment);
+ }
+
+ // GOOD - Test LDAP authentication over SSL.
+ public void testSslLdapAuth(String ldapUserName, String password) {
+ String ldapUrl = "ldaps://ad.your-server.com:636";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext dirContext = new InitialDirContext(environment);
+ }
+
+ // GOOD - Test LDAP authentication with SASL authentication.
+ public void testSaslLdapAuth(String ldapUserName, String password) {
+ String ldapUrl = "ldap://ad.your-server.com:389";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext dirContext = new InitialDirContext(environment);
+ }
+
+ // GOOD - Test LDAP authentication in cleartext connecting to local LDAP server.
+ public void testCleartextLdapAuth2(String ldapUserName, String password) {
+ String ldapUrl = "ldap://localhost:389";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ DirContext dirContext = new InitialDirContext(environment);
+ }
+
+ // BAD - Test LDAP authentication in cleartext using `InitialLdapContext`.
+ public void testCleartextLdapAuth3(String ldapUserName, String password) {
+ String ldapUrl = "ldap://ad.your-server.com:389";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ InitialLdapContext ldapContext = new InitialLdapContext(environment, null);
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.qlref b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.qlref
new file mode 100644
index 00000000000..c2baa984177
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
From c069a5b4c654e06ea669e7cdef0793d5179c9944 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Mon, 4 Jan 2021 14:51:32 +0000
Subject: [PATCH 003/429] Factor private host regex into the networking library
and enhance the query
---
.../Security/CWE/CWE-522/InsecureBasicAuth.ql | 12 +----
.../CWE/CWE-522/InsecureLdapAuth.qhelp | 2 +-
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 54 +++++++------------
.../code/java/frameworks/Networking.qll | 10 ++++
.../CWE-522/InsecureLdapAuth.expected | 4 ++
.../security/CWE-522/InsecureLdapAuth.java | 15 ++++++
6 files changed, 50 insertions(+), 47 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql
index 1fadb5bda69..3ec836a0117 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureBasicAuth.ql
@@ -14,14 +14,6 @@ import semmle.code.java.frameworks.ApacheHttp
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
-/**
- * Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
- */
-private 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\\]?(?:[:/?#].*)?"
-}
-
/**
* Class of Java URL constructor.
*/
@@ -76,7 +68,7 @@ class HttpStringLiteral extends StringLiteral {
// Match URLs with the HTTP protocol and without private IP addresses to reduce false positives.
exists(string s | this.getRepresentedString() = s |
s.regexpMatch("(?i)http://[\\[a-zA-Z0-9].*") and
- not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
+ not s.substring(7, s.length()) instanceof PrivateHostName
)
}
}
@@ -101,7 +93,7 @@ predicate concatHttpString(Expr protocol, Expr host) {
host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
|
hostString.length() = 0 or // Empty host is loopback address
- hostString.regexpMatch(getPrivateHostRegex())
+ hostString instanceof PrivateHostName
)
}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
index 9b7193bf52f..9241b52cf19 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
@@ -11,7 +11,7 @@
The following example shows two ways of using LDAP authentication. In the 'BAD' case, the credentials are transmitted in cleartext. In the 'GOOD' case, the credentials are transmitted over SSL.
-
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 663f4110524..37b3057fb2f 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -10,26 +10,19 @@
import java
import semmle.code.java.frameworks.Jndi
+import semmle.code.java.frameworks.Networking
import semmle.code.java.dataflow.TaintTracking
import DataFlow::PathGraph
/**
- * Gets a regular expression for matching private hosts, which only matches the host portion therefore checking for port is not necessary.
+ * Insecure (non-SSL, non-private) LDAP URL string literal.
*/
-private 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\\]?(?:[:/?#].*)?"
-}
-
-/**
- * String of LDAP connections not in private domains.
- */
-class LdapStringLiteral extends StringLiteral {
- LdapStringLiteral() {
+class InsecureLdapUrlLiteral extends StringLiteral {
+ InsecureLdapUrlLiteral() {
// Match connection strings with the LDAP protocol and without private IP addresses to reduce false positives.
exists(string s | this.getRepresentedString() = s |
s.regexpMatch("(?i)ldap://[\\[a-zA-Z0-9].*") and
- not s.substring(7, s.length()).regexpMatch(getPrivateHostRegex())
+ not s.substring(7, s.length()) instanceof PrivateHostName
)
}
}
@@ -47,24 +40,15 @@ class TypeHashtable extends Class {
/**
* Holds if a non-private LDAP string is concatenated from both protocol and host.
*/
-predicate concatLdapString(Expr protocol, Expr host) {
- (
- protocol.(CompileTimeConstantExpr).getStringValue().regexpMatch("(?i)ldap(://)?") or
- protocol
- .(VarAccess)
- .getVariable()
- .getAnAssignedValue()
- .(CompileTimeConstantExpr)
- .getStringValue()
- .regexpMatch("(?i)ldap(://)?")
- ) and
+predicate concatInsecureLdapString(Expr protocol, Expr host) {
+ protocol.(CompileTimeConstantExpr).getStringValue() = "ldap://" and
not exists(string hostString |
hostString = host.(CompileTimeConstantExpr).getStringValue() or
hostString =
host.(VarAccess).getVariable().getAnAssignedValue().(CompileTimeConstantExpr).getStringValue()
|
hostString.length() = 0 or // Empty host is loopback address
- hostString.regexpMatch(getPrivateHostRegex())
+ hostString instanceof PrivateHostName
)
}
@@ -76,22 +60,21 @@ Expr getLeftmostConcatOperand(Expr expr) {
}
/**
- * String concatenated with `LdapStringLiteral`.
+ * String concatenated with `InsecureLdapUrlLiteral`.
*/
-class LdapString extends Expr {
- LdapString() {
- this instanceof LdapStringLiteral
+class InsecureLdapUrl extends Expr {
+ InsecureLdapUrl() {
+ this instanceof InsecureLdapUrlLiteral
or
- concatLdapString(this.(AddExpr).getLeftOperand(),
+ concatInsecureLdapString(this.(AddExpr).getLeftOperand(),
getLeftmostConcatOperand(this.(AddExpr).getRightOperand()))
}
}
/**
- * Tainted value passed to env `Hashtable` as the provider URL, i.e.
- * `env.put(Context.PROVIDER_URL, tainted)` or `env.setProperty(Context.PROVIDER_URL, tainted)`.
+ * Holds if `ma` writes the `java.naming.provider.url` (also known as `Context.PROVIDER_URL`) key of a `Hashtable`.
*/
-predicate isProviderUrlEnv(MethodAccess ma) {
+predicate isProviderUrlSetter(MethodAccess ma) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
(
@@ -106,8 +89,7 @@ predicate isProviderUrlEnv(MethodAccess ma) {
}
/**
- * Holds if the value "simple" is passed to env `Hashtable` as the authentication mechanism, i.e.
- * `env.put(Context.SECURITY_AUTHENTICATION, "simple")` or `env.setProperty(Context.SECURITY_AUTHENTICATION, "simple")`.
+ * Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
*/
predicate isSimpleAuthEnv(MethodAccess ma) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
@@ -132,13 +114,13 @@ class LdapAuthFlowConfig extends TaintTracking::Configuration {
LdapAuthFlowConfig() { this = "InsecureLdapAuth:LdapAuthFlowConfig" }
/** Source of non-private LDAP connection string */
- override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof LdapString }
+ override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof InsecureLdapUrl }
/** Sink of provider URL with simple authentication */
override predicate isSink(DataFlow::Node sink) {
exists(MethodAccess pma |
sink.asExpr() = pma.getArgument(1) and
- isProviderUrlEnv(pma) and
+ isProviderUrlSetter(pma) and
exists(MethodAccess sma |
sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
isSimpleAuthEnv(sma)
diff --git a/java/ql/src/semmle/code/java/frameworks/Networking.qll b/java/ql/src/semmle/code/java/frameworks/Networking.qll
index 988510dd2e9..997b8075403 100644
--- a/java/ql/src/semmle/code/java/frameworks/Networking.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Networking.qll
@@ -129,3 +129,13 @@ class UrlOpenConnectionMethod extends Method {
this.getName() = "openConnection"
}
}
+
+/**
+ * A string matching private host names of IPv4 and IPv6, which only matches the host portion therefore checking for port is not necessary.
+ */
+class PrivateHostName extends string {
+ bindingset[this]
+ PrivateHostName() {
+ this.regexpMatch("(?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\\]?(?:[:/?#].*)?")
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
index 65abcef0a9b..c34c8966e0d 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
@@ -2,6 +2,7 @@ edges
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl |
+| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:47:100:53 | ldapUrl |
nodes
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
@@ -9,7 +10,10 @@ nodes
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
+| InsecureLdapAuth.java:100:47:100:53 | ldapUrl | semmle.label | ldapUrl |
#select
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:25:20:25:39 | ... + ... | LDAP connection string |
| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:100:47:100:53 | ldapUrl | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:47:100:53 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
index 236880a1e8e..cc16047ebf2 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
@@ -89,4 +89,19 @@ public class InsecureLdapAuth {
environment.put(Context.SECURITY_CREDENTIALS, password);
InitialLdapContext ldapContext = new InitialLdapContext(environment, null);
}
+
+
+ // BAD - Test LDAP authentication in cleartext using `DirContext` and string literals.
+ public void testCleartextLdapAuth4(String ldapUserName, String password) {
+ String ldapUrl = "ldap://ad.your-server.com:389";
+ Hashtable environment = new Hashtable();
+ environment.put("java.naming.factory.initial",
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put("java.naming.provider.url", ldapUrl);
+ environment.put("java.naming.referral", "follow");
+ environment.put("java.naming.security.authentication", "simple");
+ environment.put("java.naming.security.principal", ldapUserName);
+ environment.put("java.naming.security.credentials", password);
+ DirContext dirContext = new InitialDirContext(environment);
+ }
}
From 9b3070ab7c061b6d61f8e282c4e1272803750c6c Mon Sep 17 00:00:00 2001
From: intrigus
Date: Tue, 12 Jan 2021 14:48:22 +0100
Subject: [PATCH 004/429] Java: Add JXBrowser disabled certificate query.
---
.../JXBrowserWithoutCertValidation.java | 23 ++++++
.../JXBrowserWithoutCertValidation.qhelp | 31 ++++++++
.../CWE-295/JXBrowserWithoutCertValidation.ql | 73 +++++++++++++++++++
3 files changed, 127 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.java b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.java
new file mode 100644
index 00000000000..e45039e0fef
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.java
@@ -0,0 +1,23 @@
+public static void main(String[] args) {
+ {
+ Browser browser = new Browser();
+ browser.loadURL("https://example.com");
+ // no further calls
+ // BAD: The browser ignores any certificate error by default!
+ }
+
+ {
+ Browser browser = new Browser();
+ browser.setLoadHandler(new LoadHandler() {
+ public boolean onLoad(LoadParams params) {
+ return true;
+ }
+
+ public boolean onCertificateError(CertificateErrorParams params){
+ return true; // GOOD: This means that loading will be cancelled on certificate errors
+ }
+ }); // GOOD: A secure `LoadHandler` is used.
+ browser.loadURL("https://example.com");
+
+ }
+}
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp
new file mode 100644
index 00000000000..1d9588652c2
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
+
JXBrowser is a Java library that allows to embed the Chromium browser inside Java applications.
+The version 6.x.x by default ignores any HTTPS certificate errors thereby allowing man-in-the-middle attacks.
+
+
+
+
+
Do either of these:
+
Update to version 7.x.x as it now correctly rejects certificate errors by default.
+
Add a custom implementation of the LoadHandler interface whose onCertificateError method always returns true indicating that loading should be cancelled.
+Then use the setLoadHandler method with your custom LoadHandler on every Browser you use.
+
+
+
+
+
The following two examples show two ways of using a Browser. In the 'BAD' case,
+all certificate errors are ignored. In the 'GOOD' case, certificate errors are rejected.
+
+
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
new file mode 100644
index 00000000000..6e7fe37e668
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
@@ -0,0 +1,73 @@
+/**
+ * @name JXBrowser with disabled certificate validation
+ * @description Insecure configuration of JXBrowser disables certificate validation making the app vulnerable to man-in-the-middle attacks.
+ * @kind problem
+ * @id java/jxbrowser/disabled-certificate-validation
+ * @tags security
+ * external/cwe-295
+ */
+
+import java
+import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking
+
+/*
+ * This query is version specific to JXBrowser 6.x.x. The version is indirectly detected.
+ * In version 6.x.x the `Browser` class is in a different package compared to version 7.x.x.
+ */
+
+/** The `com.teamdev.jxbrowser.chromium.Browser` class. */
+private class JXBrowser extends RefType {
+ JXBrowser() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "Browser") }
+}
+
+/** The `setLoadHandler` method on the `com.teamdev.jxbrowser.chromium.Browser` class. */
+private class JXBrowserSetLoadHandler extends Method {
+ JXBrowserSetLoadHandler() {
+ this.hasName("setLoadHandler") and this.getDeclaringType() instanceof JXBrowser
+ }
+}
+
+/** The `com.teamdev.jxbrowser.chromium.LoadHandler` interface. */
+private class JXBrowserLoadHandler extends RefType {
+ JXBrowserLoadHandler() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "LoadHandler") }
+}
+
+private predicate isOnCertificateErrorMethodSafe(Method m) {
+ forex(ReturnStmt rs | rs.getEnclosingCallable() = m |
+ rs.getResult().(CompileTimeConstantExpr).getBooleanValue() = true
+ )
+}
+
+/** A class that securely implements the `com.teamdev.jxbrowser.chromium.LoadHandler` interface. */
+private class JXBrowserSafeLoadHandler extends RefType {
+ JXBrowserSafeLoadHandler() {
+ this.getASupertype() instanceof JXBrowserLoadHandler and
+ exists(Method m | m.hasName("onCertificateError") and m.getDeclaringType() = this |
+ isOnCertificateErrorMethodSafe(m)
+ )
+ }
+}
+
+private class JXBrowserTaintTracking extends TaintTracking::Configuration {
+ JXBrowserTaintTracking() { this = "JXBrowserTaintTracking" }
+
+ override predicate isSource(DataFlow::Node src) {
+ exists(ClassInstanceExpr newJXBrowser | newJXBrowser.getConstructedType() instanceof JXBrowser |
+ newJXBrowser = src.asExpr()
+ )
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma | ma.getMethod() instanceof JXBrowserSetLoadHandler |
+ ma.getArgument(0).getType() instanceof JXBrowserSafeLoadHandler and
+ ma.getQualifier() = sink.asExpr()
+ )
+ }
+}
+
+from JXBrowserTaintTracking cfg, DataFlow::Node src
+where
+ cfg.isSource(src) and
+ not cfg.hasFlow(src, _)
+select src, "This JXBrowser instance allows man-in-the-middle attacks."
From b30872806dc631f53638f99fc2499fff012c57d8 Mon Sep 17 00:00:00 2001
From: intrigus
Date: Tue, 12 Jan 2021 14:49:12 +0100
Subject: [PATCH 005/429] Java: Add tests and test stubs.
---
.../JXBrowserWithoutCertValidation.expected | 1 +
.../JXBrowserWithoutCertValidation.java | 36 +++++++++++++++++++
.../JXBrowserWithoutCertValidation.qlref | 1 +
.../query-tests/security/CWE-295/options | 1 +
.../teamdev/jxbrowser/chromium/Browser.java | 9 +++++
.../chromium/CertificateErrorParams.java | 5 +++
.../jxbrowser/chromium/LoadHandler.java | 7 ++++
.../jxbrowser/chromium/LoadParams.java | 5 +++
8 files changed, 65 insertions(+)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/options
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/Browser.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadHandler.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadParams.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected
new file mode 100644
index 00000000000..00d43d1e8b7
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected
@@ -0,0 +1 @@
+| JXBrowserWithoutCertValidation.java:17:27:17:39 | new Browser(...) | This JXBrowser instance allows man-in-the-middle attacks. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java
new file mode 100644
index 00000000000..97fc8e8770f
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java
@@ -0,0 +1,36 @@
+import com.teamdev.jxbrowser.chromium.Browser;
+import com.teamdev.jxbrowser.chromium.LoadHandler;
+import com.teamdev.jxbrowser.chromium.LoadParams;
+import com.teamdev.jxbrowser.chromium.CertificateErrorParams;
+
+public class JXBrowserWithoutCertValidation {
+
+ public static void main(String[] args) {
+
+ badUsage();
+
+ goodUsage();
+
+ }
+
+ private static void badUsage() {
+ Browser browser = new Browser();
+ browser.loadURL("https://example.com");
+ // no further calls
+ // BAD: The browser ignores any certificate error by default!
+ }
+
+ private static void goodUsage() {
+ Browser browser = new Browser();
+ browser.setLoadHandler(new LoadHandler() {
+ public boolean onLoad(LoadParams params) {
+ return true;
+ }
+
+ public boolean onCertificateError(CertificateErrorParams params) {
+ return true; // GOOD: This means that loading will be cancelled on certificate errors
+ }
+ }); // GOOD: A secure `LoadHandler` is used.
+ browser.loadURL("https://example.com");
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref
new file mode 100644
index 00000000000..ab2a3f14bb9
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/options b/java/ql/test/experimental/query-tests/security/CWE-295/options
new file mode 100644
index 00000000000..770bbcd38e6
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/options
@@ -0,0 +1 @@
+ //semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/jxbrowser-6.23.1
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/Browser.java b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/Browser.java
new file mode 100644
index 00000000000..327a4b4ecd8
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/Browser.java
@@ -0,0 +1,9 @@
+package com.teamdev.jxbrowser.chromium;
+
+public class Browser extends java.lang.Object {
+ public void setLoadHandler(LoadHandler handler) {
+ }
+
+ public void loadURL(String url) {
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java
new file mode 100644
index 00000000000..904b98a6c51
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java
@@ -0,0 +1,5 @@
+package com.teamdev.jxbrowser.chromium;
+
+public final class CertificateErrorParams extends Object {
+
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadHandler.java b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadHandler.java
new file mode 100644
index 00000000000..a628d88439c
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadHandler.java
@@ -0,0 +1,7 @@
+package com.teamdev.jxbrowser.chromium;
+
+public interface LoadHandler {
+ boolean onCertificateError(CertificateErrorParams params);
+
+ boolean onLoad(LoadParams params);
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadParams.java b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadParams.java
new file mode 100644
index 00000000000..213e54f1dbc
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.23.1/com/teamdev/jxbrowser/chromium/LoadParams.java
@@ -0,0 +1,5 @@
+package com.teamdev.jxbrowser.chromium;
+
+public final class LoadParams extends Object {
+
+}
\ No newline at end of file
From 1ebc9f4d9399b9ec0394d9bc810bef481194d7a1 Mon Sep 17 00:00:00 2001
From: intrigus
Date: Tue, 12 Jan 2021 22:39:08 +0100
Subject: [PATCH 006/429] Java: Only detect JxBrowser < 6.24
---
.../CWE/CWE-295/JXBrowserWithoutCertValidation.ql | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
index 6e7fe37e668..ab74c78d1e7 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
@@ -12,10 +12,18 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
/*
- * This query is version specific to JXBrowser 6.x.x. The version is indirectly detected.
+ * This query is version specific to JXBrowser < 6.24. The version is indirectly detected.
* In version 6.x.x the `Browser` class is in a different package compared to version 7.x.x.
*/
+/**
+ * Holds if a safe JXBrowser 6.x.x version is used, such as version 6.24.
+ * This is detected by the the presence of the `addBoundsListener` in the `Browser` class.
+ */
+private predicate isSafeJXBrowserVersion() {
+ exists(Method m | m.getDeclaringType() instanceof JXBrowser | m.hasName("addBoundsListener"))
+}
+
/** The `com.teamdev.jxbrowser.chromium.Browser` class. */
private class JXBrowser extends RefType {
JXBrowser() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "Browser") }
@@ -69,5 +77,6 @@ private class JXBrowserTaintTracking extends TaintTracking::Configuration {
from JXBrowserTaintTracking cfg, DataFlow::Node src
where
cfg.isSource(src) and
- not cfg.hasFlow(src, _)
+ not cfg.hasFlow(src, _) and
+ not isSafeJXBrowserVersion()
select src, "This JXBrowser instance allows man-in-the-middle attacks."
From 5b3086a93a07313d79520a24c36d9b45992fd2d1 Mon Sep 17 00:00:00 2001
From: intrigus
Date: Tue, 12 Jan 2021 22:43:41 +0100
Subject: [PATCH 007/429] Java: Fix capitalization of JxBrowser
---
...va => JxBrowserWithoutCertValidation.java} | 0
...p => JxBrowserWithoutCertValidation.qhelp} | 12 ++---
...n.ql => JxBrowserWithoutCertValidation.ql} | 50 +++++++++----------
.../JXBrowserWithoutCertValidation.expected | 1 -
.../JXBrowserWithoutCertValidation.qlref | 1 -
.../JxBrowserWithoutCertValidation.expected | 1 +
...va => JxBrowserWithoutCertValidation.java} | 2 +-
.../JxBrowserWithoutCertValidation.qlref | 1 +
8 files changed, 34 insertions(+), 34 deletions(-)
rename java/ql/src/experimental/Security/CWE/CWE-295/{JXBrowserWithoutCertValidation.java => JxBrowserWithoutCertValidation.java} (100%)
rename java/ql/src/experimental/Security/CWE/CWE-295/{JXBrowserWithoutCertValidation.qhelp => JxBrowserWithoutCertValidation.qhelp} (58%)
rename java/ql/src/experimental/Security/CWE/CWE-295/{JXBrowserWithoutCertValidation.ql => JxBrowserWithoutCertValidation.ql} (54%)
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected
rename java/ql/test/experimental/query-tests/security/CWE-295/{JXBrowserWithoutCertValidation.java => JxBrowserWithoutCertValidation.java} (95%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.qlref
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.java b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.java
similarity index 100%
rename from java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.java
rename to java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.java
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.qhelp
similarity index 58%
rename from java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp
rename to java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.qhelp
index 1d9588652c2..27d10c15223 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.qhelp
@@ -4,14 +4,14 @@
-
JXBrowser is a Java library that allows to embed the Chromium browser inside Java applications.
-The version 6.x.x by default ignores any HTTPS certificate errors thereby allowing man-in-the-middle attacks.
+
JxBrowser is a Java library that allows to embed the Chromium browser inside Java applications.
+Versions smaller than 6.24 by default ignore any HTTPS certificate errors thereby allowing man-in-the-middle attacks.
Do either of these:
-
Update to version 7.x.x as it now correctly rejects certificate errors by default.
+
Update to version 6.24 or 7.x.x as these correctly reject certificate errors by default.
Add a custom implementation of the LoadHandler interface whose onCertificateError method always returns true indicating that loading should be cancelled.
Then use the setLoadHandler method with your custom LoadHandler on every Browser you use.
@@ -20,12 +20,12 @@ Then use the setLoadHandler method with your custom LoadHandl
The following two examples show two ways of using a Browser. In the 'BAD' case,
all certificate errors are ignored. In the 'GOOD' case, certificate errors are rejected.
+
+Changelog of JxBrowser 6.24.
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
similarity index 54%
rename from java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
rename to java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
index ab74c78d1e7..0de4290a44e 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
@@ -1,6 +1,6 @@
/**
- * @name JXBrowser with disabled certificate validation
- * @description Insecure configuration of JXBrowser disables certificate validation making the app vulnerable to man-in-the-middle attacks.
+ * @name JxBrowser with disabled certificate validation
+ * @description Insecure configuration of JxBrowser disables certificate validation making the app vulnerable to man-in-the-middle attacks.
* @kind problem
* @id java/jxbrowser/disabled-certificate-validation
* @tags security
@@ -12,33 +12,33 @@ import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
/*
- * This query is version specific to JXBrowser < 6.24. The version is indirectly detected.
+ * This query is version specific to JxBrowser < 6.24. The version is indirectly detected.
* In version 6.x.x the `Browser` class is in a different package compared to version 7.x.x.
*/
/**
- * Holds if a safe JXBrowser 6.x.x version is used, such as version 6.24.
+ * Holds if a safe JxBrowser 6.x.x version is used, such as version 6.24.
* This is detected by the the presence of the `addBoundsListener` in the `Browser` class.
*/
-private predicate isSafeJXBrowserVersion() {
- exists(Method m | m.getDeclaringType() instanceof JXBrowser | m.hasName("addBoundsListener"))
+private predicate isSafeJxBrowserVersion() {
+ exists(Method m | m.getDeclaringType() instanceof JxBrowser | m.hasName("addBoundsListener"))
}
/** The `com.teamdev.jxbrowser.chromium.Browser` class. */
-private class JXBrowser extends RefType {
- JXBrowser() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "Browser") }
+private class JxBrowser extends RefType {
+ JxBrowser() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "Browser") }
}
/** The `setLoadHandler` method on the `com.teamdev.jxbrowser.chromium.Browser` class. */
-private class JXBrowserSetLoadHandler extends Method {
- JXBrowserSetLoadHandler() {
- this.hasName("setLoadHandler") and this.getDeclaringType() instanceof JXBrowser
+private class JxBrowserSetLoadHandler extends Method {
+ JxBrowserSetLoadHandler() {
+ this.hasName("setLoadHandler") and this.getDeclaringType() instanceof JxBrowser
}
}
/** The `com.teamdev.jxbrowser.chromium.LoadHandler` interface. */
-private class JXBrowserLoadHandler extends RefType {
- JXBrowserLoadHandler() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "LoadHandler") }
+private class JxBrowserLoadHandler extends RefType {
+ JxBrowserLoadHandler() { this.hasQualifiedName("com.teamdev.jxbrowser.chromium", "LoadHandler") }
}
private predicate isOnCertificateErrorMethodSafe(Method m) {
@@ -48,35 +48,35 @@ private predicate isOnCertificateErrorMethodSafe(Method m) {
}
/** A class that securely implements the `com.teamdev.jxbrowser.chromium.LoadHandler` interface. */
-private class JXBrowserSafeLoadHandler extends RefType {
- JXBrowserSafeLoadHandler() {
- this.getASupertype() instanceof JXBrowserLoadHandler and
+private class JxBrowserSafeLoadHandler extends RefType {
+ JxBrowserSafeLoadHandler() {
+ this.getASupertype() instanceof JxBrowserLoadHandler and
exists(Method m | m.hasName("onCertificateError") and m.getDeclaringType() = this |
isOnCertificateErrorMethodSafe(m)
)
}
}
-private class JXBrowserTaintTracking extends TaintTracking::Configuration {
- JXBrowserTaintTracking() { this = "JXBrowserTaintTracking" }
+private class JxBrowserTaintTracking extends TaintTracking::Configuration {
+ JxBrowserTaintTracking() { this = "JxBrowserTaintTracking" }
override predicate isSource(DataFlow::Node src) {
- exists(ClassInstanceExpr newJXBrowser | newJXBrowser.getConstructedType() instanceof JXBrowser |
- newJXBrowser = src.asExpr()
+ exists(ClassInstanceExpr newJxBrowser | newJxBrowser.getConstructedType() instanceof JxBrowser |
+ newJxBrowser = src.asExpr()
)
}
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess ma | ma.getMethod() instanceof JXBrowserSetLoadHandler |
- ma.getArgument(0).getType() instanceof JXBrowserSafeLoadHandler and
+ exists(MethodAccess ma | ma.getMethod() instanceof JxBrowserSetLoadHandler |
+ ma.getArgument(0).getType() instanceof JxBrowserSafeLoadHandler and
ma.getQualifier() = sink.asExpr()
)
}
}
-from JXBrowserTaintTracking cfg, DataFlow::Node src
+from JxBrowserTaintTracking cfg, DataFlow::Node src
where
cfg.isSource(src) and
not cfg.hasFlow(src, _) and
- not isSafeJXBrowserVersion()
-select src, "This JXBrowser instance allows man-in-the-middle attacks."
+ not isSafeJxBrowserVersion()
+select src, "This JxBrowser instance allows man-in-the-middle attacks."
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected
deleted file mode 100644
index 00d43d1e8b7..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.expected
+++ /dev/null
@@ -1 +0,0 @@
-| JXBrowserWithoutCertValidation.java:17:27:17:39 | new Browser(...) | This JXBrowser instance allows man-in-the-middle attacks. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref b/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref
deleted file mode 100644
index ab2a3f14bb9..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.qlref
+++ /dev/null
@@ -1 +0,0 @@
-experimental/Security/CWE/CWE-295/JXBrowserWithoutCertValidation.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected
new file mode 100644
index 00000000000..121de8759ac
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected
@@ -0,0 +1 @@
+| JxBrowserWithoutCertValidation.java:17:27:17:39 | new Browser(...) | This JxBrowser instance allows man-in-the-middle attacks. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.java
similarity index 95%
rename from java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java
rename to java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.java
index 97fc8e8770f..b7e2710842f 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-295/JXBrowserWithoutCertValidation.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.java
@@ -3,7 +3,7 @@ import com.teamdev.jxbrowser.chromium.LoadHandler;
import com.teamdev.jxbrowser.chromium.LoadParams;
import com.teamdev.jxbrowser.chromium.CertificateErrorParams;
-public class JXBrowserWithoutCertValidation {
+public class JxBrowserWithoutCertValidation {
public static void main(String[] args) {
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.qlref b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.qlref
new file mode 100644
index 00000000000..cab6f2a4962
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
From babe744a3011a4f7e2216ff5bd95bed241dd4984 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 13 Jan 2021 03:49:08 +0000
Subject: [PATCH 008/429] Add SECURITY_PROTOCOL check
---
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 30 +++++++++++++++----
.../CWE-522/InsecureLdapAuth.expected | 14 ++++-----
.../security/CWE-522/InsecureLdapAuth.java | 15 ++++++++++
3 files changed, 46 insertions(+), 13 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 37b3057fb2f..1cebfe8bad3 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -89,22 +89,36 @@ predicate isProviderUrlSetter(MethodAccess ma) {
}
/**
- * Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
+ * Holds if `ma` sets `fieldValue` with attribute name `fieldName` to `envValue` in some `Hashtable`.
*/
-predicate isSimpleAuthEnv(MethodAccess ma) {
+bindingset[fieldName, fieldValue, envValue]
+predicate hasEnvWithValue(MethodAccess ma, string fieldName, string fieldValue, string envValue) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
(
- ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() =
- "java.naming.security.authentication"
+ ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = fieldValue
or
exists(Field f |
ma.getArgument(0) = f.getAnAccess() and
- f.hasName("SECURITY_AUTHENTICATION") and
+ f.hasName(fieldName) and
f.getDeclaringType() instanceof TypeNamingContext
)
) and
- ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = "simple"
+ ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = envValue
+}
+
+/**
+ * Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
+ */
+predicate isSimpleAuthEnv(MethodAccess ma) {
+ hasEnvWithValue(ma, "SECURITY_AUTHENTICATION", "java.naming.security.authentication", "simple")
+}
+
+/**
+ * Holds if `ma` sets `java.naming.security.protocol` (also known as `Context.SECURITY_PROTOCOL`) to `ssl` in some `Hashtable`.
+ */
+predicate isSSLEnv(MethodAccess ma) {
+ hasEnvWithValue(ma, "SECURITY_PROTOCOL", "java.naming.security.protocol", "ssl")
}
/**
@@ -124,6 +138,10 @@ class LdapAuthFlowConfig extends TaintTracking::Configuration {
exists(MethodAccess sma |
sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
isSimpleAuthEnv(sma)
+ ) and
+ not exists(MethodAccess sma |
+ sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
+ isSSLEnv(sma)
)
)
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
index c34c8966e0d..745fd9c1b75 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
@@ -1,19 +1,19 @@
edges
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
-| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl |
-| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:47:100:53 | ldapUrl |
+| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:41:100:47 | ldapUrl |
+| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:115:47:115:53 | ldapUrl |
nodes
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
-| InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | semmle.label | ldapUrl |
| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:100:47:100:53 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:100:41:100:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
+| InsecureLdapAuth.java:115:47:115:53 | ldapUrl | semmle.label | ldapUrl |
#select
| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:25:20:25:39 | ... + ... | LDAP connection string |
-| InsecureLdapAuth.java:85:41:85:47 | ldapUrl | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:85:41:85:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:81:20:81:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
-| InsecureLdapAuth.java:100:47:100:53 | ldapUrl | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:47:100:53 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:100:41:100:47 | ldapUrl | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:41:100:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:115:47:115:53 | ldapUrl | InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:115:47:115:53 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
index cc16047ebf2..4052557d8b0 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
@@ -48,6 +48,21 @@ public class InsecureLdapAuth {
DirContext dirContext = new InitialDirContext(environment);
}
+ // GOOD - Test LDAP authentication over SSL.
+ public void testSslLdapAuth2(String ldapUserName, String password) {
+ String ldapUrl = "ldap://ad.your-server.com:636";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ environment.put(Context.REFERRAL, "follow");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ environment.put(Context.SECURITY_CREDENTIALS, password);
+ environment.put(Context.SECURITY_PROTOCOL, "ssl");
+ DirContext dirContext = new InitialDirContext(environment);
+ }
+
// GOOD - Test LDAP authentication with SASL authentication.
public void testSaslLdapAuth(String ldapUserName, String password) {
String ldapUrl = "ldap://ad.your-server.com:389";
From f3b8fe2e2ed760301bbf417e94d2738f2d0d5b06 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Wed, 13 Jan 2021 13:42:35 +0100
Subject: [PATCH 009/429] Java: Add Member.hasQualifiedName.
---
java/ql/src/semmle/code/java/Member.qll | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/java/ql/src/semmle/code/java/Member.qll b/java/ql/src/semmle/code/java/Member.qll
index e9d4fe82978..a0e988bd386 100755
--- a/java/ql/src/semmle/code/java/Member.qll
+++ b/java/ql/src/semmle/code/java/Member.qll
@@ -23,6 +23,14 @@ class Member extends Element, Annotatable, Modifiable, @member {
/** Gets the qualified name of this member. */
string getQualifiedName() { result = getDeclaringType().getName() + "." + getName() }
+ /**
+ * Holds if this member has the specified name and is declared in the
+ * specified package and type.
+ */
+ predicate hasQualifiedName(string package, string type, string name) {
+ this.getDeclaringType().hasQualifiedName(package, type) and this.hasName(name)
+ }
+
/** Holds if this member is package protected, that is, neither public nor private nor protected. */
predicate isPackageProtected() {
not isPrivate() and
From b8076481bf353caf6b3a68348144114ebb1d96d7 Mon Sep 17 00:00:00 2001
From: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
Date: Wed, 13 Jan 2021 20:32:23 +0100
Subject: [PATCH 010/429] Java: Suggestions from Review
---
.../Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
index 0de4290a44e..6aee64ed010 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
@@ -79,4 +79,4 @@ where
cfg.isSource(src) and
not cfg.hasFlow(src, _) and
not isSafeJxBrowserVersion()
-select src, "This JxBrowser instance allows man-in-the-middle attacks."
+select src, "This JxBrowser instance may not check HTTPS certificates."
From e5a703e49c3fe53aeda8e6ecb9d3766df7683124 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 15 Jan 2021 04:05:11 +0000
Subject: [PATCH 011/429] Revamp the query
---
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 109 +++++++++++++++---
.../CWE-522/InsecureLdapAuth.expected | 79 +++++++++++--
.../security/CWE-522/InsecureLdapAuth.java | 33 ++++++
3 files changed, 190 insertions(+), 31 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 1cebfe8bad3..1a38f74092b 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -110,7 +110,7 @@ predicate hasEnvWithValue(MethodAccess ma, string fieldName, string fieldValue,
/**
* Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
*/
-predicate isSimpleAuthEnv(MethodAccess ma) {
+predicate isBasicAuthEnv(MethodAccess ma) {
hasEnvWithValue(ma, "SECURITY_AUTHENTICATION", "java.naming.security.authentication", "simple")
}
@@ -122,32 +122,103 @@ predicate isSSLEnv(MethodAccess ma) {
}
/**
- * A taint-tracking configuration for cleartext credentials in LDAP authentication.
+ * A taint-tracking configuration for `ldap://` URL in LDAP authentication.
*/
-class LdapAuthFlowConfig extends TaintTracking::Configuration {
- LdapAuthFlowConfig() { this = "InsecureLdapAuth:LdapAuthFlowConfig" }
+class InsecureUrlFlowConfig extends TaintTracking::Configuration {
+ InsecureUrlFlowConfig() { this = "InsecureLdapAuth:InsecureUrlFlowConfig" }
- /** Source of non-private LDAP connection string */
+ /** Source of `ldap://` connection string. */
override predicate isSource(DataFlow::Node src) { src.asExpr() instanceof InsecureLdapUrl }
- /** Sink of provider URL with simple authentication */
+ /** Sink of directory context creation. */
override predicate isSink(DataFlow::Node sink) {
- exists(MethodAccess pma |
- sink.asExpr() = pma.getArgument(1) and
- isProviderUrlSetter(pma) and
- exists(MethodAccess sma |
- sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
- isSimpleAuthEnv(sma)
- ) and
- not exists(MethodAccess sma |
- sma.getQualifier() = pma.getQualifier().(VarAccess).getVariable().getAnAccess() and
- isSSLEnv(sma)
- )
+ exists(ConstructorCall cc |
+ cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
+ sink.asExpr() = cc.getArgument(0)
+ )
+ }
+
+ /** Method call of `env.put()`. */
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ pred.asExpr() = ma.getArgument(1) and
+ isProviderUrlSetter(ma) and
+ succ.asExpr() = ma.getQualifier()
)
}
}
-from DataFlow::PathNode source, DataFlow::PathNode sink, LdapAuthFlowConfig config
-where config.hasFlowPath(source, sink)
+/**
+ * A taint-tracking configuration for `simple` basic-authentication in LDAP configuration.
+ */
+class BasicAuthFlowConfig extends TaintTracking::Configuration {
+ BasicAuthFlowConfig() { this = "InsecureLdapAuth:BasicAuthFlowConfig" }
+
+ /** Source of `simple` configuration. */
+ override predicate isSource(DataFlow::Node src) {
+ src.asExpr().(CompileTimeConstantExpr).getStringValue() = "simple"
+ }
+
+ /** Sink of directory context creation. */
+ override predicate isSink(DataFlow::Node sink) {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
+ sink.asExpr() = cc.getArgument(0)
+ )
+ }
+
+ /** Method call of `env.put()`. */
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ pred.asExpr() = ma.getArgument(1) and
+ isBasicAuthEnv(ma) and
+ succ.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/**
+ * A taint-tracking configuration for `ssl` configuration in LDAP authentication.
+ */
+class SSLFlowConfig extends TaintTracking::Configuration {
+ SSLFlowConfig() { this = "InsecureLdapAuth:SSLFlowConfig" }
+
+ /** Source of `ssl` configuration. */
+ override predicate isSource(DataFlow::Node src) {
+ src.asExpr().(CompileTimeConstantExpr).getStringValue() = "ssl"
+ }
+
+ /** Sink of directory context creation. */
+ override predicate isSink(DataFlow::Node sink) {
+ exists(ConstructorCall cc |
+ cc.getConstructedType().getASupertype*() instanceof TypeDirContext and
+ sink.asExpr() = cc.getArgument(0)
+ )
+ }
+
+ /** Method call of `env.put()`. */
+ override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ exists(MethodAccess ma |
+ pred.asExpr() = ma.getArgument(1) and
+ isSSLEnv(ma) and
+ succ.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureUrlFlowConfig config, VarAccess va
+where
+ config.hasFlowPath(source, sink) and
+ sink.getNode().asExpr() = va and
+ exists(BasicAuthFlowConfig bc, DataFlow::PathNode source2, DataFlow::PathNode sink2 |
+ bc.hasFlowPath(source2, sink2) and
+ source2.getNode().asExpr().(CompileTimeConstantExpr).getStringValue() = "simple" and
+ sink2.getNode().asExpr() = va
+ ) and
+ not exists(SSLFlowConfig sc, DataFlow::PathNode source3, DataFlow::PathNode sink3 |
+ sc.hasFlowPath(source3, sink3) and
+ source3.getNode().asExpr().(CompileTimeConstantExpr).getStringValue() = "ssl" and
+ sink3.getNode().asExpr() = va.getVariable().getAnAccess()
+ )
select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
"LDAP connection string"
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
index 745fd9c1b75..1af36e67d05 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
@@ -1,19 +1,74 @@
edges
-| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl |
-| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl |
-| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:41:100:47 | ldapUrl |
-| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:115:47:115:53 | ldapUrl |
+| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:20:49:20:59 | environment |
+| InsecureLdapAuth.java:17:52:17:59 | "simple" : String | InsecureLdapAuth.java:20:49:20:59 | environment |
+| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:34:49:34:59 | environment |
+| InsecureLdapAuth.java:31:52:31:59 | "simple" : String | InsecureLdapAuth.java:34:49:34:59 | environment |
+| InsecureLdapAuth.java:45:52:45:59 | "simple" : String | InsecureLdapAuth.java:48:49:48:59 | environment |
+| InsecureLdapAuth.java:53:20:53:50 | "ldap://ad.your-server.com:636" : String | InsecureLdapAuth.java:63:49:63:59 | environment |
+| InsecureLdapAuth.java:59:52:59:59 | "simple" : String | InsecureLdapAuth.java:63:49:63:59 | environment |
+| InsecureLdapAuth.java:62:46:62:50 | "ssl" : String | InsecureLdapAuth.java:63:49:63:59 | environment |
+| InsecureLdapAuth.java:68:20:68:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:77:49:77:59 | environment |
+| InsecureLdapAuth.java:88:52:88:59 | "simple" : String | InsecureLdapAuth.java:91:49:91:59 | environment |
+| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:105:59:105:69 | environment |
+| InsecureLdapAuth.java:102:52:102:59 | "simple" : String | InsecureLdapAuth.java:105:59:105:69 | environment |
+| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:120:49:120:59 | environment |
+| InsecureLdapAuth.java:117:58:117:65 | "simple" : String | InsecureLdapAuth.java:120:49:120:59 | environment |
+| InsecureLdapAuth.java:124:3:124:5 | env [post update] : Hashtable | InsecureLdapAuth.java:137:10:137:20 | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:124:38:124:42 | "ssl" : String | InsecureLdapAuth.java:124:3:124:5 | env [post update] : Hashtable |
+| InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable | InsecureLdapAuth.java:141:16:141:26 | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable | InsecureLdapAuth.java:152:16:152:26 | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:128:44:128:51 | "simple" : String | InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable |
+| InsecureLdapAuth.java:135:20:135:39 | ... + ... : String | InsecureLdapAuth.java:142:50:142:60 | environment |
+| InsecureLdapAuth.java:137:10:137:20 | environment [post update] : Hashtable | InsecureLdapAuth.java:142:50:142:60 | environment |
+| InsecureLdapAuth.java:141:16:141:26 | environment [post update] : Hashtable | InsecureLdapAuth.java:142:50:142:60 | environment |
+| InsecureLdapAuth.java:147:20:147:39 | ... + ... : String | InsecureLdapAuth.java:153:50:153:60 | environment |
+| InsecureLdapAuth.java:152:16:152:26 | environment [post update] : Hashtable | InsecureLdapAuth.java:153:50:153:60 | environment |
nodes
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:17:52:17:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:20:49:20:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:20:49:20:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
-| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:31:52:31:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:34:49:34:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:34:49:34:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:45:52:45:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:48:49:48:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:53:20:53:50 | "ldap://ad.your-server.com:636" : String | semmle.label | "ldap://ad.your-server.com:636" : String |
+| InsecureLdapAuth.java:59:52:59:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:62:46:62:50 | "ssl" : String | semmle.label | "ssl" : String |
+| InsecureLdapAuth.java:63:49:63:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:63:49:63:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:63:49:63:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:68:20:68:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
+| InsecureLdapAuth.java:77:49:77:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:88:52:88:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:91:49:91:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:100:41:100:47 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:102:52:102:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:105:59:105:69 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:105:59:105:69 | environment | semmle.label | environment |
| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:115:47:115:53 | ldapUrl | semmle.label | ldapUrl |
+| InsecureLdapAuth.java:117:58:117:65 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:120:49:120:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:120:49:120:59 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:124:3:124:5 | env [post update] : Hashtable | semmle.label | env [post update] : Hashtable |
+| InsecureLdapAuth.java:124:38:124:42 | "ssl" : String | semmle.label | "ssl" : String |
+| InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable | semmle.label | env [post update] : Hashtable |
+| InsecureLdapAuth.java:128:44:128:51 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:135:20:135:39 | ... + ... : String | semmle.label | ... + ... : String |
+| InsecureLdapAuth.java:137:10:137:20 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:141:16:141:26 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:142:50:142:60 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:142:50:142:60 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:142:50:142:60 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:147:20:147:39 | ... + ... : String | semmle.label | ... + ... : String |
+| InsecureLdapAuth.java:152:16:152:26 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:153:50:153:60 | environment | semmle.label | environment |
+| InsecureLdapAuth.java:153:50:153:60 | environment | semmle.label | environment |
#select
-| InsecureLdapAuth.java:15:41:15:47 | ldapUrl | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:15:41:15:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
-| InsecureLdapAuth.java:29:41:29:47 | ldapUrl | InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:29:41:29:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:25:20:25:39 | ... + ... | LDAP connection string |
-| InsecureLdapAuth.java:100:41:100:47 | ldapUrl | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:100:41:100:47 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
-| InsecureLdapAuth.java:115:47:115:53 | ldapUrl | InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:115:47:115:53 | ldapUrl | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:20:49:20:59 | environment | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:20:49:20:59 | environment | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:34:49:34:59 | environment | InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:34:49:34:59 | environment | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:25:20:25:39 | ... + ... | LDAP connection string |
+| InsecureLdapAuth.java:105:59:105:69 | environment | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:105:59:105:69 | environment | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:120:49:120:59 | environment | InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:120:49:120:59 | environment | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" | LDAP connection string |
+| InsecureLdapAuth.java:153:50:153:60 | environment | InsecureLdapAuth.java:147:20:147:39 | ... + ... : String | InsecureLdapAuth.java:153:50:153:60 | environment | Insecure LDAP authentication from $@. | InsecureLdapAuth.java:147:20:147:39 | ... + ... | LDAP connection string |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
index 4052557d8b0..14142d31b21 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.java
@@ -119,4 +119,37 @@ public class InsecureLdapAuth {
environment.put("java.naming.security.credentials", password);
DirContext dirContext = new InitialDirContext(environment);
}
+
+ private void setSSL(Hashtable env) {
+ env.put(Context.SECURITY_PROTOCOL, "ssl");
+ }
+
+ private void setBasicAuth(Hashtable env, String ldapUserName, String password) {
+ env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ env.put(Context.SECURITY_PRINCIPAL, ldapUserName);
+ env.put(Context.SECURITY_CREDENTIALS, password);
+ }
+
+ // GOOD - Test LDAP authentication with `ssl` configuration and basic authentication.
+ public void testCleartextLdapAuth5(String ldapUserName, String password, String serverName) {
+ String ldapUrl = "ldap://"+serverName+":389";
+ Hashtable environment = new Hashtable();
+ setSSL(environment);
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ setBasicAuth(environment, ldapUserName, password);
+ DirContext dirContext = new InitialLdapContext(environment, null);
+ }
+
+ // BAD - Test LDAP authentication with basic authentication.
+ public void testCleartextLdapAuth6(String ldapUserName, String password, String serverName) {
+ String ldapUrl = "ldap://"+serverName+":389";
+ Hashtable environment = new Hashtable();
+ environment.put(Context.INITIAL_CONTEXT_FACTORY,
+ "com.sun.jndi.ldap.LdapCtxFactory");
+ environment.put(Context.PROVIDER_URL, ldapUrl);
+ setBasicAuth(environment, ldapUserName, password);
+ DirContext dirContext = new InitialLdapContext(environment, null);
+ }
}
From 32c54628f8afc010e4000368d9abdecea6b50f7f Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 15 Jan 2021 12:32:13 +0000
Subject: [PATCH 012/429] Drop fieldName from the function for runtime
evaluation
---
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 20 +++++--------------
1 file changed, 5 insertions(+), 15 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 1a38f74092b..602dae21ad7 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -91,19 +91,11 @@ predicate isProviderUrlSetter(MethodAccess ma) {
/**
* Holds if `ma` sets `fieldValue` with attribute name `fieldName` to `envValue` in some `Hashtable`.
*/
-bindingset[fieldName, fieldValue, envValue]
-predicate hasEnvWithValue(MethodAccess ma, string fieldName, string fieldValue, string envValue) {
+bindingset[fieldValue, envValue]
+predicate hasEnvWithValue(MethodAccess ma, string fieldValue, string envValue) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
(ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
- (
- ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = fieldValue
- or
- exists(Field f |
- ma.getArgument(0) = f.getAnAccess() and
- f.hasName(fieldName) and
- f.getDeclaringType() instanceof TypeNamingContext
- )
- ) and
+ ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = fieldValue and
ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = envValue
}
@@ -111,15 +103,13 @@ predicate hasEnvWithValue(MethodAccess ma, string fieldName, string fieldValue,
* Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
*/
predicate isBasicAuthEnv(MethodAccess ma) {
- hasEnvWithValue(ma, "SECURITY_AUTHENTICATION", "java.naming.security.authentication", "simple")
+ hasEnvWithValue(ma, "java.naming.security.authentication", "simple")
}
/**
* Holds if `ma` sets `java.naming.security.protocol` (also known as `Context.SECURITY_PROTOCOL`) to `ssl` in some `Hashtable`.
*/
-predicate isSSLEnv(MethodAccess ma) {
- hasEnvWithValue(ma, "SECURITY_PROTOCOL", "java.naming.security.protocol", "ssl")
-}
+predicate isSSLEnv(MethodAccess ma) { hasEnvWithValue(ma, "java.naming.security.protocol", "ssl") }
/**
* A taint-tracking configuration for `ldap://` URL in LDAP authentication.
From a4cbd7037bd068228bef5448aa11ceeed92d1554 Mon Sep 17 00:00:00 2001
From: intrigus
Date: Fri, 15 Jan 2021 17:18:22 +0100
Subject: [PATCH 013/429] Java: Add tests for different versions.
Adds a test for version 6.24, because that version is not vulnerable.
The other test is for versions < 6.24, because these versions are
vulnerable.
---
.../JxBrowserWithoutCertValidation.expected | 1 -
.../JxBrowserWithoutCertValidation.expected | 1 +
.../JxBrowserWithoutCertValidation.qlref | 0
...xBrowserWithoutCertValidationV6_23_1.java} | 2 +-
.../security/CWE-295/jxbrowser-6.23.1/options | 1 +
.../JxBrowserWithoutCertValidation.expected | 0
.../JxBrowserWithoutCertValidation.qlref | 1 +
.../JxBrowserWithoutCertValidationV6_24.java | 36 +++++++++++++++++++
.../CWE-295/{ => jxbrowser-6.24}/options | 2 +-
.../jxbrowser/chromium/BoundsListener.java | 5 +++
.../teamdev/jxbrowser/chromium/Browser.java | 13 +++++++
.../chromium/CertificateErrorParams.java | 5 +++
.../jxbrowser/chromium/LoadHandler.java | 7 ++++
.../jxbrowser/chromium/LoadParams.java | 5 +++
14 files changed, 76 insertions(+), 3 deletions(-)
delete mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidation.expected
rename java/ql/test/experimental/query-tests/security/CWE-295/{ => jxbrowser-6.23.1}/JxBrowserWithoutCertValidation.qlref (100%)
rename java/ql/test/experimental/query-tests/security/CWE-295/{JxBrowserWithoutCertValidation.java => jxbrowser-6.23.1/JxBrowserWithoutCertValidationV6_23_1.java} (95%)
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/options
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.qlref
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidationV6_24.java
rename java/ql/test/experimental/query-tests/security/CWE-295/{ => jxbrowser-6.24}/options (71%)
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/BoundsListener.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/Browser.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadHandler.java
create mode 100644 java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadParams.java
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected b/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected
deleted file mode 100644
index 121de8759ac..00000000000
--- a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.expected
+++ /dev/null
@@ -1 +0,0 @@
-| JxBrowserWithoutCertValidation.java:17:27:17:39 | new Browser(...) | This JxBrowser instance allows man-in-the-middle attacks. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidation.expected b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidation.expected
new file mode 100644
index 00000000000..9b611b6cfc9
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidation.expected
@@ -0,0 +1 @@
+| JxBrowserWithoutCertValidationV6_23_1.java:17:27:17:39 | new Browser(...) | This JxBrowser instance allows man-in-the-middle attacks. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.qlref b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidation.qlref
similarity index 100%
rename from java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.qlref
rename to java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidation.qlref
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.java b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidationV6_23_1.java
similarity index 95%
rename from java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.java
rename to java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidationV6_23_1.java
index b7e2710842f..8f7be261413 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-295/JxBrowserWithoutCertValidation.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/JxBrowserWithoutCertValidationV6_23_1.java
@@ -3,7 +3,7 @@ import com.teamdev.jxbrowser.chromium.LoadHandler;
import com.teamdev.jxbrowser.chromium.LoadParams;
import com.teamdev.jxbrowser.chromium.CertificateErrorParams;
-public class JxBrowserWithoutCertValidation {
+public class JxBrowserWithoutCertValidationV6_23_1 {
public static void main(String[] args) {
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/options b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/options
new file mode 100644
index 00000000000..37339271f9c
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.23.1/options
@@ -0,0 +1 @@
+ //semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/jxbrowser-6.23.1
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.expected b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.expected
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.qlref b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.qlref
new file mode 100644
index 00000000000..cab6f2a4962
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidation.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidationV6_24.java b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidationV6_24.java
new file mode 100644
index 00000000000..62057fcb8ef
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/JxBrowserWithoutCertValidationV6_24.java
@@ -0,0 +1,36 @@
+import com.teamdev.jxbrowser.chromium.Browser;
+import com.teamdev.jxbrowser.chromium.LoadHandler;
+import com.teamdev.jxbrowser.chromium.LoadParams;
+import com.teamdev.jxbrowser.chromium.CertificateErrorParams;
+
+public class JxBrowserWithoutCertValidationV6_24 {
+
+ public static void main(String[] args) {
+
+ goodUsage();
+
+ goodUsage2();
+
+ }
+
+ private static void goodUsage() {
+ Browser browser = new Browser();
+ browser.loadURL("https://example.com");
+ // no further calls
+ // GOOD: On version 6.24 the browser properly validates certificates by default!
+ }
+
+ private static void goodUsage2() {
+ Browser browser = new Browser();
+ browser.setLoadHandler(new LoadHandler() {
+ public boolean onLoad(LoadParams params) {
+ return true;
+ }
+
+ public boolean onCertificateError(CertificateErrorParams params) {
+ return true; // GOOD: This means that loading will be cancelled on certificate errors
+ }
+ }); // GOOD: A secure `LoadHandler` is used.
+ browser.loadURL("https://example.com");
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/query-tests/security/CWE-295/options b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/options
similarity index 71%
rename from java/ql/test/experimental/query-tests/security/CWE-295/options
rename to java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/options
index 770bbcd38e6..f001bf777a2 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-295/options
+++ b/java/ql/test/experimental/query-tests/security/CWE-295/jxbrowser-6.24/options
@@ -1 +1 @@
- //semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/jxbrowser-6.23.1
\ No newline at end of file
+ //semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/jxbrowser-6.24
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/BoundsListener.java b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/BoundsListener.java
new file mode 100644
index 00000000000..39bb56e0565
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/BoundsListener.java
@@ -0,0 +1,5 @@
+package com.teamdev.jxbrowser.chromium;
+
+public interface BoundsListener {
+
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/Browser.java b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/Browser.java
new file mode 100644
index 00000000000..c32656e4228
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/Browser.java
@@ -0,0 +1,13 @@
+package com.teamdev.jxbrowser.chromium;
+
+public class Browser extends java.lang.Object {
+ public void setLoadHandler(LoadHandler handler) {
+ }
+
+ public void loadURL(String url) {
+ }
+
+ public void addBoundsListener(BoundsListener listener) {
+
+ }
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java
new file mode 100644
index 00000000000..904b98a6c51
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/CertificateErrorParams.java
@@ -0,0 +1,5 @@
+package com.teamdev.jxbrowser.chromium;
+
+public final class CertificateErrorParams extends Object {
+
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadHandler.java b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadHandler.java
new file mode 100644
index 00000000000..a628d88439c
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadHandler.java
@@ -0,0 +1,7 @@
+package com.teamdev.jxbrowser.chromium;
+
+public interface LoadHandler {
+ boolean onCertificateError(CertificateErrorParams params);
+
+ boolean onLoad(LoadParams params);
+}
\ No newline at end of file
diff --git a/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadParams.java b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadParams.java
new file mode 100644
index 00000000000..213e54f1dbc
--- /dev/null
+++ b/java/ql/test/experimental/stubs/jxbrowser-6.24/com/teamdev/jxbrowser/chromium/LoadParams.java
@@ -0,0 +1,5 @@
+package com.teamdev.jxbrowser.chromium;
+
+public final class LoadParams extends Object {
+
+}
\ No newline at end of file
From efb872ad1e96bcc703394777e80046ab8b043a79 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 14 Jan 2021 19:15:52 +0100
Subject: [PATCH 014/429] Python: Add HttpRedirectResponse concept
---
python/ql/src/semmle/python/Concepts.qll | 35 ++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/python/ql/src/semmle/python/Concepts.qll b/python/ql/src/semmle/python/Concepts.qll
index a79eaa2dc55..b81817d7f10 100644
--- a/python/ql/src/semmle/python/Concepts.qll
+++ b/python/ql/src/semmle/python/Concepts.qll
@@ -473,5 +473,40 @@ module HTTP {
}
}
}
+
+ /**
+ * A data-flow node that creates a HTTP redirect response on a server.
+ *
+ * Note: we don't require that this redirect must be sent to a client (a kind of
+ * "if a tree falls in a forest and nobody hears it" situation).
+ *
+ * Extend this class to refine existing API models. If you want to model new APIs,
+ * extend `HttpRedirectResponse::Range` instead.
+ */
+ class HttpRedirectResponse extends HttpResponse {
+ override HttpRedirectResponse::Range range;
+
+ HttpRedirectResponse() { this = range }
+
+ /** Gets the data-flow node that specifies the location of this HTTP redirect response. */
+ DataFlow::Node getRedirectLocation() { result = range.getRedirectLocation() }
+ }
+
+ /** Provides a class for modeling new HTTP redirect response APIs. */
+ module HttpRedirectResponse {
+ /**
+ * A data-flow node that creates a HTTP redirect response on a server.
+ *
+ * Note: we don't require that this redirect must be sent to a client (a kind of
+ * "if a tree falls in a forest and nobody hears it" situation).
+ *
+ * Extend this class to model new APIs. If you want to refine existing API models,
+ * extend `HttpResponse` instead.
+ */
+ abstract class Range extends HTTP::Server::HttpResponse::Range {
+ /** Gets the data-flow node that specifies the location of this HTTP redirect response. */
+ abstract DataFlow::Node getRedirectLocation();
+ }
+ }
}
}
From 501e5106226f4dc8cb287d9db4a099435c41708a Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 14:43:25 +0100
Subject: [PATCH 015/429] Python: Add redirect modeling tests (flask/django)
---
.../frameworks/django-v1/response_test.py | 30 +++++++++++++++----
.../frameworks/flask/response_test.py | 14 ++++++++-
.../test/experimental/meta/ConceptsTest.qll | 25 ++++++++++++++++
3 files changed, 63 insertions(+), 6 deletions(-)
diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
index 1713173d026..b36b1e1822c 100644
--- a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
@@ -1,4 +1,4 @@
-from django.http.response import HttpResponse, HttpResponseRedirect, JsonResponse, HttpResponseNotFound
+from django.http.response import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, JsonResponse, HttpResponseNotFound
# Not an XSS sink, since the Content-Type is not "text/html"
# FP reported in https://github.com/github/codeql-python-team/issues/38
@@ -18,15 +18,35 @@ def safe__manual_content_type(request):
# XSS FP reported in https://github.com/github/codeql/issues/3466
# Note: This should be an open-redirect sink, but not an XSS sink.
def or__redirect(request):
- return HttpResponseRedirect(request.GET.get("next")) # $HttpResponse mimetype=text/html
+ next = request.GET.get("next")
+ return HttpResponseRedirect(next) # $HttpResponse mimetype=text/html MISSING: HttpRedirectResponse redirectLocation=next
-def information_exposure_through_redirect(request, as_kw=False):
+def information_exposure_through_redirect(request, as_kw=False, perm_redirect=False):
# This is a contrived example, but possible
private = "private"
+ next = request.GET.get("next")
if as_kw:
- return HttpResponseRedirect(request.GET.get("next"), content=private) # $HttpResponse mimetype=text/html responseBody=private
+ return HttpResponseRedirect(next, content=private) # $HttpResponse mimetype=text/html responseBody=private MISSING: HttpRedirectResponse redirectLocation=next
else:
- return HttpResponseRedirect(request.GET.get("next"), private) # $HttpResponse mimetype=text/html responseBody=private
+ return HttpResponseRedirect(next, private) # $ HttpResponse mimetype=text/html responseBody=private MISSING: HttpRedirectResponse redirectLocation=next
+
+
+def perm_redirect(request):
+ private = "private"
+ next = request.GET.get("next")
+ return HttpResponsePermanentRedirect(next, private) # $ HttpResponse mimetype=text/html responseBody=private MISSING: HttpRedirectResponse redirectLocation=next
+
+
+def redirect_through_normal_response(request):
+ private = "private"
+ next = request.GET.get("next")
+
+ resp = HttpResponse() # $ HttpResponse mimetype=text/html
+ resp.status_code = 302
+ resp['Location'] = next # $ MISSING: redirectLocation=next
+ resp.content = private # $ MISSING: responseBody=private
+ return resp
+
# Ensure that simple subclasses are still vuln to XSS
def xss__not_found(request):
diff --git a/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py b/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py
index b067f173882..7c15704f7e3 100644
--- a/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py
@@ -1,6 +1,6 @@
import json
-from flask import Flask, make_response, jsonify, Response, request
+from flask import Flask, make_response, jsonify, Response, request, redirect
app = Flask(__name__)
@@ -172,6 +172,18 @@ def app_response_class(): # $requestHandler
# TODO: add tests for setting status code
# TODO: add test that manually creates a redirect by setting status code and suitable header.
+################################################################################
+# Redirect
+################################################################################
+
+
+@app.route("/redirect-simple") # $routeSetup="/redirect-simple"
+def redirect_simple(): # $requestHandler
+ next = request.args['next']
+ resp = redirect(next) # $ MISSING: HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation=next
+ return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
+
+
################################################################################
diff --git a/python/ql/test/experimental/meta/ConceptsTest.qll b/python/ql/test/experimental/meta/ConceptsTest.qll
index 0516fe5dac3..eafcb8b0ef9 100644
--- a/python/ql/test/experimental/meta/ConceptsTest.qll
+++ b/python/ql/test/experimental/meta/ConceptsTest.qll
@@ -239,6 +239,31 @@ class HttpServerHttpResponseTest extends InlineExpectationsTest {
}
}
+class HttpServerHttpRedirectResponseTest extends InlineExpectationsTest {
+ HttpServerHttpRedirectResponseTest() { this = "HttpServerHttpRedirectResponseTest" }
+
+ override string getARelevantTag() { result in ["HttpRedirectResponse", "redirectLocation"] }
+
+ override predicate hasActualResult(Location location, string element, string tag, string value) {
+ exists(location.getFile().getRelativePath()) and
+ (
+ exists(HTTP::Server::HttpRedirectResponse redirect |
+ location = redirect.getLocation() and
+ element = redirect.toString() and
+ value = "" and
+ tag = "HttpRedirectResponse"
+ )
+ or
+ exists(HTTP::Server::HttpRedirectResponse redirect |
+ location = redirect.getLocation() and
+ element = redirect.toString() and
+ value = value_from_expr(redirect.getRedirectLocation().asExpr()) and
+ tag = "redirectLocation"
+ )
+ )
+ }
+}
+
class FileSystemAccessTest extends InlineExpectationsTest {
FileSystemAccessTest() { this = "FileSystemAccessTest" }
From aea974ee0c1372a7872222667f77b22e24263eea Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 14:44:50 +0100
Subject: [PATCH 016/429] Python: Add redirect modeling for Flask
---
.../ql/src/semmle/python/frameworks/Flask.qll | 29 ++++++++++++++++++-
.../frameworks/flask/response_test.py | 2 +-
2 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Flask.qll b/python/ql/src/semmle/python/frameworks/Flask.qll
index 68f81e32ceb..c49c012f6f0 100644
--- a/python/ql/src/semmle/python/frameworks/Flask.qll
+++ b/python/ql/src/semmle/python/frameworks/Flask.qll
@@ -34,7 +34,7 @@ private module FlaskModel {
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node flask_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["request", "make_response", "Response", "views"] and
+ attr_name in ["request", "make_response", "Response", "views", "redirect"] and
(
t.start() and
result = DataFlow::importNode("flask" + "." + attr_name)
@@ -669,4 +669,31 @@ private module FlaskModel {
override string getMimetypeDefault() { result = "text/html" }
}
+
+ /**
+ * A call to the `flask.redirect` function.
+ *
+ * See https://flask.palletsprojects.com/en/1.1.x/api/#flask.redirect
+ */
+ private class FlaskRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
+ DataFlow::CfgNode {
+ override CallNode node;
+
+ FlaskRedirectCall() { node.getFunction() = flask_attr("redirect").asCfgNode() }
+
+ override DataFlow::Node getRedirectLocation() {
+ result.asCfgNode() in [node.getArg(0), node.getArgByName("location")]
+ }
+
+ override DataFlow::Node getBody() { none() }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
+
+ override string getMimetypeDefault() {
+ // note that while you're not able to set content yourself, the function will
+ // actually fill out some default content, that is served with mimetype
+ // `text/html`.
+ result = "text/html"
+ }
+ }
}
diff --git a/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py b/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py
index 7c15704f7e3..ec2de218c84 100644
--- a/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/flask/response_test.py
@@ -180,7 +180,7 @@ def app_response_class(): # $requestHandler
@app.route("/redirect-simple") # $routeSetup="/redirect-simple"
def redirect_simple(): # $requestHandler
next = request.args['next']
- resp = redirect(next) # $ MISSING: HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation=next
+ resp = redirect(next) # $ HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation=next
return resp # $ SPURIOUS: HttpResponse mimetype=text/html responseBody=resp
From ab607b803093fd8a6055b7236c25bdc97fff86e8 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 14:45:41 +0100
Subject: [PATCH 017/429] Python: Add redirect modeling for Django
---
python/ql/src/semmle/python/frameworks/Django.qll | 14 ++++++++++++--
.../frameworks/django-v1/response_test.py | 8 ++++----
2 files changed, 16 insertions(+), 6 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 674aedec566..4251b515322 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -724,7 +724,8 @@ private module Django {
*
* Use the predicate `HttpResponseRedirect::instance()` to get references to instances of `django.http.response.HttpResponseRedirect`.
*/
- abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
+ abstract class InstanceSource extends HttpResponse::InstanceSource,
+ HTTP::Server::HttpRedirectResponse::Range, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponseRedirect`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
@@ -739,6 +740,10 @@ private module Django {
result.asCfgNode() in [node.getArg(1), node.getArgByName("content")]
}
+ override DataFlow::Node getRedirectLocation() {
+ result.asCfgNode() in [node.getArg(0), node.getArgByName("redirect_to")]
+ }
+
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
@@ -790,7 +795,8 @@ private module Django {
*
* Use the predicate `HttpResponsePermanentRedirect::instance()` to get references to instances of `django.http.response.HttpResponsePermanentRedirect`.
*/
- abstract class InstanceSource extends HttpResponse::InstanceSource, DataFlow::Node { }
+ abstract class InstanceSource extends HttpResponse::InstanceSource,
+ HTTP::Server::HttpRedirectResponse::Range, DataFlow::Node { }
/** A direct instantiation of `django.http.response.HttpResponsePermanentRedirect`. */
private class ClassInstantiation extends InstanceSource, DataFlow::CfgNode {
@@ -805,6 +811,10 @@ private module Django {
result.asCfgNode() in [node.getArg(1), node.getArgByName("content")]
}
+ override DataFlow::Node getRedirectLocation() {
+ result.asCfgNode() in [node.getArg(0), node.getArgByName("redirect_to")]
+ }
+
// How to support the `headers` argument here?
override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
index b36b1e1822c..69cdd899c8d 100644
--- a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
@@ -19,22 +19,22 @@ def safe__manual_content_type(request):
# Note: This should be an open-redirect sink, but not an XSS sink.
def or__redirect(request):
next = request.GET.get("next")
- return HttpResponseRedirect(next) # $HttpResponse mimetype=text/html MISSING: HttpRedirectResponse redirectLocation=next
+ return HttpResponseRedirect(next) # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation=next
def information_exposure_through_redirect(request, as_kw=False, perm_redirect=False):
# This is a contrived example, but possible
private = "private"
next = request.GET.get("next")
if as_kw:
- return HttpResponseRedirect(next, content=private) # $HttpResponse mimetype=text/html responseBody=private MISSING: HttpRedirectResponse redirectLocation=next
+ return HttpResponseRedirect(next, content=private) # $HttpResponse mimetype=text/html responseBody=private HttpRedirectResponse redirectLocation=next
else:
- return HttpResponseRedirect(next, private) # $ HttpResponse mimetype=text/html responseBody=private MISSING: HttpRedirectResponse redirectLocation=next
+ return HttpResponseRedirect(next, private) # $ HttpResponse mimetype=text/html responseBody=private HttpRedirectResponse redirectLocation=next
def perm_redirect(request):
private = "private"
next = request.GET.get("next")
- return HttpResponsePermanentRedirect(next, private) # $ HttpResponse mimetype=text/html responseBody=private MISSING: HttpRedirectResponse redirectLocation=next
+ return HttpResponsePermanentRedirect(next, private) # $ HttpResponse mimetype=text/html responseBody=private HttpRedirectResponse redirectLocation=next
def redirect_through_normal_response(request):
From 9d8925ae6a47aa74edacf14e32f15221d5269e3f Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 15:16:55 +0100
Subject: [PATCH 018/429] Python: Extend url-redirect tests
Specifically to show how it currently handles prefixing user-input with known
constant.
I changed test to be Python 3 only since I wanted to use f-string.
---
.../Security/CWE-601/UrlRedirect.expected | 45 +++++++++++++
.../test/query-tests/Security/CWE-601/options | 2 +-
.../test/query-tests/Security/CWE-601/test.py | 64 ++++++++++++++++++-
3 files changed, 107 insertions(+), 4 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
index 87e3a9b98f1..ec681a905a5 100644
--- a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
+++ b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
@@ -3,5 +3,50 @@ edges
| test.py:7:14:7:25 | dict of externally controlled string | test.py:7:14:7:43 | externally controlled string |
| test.py:7:14:7:43 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
| test.py:7:14:7:43 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
+| test.py:30:17:30:28 | dict of externally controlled string | test.py:30:17:30:46 | externally controlled string |
+| test.py:30:17:30:28 | dict of externally controlled string | test.py:30:17:30:46 | externally controlled string |
+| test.py:30:17:30:46 | externally controlled string | test.py:31:41:31:49 | externally controlled string |
+| test.py:30:17:30:46 | externally controlled string | test.py:31:41:31:49 | externally controlled string |
+| test.py:31:12:31:50 | externally controlled string | test.py:32:21:32:24 | externally controlled string |
+| test.py:31:12:31:50 | externally controlled string | test.py:32:21:32:24 | externally controlled string |
+| test.py:31:41:31:49 | externally controlled string | test.py:31:12:31:50 | externally controlled string |
+| test.py:31:41:31:49 | externally controlled string | test.py:31:12:31:50 | externally controlled string |
+| test.py:37:17:37:28 | dict of externally controlled string | test.py:37:17:37:46 | externally controlled string |
+| test.py:37:17:37:28 | dict of externally controlled string | test.py:37:17:37:46 | externally controlled string |
+| test.py:37:17:37:46 | externally controlled string | test.py:38:32:38:40 | externally controlled string |
+| test.py:37:17:37:46 | externally controlled string | test.py:38:32:38:40 | externally controlled string |
+| test.py:38:12:38:42 | externally controlled string | test.py:39:21:39:24 | externally controlled string |
+| test.py:38:12:38:42 | externally controlled string | test.py:39:21:39:24 | externally controlled string |
+| test.py:38:32:38:40 | externally controlled string | test.py:38:12:38:42 | externally controlled string |
+| test.py:38:32:38:40 | externally controlled string | test.py:38:12:38:42 | externally controlled string |
+| test.py:53:17:53:28 | dict of externally controlled string | test.py:53:17:53:46 | externally controlled string |
+| test.py:53:17:53:28 | dict of externally controlled string | test.py:53:17:53:46 | externally controlled string |
+| test.py:53:17:53:46 | externally controlled string | test.py:54:14:54:22 | externally controlled string |
+| test.py:53:17:53:46 | externally controlled string | test.py:54:14:54:22 | externally controlled string |
+| test.py:54:14:54:22 | externally controlled string | test.py:54:14:54:41 | externally controlled string |
+| test.py:54:14:54:22 | externally controlled string | test.py:54:14:54:41 | externally controlled string |
+| test.py:54:14:54:41 | externally controlled string | test.py:55:21:55:26 | externally controlled string |
+| test.py:54:14:54:41 | externally controlled string | test.py:55:21:55:26 | externally controlled string |
+| test.py:60:17:60:28 | dict of externally controlled string | test.py:60:17:60:46 | externally controlled string |
+| test.py:60:17:60:28 | dict of externally controlled string | test.py:60:17:60:46 | externally controlled string |
+| test.py:60:17:60:46 | externally controlled string | test.py:61:40:61:48 | externally controlled string |
+| test.py:60:17:60:46 | externally controlled string | test.py:61:40:61:48 | externally controlled string |
+| test.py:61:14:61:49 | externally controlled string | test.py:62:21:62:26 | externally controlled string |
+| test.py:61:14:61:49 | externally controlled string | test.py:62:21:62:26 | externally controlled string |
+| test.py:61:40:61:48 | externally controlled string | test.py:61:14:61:49 | externally controlled string |
+| test.py:61:40:61:48 | externally controlled string | test.py:61:14:61:49 | externally controlled string |
+| test.py:67:17:67:28 | dict of externally controlled string | test.py:67:17:67:46 | externally controlled string |
+| test.py:67:17:67:28 | dict of externally controlled string | test.py:67:17:67:46 | externally controlled string |
+| test.py:67:17:67:46 | externally controlled string | test.py:68:17:68:25 | externally controlled string |
+| test.py:67:17:67:46 | externally controlled string | test.py:68:17:68:25 | externally controlled string |
+| test.py:68:14:68:41 | externally controlled string | test.py:69:21:69:26 | externally controlled string |
+| test.py:68:14:68:41 | externally controlled string | test.py:69:21:69:26 | externally controlled string |
+| test.py:68:17:68:25 | externally controlled string | test.py:68:14:68:41 | externally controlled string |
+| test.py:68:17:68:25 | externally controlled string | test.py:68:14:68:41 | externally controlled string |
#select
| test.py:8:21:8:26 | target | test.py:7:14:7:25 | dict of externally controlled string | test.py:8:21:8:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | Attribute | a user-provided value |
+| test.py:32:21:32:24 | safe | test.py:30:17:30:28 | dict of externally controlled string | test.py:32:21:32:24 | externally controlled string | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | Attribute | a user-provided value |
+| test.py:39:21:39:24 | safe | test.py:37:17:37:28 | dict of externally controlled string | test.py:39:21:39:24 | externally controlled string | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | Attribute | a user-provided value |
+| test.py:55:21:55:26 | unsafe | test.py:53:17:53:28 | dict of externally controlled string | test.py:55:21:55:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:53:17:53:28 | Attribute | a user-provided value |
+| test.py:62:21:62:26 | unsafe | test.py:60:17:60:28 | dict of externally controlled string | test.py:62:21:62:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:60:17:60:28 | Attribute | a user-provided value |
+| test.py:69:21:69:26 | unsafe | test.py:67:17:67:28 | dict of externally controlled string | test.py:69:21:69:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:67:17:67:28 | Attribute | a user-provided value |
diff --git a/python/ql/test/query-tests/Security/CWE-601/options b/python/ql/test/query-tests/Security/CWE-601/options
index 84717fe64cf..28b616e5f19 100644
--- a/python/ql/test/query-tests/Security/CWE-601/options
+++ b/python/ql/test/query-tests/Security/CWE-601/options
@@ -1 +1 @@
-semmle-extractor-options: --max-import-depth=2 -p ../lib
+semmle-extractor-options: --lang=3 --max-import-depth=2 -p ../lib
diff --git a/python/ql/test/query-tests/Security/CWE-601/test.py b/python/ql/test/query-tests/Security/CWE-601/test.py
index 3943f7276bd..dfc9e37281d 100644
--- a/python/ql/test/query-tests/Security/CWE-601/test.py
+++ b/python/ql/test/query-tests/Security/CWE-601/test.py
@@ -8,11 +8,69 @@ def hello():
return redirect(target, code=302)
-#Check for safe prefixes
+# Check for safe prefixes
@app.route('/ok')
def ok():
- untrusted = request.args.get('ok', '')
- safe = "safe"
+ untrusted = request.args.get('target', '')
+ safe = "https://safe.com/"
safe += untrusted
return redirect(safe, code=302)
+
+
+@app.route('/ok2')
+def ok2():
+ untrusted = request.args.get('target', '')
+ safe = "https://safe.com/" + untrusted
+ return redirect(safe, code=302)
+
+
+@app.route('/ok3')
+def ok3():
+ untrusted = request.args.get('target', '')
+ safe = "https://safe.com/{}".format(untrusted)
+ return redirect(safe, code=302) # FP
+
+
+@app.route('/ok4')
+def ok4():
+ untrusted = request.args.get('target', '')
+ safe = f"https://safe.com/{untrusted}"
+ return redirect(safe, code=302) # FP
+
+
+@app.route('/ok5')
+def ok5():
+ untrusted = request.args.get('target', '')
+ safe = "https://safe.com/%s" % untrusted
+ return redirect(safe, code=302)
+
+
+# Check that our sanitizer is not too broad
+
+@app.route('/not_ok1')
+def not_ok1():
+ untrusted = request.args.get('target', '')
+ unsafe = untrusted + "?login=success"
+ return redirect(unsafe, code=302)
+
+
+@app.route('/not_ok2')
+def not_ok2():
+ untrusted = request.args.get('target', '')
+ unsafe = "{}?login=success".format(untrusted)
+ return redirect(unsafe, code=302)
+
+
+@app.route('/not_ok3')
+def not_ok3():
+ untrusted = request.args.get('target', '')
+ unsafe = f"{untrusted}?login=success"
+ return redirect(unsafe, code=302)
+
+
+@app.route('/not_ok4')
+def not_ok4():
+ untrusted = request.args.get('target', '')
+ unsafe = "%s?login=success" % untrusted
+ return redirect(unsafe, code=302) # Missing result
From d8bfa3565fb117a1586cd5aa9d443e2558000ef3 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 15:44:51 +0100
Subject: [PATCH 019/429] Python: Simple port of URL redirect query
Still have not added sanitizer, but seems like old sanitizer was a bit too broad
(also covering %-formatting)
---
.../2021-01-19-port-url-redirect-query.md | 2 +
python/ql/src/Security/CWE-601/UrlRedirect.ql | 32 ++-----
.../CWE-601/UrlRedirect.ql | 41 +++++++++
.../python/security/dataflow/UrlRedirect.qll | 28 ++++++
.../Security/CWE-601/UrlRedirect.expected | 91 +++++++++----------
.../test/query-tests/Security/CWE-601/test.py | 8 +-
6 files changed, 122 insertions(+), 80 deletions(-)
create mode 100644 python/change-notes/2021-01-19-port-url-redirect-query.md
create mode 100644 python/ql/src/experimental/Security-old-dataflow/CWE-601/UrlRedirect.ql
create mode 100644 python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
diff --git a/python/change-notes/2021-01-19-port-url-redirect-query.md b/python/change-notes/2021-01-19-port-url-redirect-query.md
new file mode 100644
index 00000000000..a80093bc0f2
--- /dev/null
+++ b/python/change-notes/2021-01-19-port-url-redirect-query.md
@@ -0,0 +1,2 @@
+lgtm,codescanning
+* Ported URL redirection (`py/url-redirection`) query to use new data-flow library. This might result in different results, but overall a more robust and accurate analysis.
diff --git a/python/ql/src/Security/CWE-601/UrlRedirect.ql b/python/ql/src/Security/CWE-601/UrlRedirect.ql
index cb517043a36..944726e1c98 100644
--- a/python/ql/src/Security/CWE-601/UrlRedirect.ql
+++ b/python/ql/src/Security/CWE-601/UrlRedirect.ql
@@ -12,30 +12,10 @@
*/
import python
-import semmle.python.security.Paths
-import semmle.python.web.HttpRedirect
-import semmle.python.web.HttpRequest
-import semmle.python.security.strings.Untrusted
+import semmle.python.security.dataflow.UrlRedirect
+import DataFlow::PathGraph
-/** Url redirection is a problem only if the user controls the prefix of the URL */
-class UntrustedPrefixStringKind extends UntrustedStringKind {
- override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
- result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
- not tonode.(BinaryExprNode).getRight() = fromnode
- }
-}
-
-class UrlRedirectConfiguration extends TaintTracking::Configuration {
- UrlRedirectConfiguration() { this = "URL redirect configuration" }
-
- override predicate isSource(TaintTracking::Source source) {
- source instanceof HttpRequestTaintSource
- }
-
- override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
-}
-
-from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
-where config.hasFlowPath(src, sink)
-select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
- "a user-provided value"
+from UrlRedirectConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
+where config.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Untrusted URL redirection due to $@.", source.getNode(),
+ "A user-provided value"
diff --git a/python/ql/src/experimental/Security-old-dataflow/CWE-601/UrlRedirect.ql b/python/ql/src/experimental/Security-old-dataflow/CWE-601/UrlRedirect.ql
new file mode 100644
index 00000000000..cb517043a36
--- /dev/null
+++ b/python/ql/src/experimental/Security-old-dataflow/CWE-601/UrlRedirect.ql
@@ -0,0 +1,41 @@
+/**
+ * @name URL redirection from remote source
+ * @description URL redirection based on unvalidated user input
+ * may cause redirection to malicious web sites.
+ * @kind path-problem
+ * @problem.severity error
+ * @sub-severity low
+ * @id py/url-redirection
+ * @tags security
+ * external/cwe/cwe-601
+ * @precision high
+ */
+
+import python
+import semmle.python.security.Paths
+import semmle.python.web.HttpRedirect
+import semmle.python.web.HttpRequest
+import semmle.python.security.strings.Untrusted
+
+/** Url redirection is a problem only if the user controls the prefix of the URL */
+class UntrustedPrefixStringKind extends UntrustedStringKind {
+ override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
+ result = UntrustedStringKind.super.getTaintForFlowStep(fromnode, tonode) and
+ not tonode.(BinaryExprNode).getRight() = fromnode
+ }
+}
+
+class UrlRedirectConfiguration extends TaintTracking::Configuration {
+ UrlRedirectConfiguration() { this = "URL redirect configuration" }
+
+ override predicate isSource(TaintTracking::Source source) {
+ source instanceof HttpRequestTaintSource
+ }
+
+ override predicate isSink(TaintTracking::Sink sink) { sink instanceof HttpRedirectTaintSink }
+}
+
+from UrlRedirectConfiguration config, TaintedPathSource src, TaintedPathSink sink
+where config.hasFlowPath(src, sink)
+select sink.getSink(), src, sink, "Untrusted URL redirection due to $@.", src.getSource(),
+ "a user-provided value"
diff --git a/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll b/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
new file mode 100644
index 00000000000..d43df8417b0
--- /dev/null
+++ b/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
@@ -0,0 +1,28 @@
+/**
+ * Provides a taint-tracking configuration for detecting URL redirection
+ * vulnerabilities.
+ */
+
+import python
+import semmle.python.dataflow.new.DataFlow
+import semmle.python.dataflow.new.TaintTracking
+import semmle.python.Concepts
+import semmle.python.dataflow.new.RemoteFlowSources
+import semmle.python.dataflow.new.BarrierGuards
+
+/**
+ * A taint-tracking configuration for detecting URL redirection vulnerabilities.
+ */
+class UrlRedirectConfiguration extends TaintTracking::Configuration {
+ UrlRedirectConfiguration() { this = "UrlRedirectConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
+
+ override predicate isSink(DataFlow::Node sink) {
+ sink = any(HTTP::Server::HttpRedirectResponse e).getRedirectLocation()
+ }
+
+ override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
+ guard instanceof StringConstCompare
+ }
+}
diff --git a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
index ec681a905a5..e1bcf5948d9 100644
--- a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
+++ b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
@@ -1,52 +1,43 @@
edges
-| test.py:7:14:7:25 | dict of externally controlled string | test.py:7:14:7:43 | externally controlled string |
-| test.py:7:14:7:25 | dict of externally controlled string | test.py:7:14:7:43 | externally controlled string |
-| test.py:7:14:7:43 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
-| test.py:7:14:7:43 | externally controlled string | test.py:8:21:8:26 | externally controlled string |
-| test.py:30:17:30:28 | dict of externally controlled string | test.py:30:17:30:46 | externally controlled string |
-| test.py:30:17:30:28 | dict of externally controlled string | test.py:30:17:30:46 | externally controlled string |
-| test.py:30:17:30:46 | externally controlled string | test.py:31:41:31:49 | externally controlled string |
-| test.py:30:17:30:46 | externally controlled string | test.py:31:41:31:49 | externally controlled string |
-| test.py:31:12:31:50 | externally controlled string | test.py:32:21:32:24 | externally controlled string |
-| test.py:31:12:31:50 | externally controlled string | test.py:32:21:32:24 | externally controlled string |
-| test.py:31:41:31:49 | externally controlled string | test.py:31:12:31:50 | externally controlled string |
-| test.py:31:41:31:49 | externally controlled string | test.py:31:12:31:50 | externally controlled string |
-| test.py:37:17:37:28 | dict of externally controlled string | test.py:37:17:37:46 | externally controlled string |
-| test.py:37:17:37:28 | dict of externally controlled string | test.py:37:17:37:46 | externally controlled string |
-| test.py:37:17:37:46 | externally controlled string | test.py:38:32:38:40 | externally controlled string |
-| test.py:37:17:37:46 | externally controlled string | test.py:38:32:38:40 | externally controlled string |
-| test.py:38:12:38:42 | externally controlled string | test.py:39:21:39:24 | externally controlled string |
-| test.py:38:12:38:42 | externally controlled string | test.py:39:21:39:24 | externally controlled string |
-| test.py:38:32:38:40 | externally controlled string | test.py:38:12:38:42 | externally controlled string |
-| test.py:38:32:38:40 | externally controlled string | test.py:38:12:38:42 | externally controlled string |
-| test.py:53:17:53:28 | dict of externally controlled string | test.py:53:17:53:46 | externally controlled string |
-| test.py:53:17:53:28 | dict of externally controlled string | test.py:53:17:53:46 | externally controlled string |
-| test.py:53:17:53:46 | externally controlled string | test.py:54:14:54:22 | externally controlled string |
-| test.py:53:17:53:46 | externally controlled string | test.py:54:14:54:22 | externally controlled string |
-| test.py:54:14:54:22 | externally controlled string | test.py:54:14:54:41 | externally controlled string |
-| test.py:54:14:54:22 | externally controlled string | test.py:54:14:54:41 | externally controlled string |
-| test.py:54:14:54:41 | externally controlled string | test.py:55:21:55:26 | externally controlled string |
-| test.py:54:14:54:41 | externally controlled string | test.py:55:21:55:26 | externally controlled string |
-| test.py:60:17:60:28 | dict of externally controlled string | test.py:60:17:60:46 | externally controlled string |
-| test.py:60:17:60:28 | dict of externally controlled string | test.py:60:17:60:46 | externally controlled string |
-| test.py:60:17:60:46 | externally controlled string | test.py:61:40:61:48 | externally controlled string |
-| test.py:60:17:60:46 | externally controlled string | test.py:61:40:61:48 | externally controlled string |
-| test.py:61:14:61:49 | externally controlled string | test.py:62:21:62:26 | externally controlled string |
-| test.py:61:14:61:49 | externally controlled string | test.py:62:21:62:26 | externally controlled string |
-| test.py:61:40:61:48 | externally controlled string | test.py:61:14:61:49 | externally controlled string |
-| test.py:61:40:61:48 | externally controlled string | test.py:61:14:61:49 | externally controlled string |
-| test.py:67:17:67:28 | dict of externally controlled string | test.py:67:17:67:46 | externally controlled string |
-| test.py:67:17:67:28 | dict of externally controlled string | test.py:67:17:67:46 | externally controlled string |
-| test.py:67:17:67:46 | externally controlled string | test.py:68:17:68:25 | externally controlled string |
-| test.py:67:17:67:46 | externally controlled string | test.py:68:17:68:25 | externally controlled string |
-| test.py:68:14:68:41 | externally controlled string | test.py:69:21:69:26 | externally controlled string |
-| test.py:68:14:68:41 | externally controlled string | test.py:69:21:69:26 | externally controlled string |
-| test.py:68:17:68:25 | externally controlled string | test.py:68:14:68:41 | externally controlled string |
-| test.py:68:17:68:25 | externally controlled string | test.py:68:14:68:41 | externally controlled string |
+| test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target |
+| test.py:15:17:15:28 | ControlFlowNode for Attribute | test.py:18:21:18:24 | ControlFlowNode for safe |
+| test.py:23:17:23:28 | ControlFlowNode for Attribute | test.py:25:21:25:24 | ControlFlowNode for safe |
+| test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe |
+| test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe |
+| test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe |
+| test.py:53:17:53:28 | ControlFlowNode for Attribute | test.py:55:21:55:26 | ControlFlowNode for unsafe |
+| test.py:60:17:60:28 | ControlFlowNode for Attribute | test.py:62:21:62:26 | ControlFlowNode for unsafe |
+| test.py:67:17:67:28 | ControlFlowNode for Attribute | test.py:69:21:69:26 | ControlFlowNode for unsafe |
+| test.py:74:17:74:28 | ControlFlowNode for Attribute | test.py:76:21:76:26 | ControlFlowNode for unsafe |
+nodes
+| test.py:7:14:7:25 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:8:21:8:26 | ControlFlowNode for target | semmle.label | ControlFlowNode for target |
+| test.py:15:17:15:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:18:21:18:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
+| test.py:23:17:23:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:25:21:25:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
+| test.py:30:17:30:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:32:21:32:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
+| test.py:37:17:37:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:39:21:39:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
+| test.py:44:17:44:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:46:21:46:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
+| test.py:53:17:53:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:55:21:55:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
+| test.py:60:17:60:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:62:21:62:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
+| test.py:67:17:67:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:69:21:69:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
+| test.py:74:17:74:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:76:21:76:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
#select
-| test.py:8:21:8:26 | target | test.py:7:14:7:25 | dict of externally controlled string | test.py:8:21:8:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | Attribute | a user-provided value |
-| test.py:32:21:32:24 | safe | test.py:30:17:30:28 | dict of externally controlled string | test.py:32:21:32:24 | externally controlled string | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | Attribute | a user-provided value |
-| test.py:39:21:39:24 | safe | test.py:37:17:37:28 | dict of externally controlled string | test.py:39:21:39:24 | externally controlled string | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | Attribute | a user-provided value |
-| test.py:55:21:55:26 | unsafe | test.py:53:17:53:28 | dict of externally controlled string | test.py:55:21:55:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:53:17:53:28 | Attribute | a user-provided value |
-| test.py:62:21:62:26 | unsafe | test.py:60:17:60:28 | dict of externally controlled string | test.py:62:21:62:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:60:17:60:28 | Attribute | a user-provided value |
-| test.py:69:21:69:26 | unsafe | test.py:67:17:67:28 | dict of externally controlled string | test.py:69:21:69:26 | externally controlled string | Untrusted URL redirection due to $@. | test.py:67:17:67:28 | Attribute | a user-provided value |
+| test.py:8:21:8:26 | ControlFlowNode for target | test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:18:21:18:24 | ControlFlowNode for safe | test.py:15:17:15:28 | ControlFlowNode for Attribute | test.py:18:21:18:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:15:17:15:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:25:21:25:24 | ControlFlowNode for safe | test.py:23:17:23:28 | ControlFlowNode for Attribute | test.py:25:21:25:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:23:17:23:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:32:21:32:24 | ControlFlowNode for safe | test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:39:21:39:24 | ControlFlowNode for safe | test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:46:21:46:24 | ControlFlowNode for safe | test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:44:17:44:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:55:21:55:26 | ControlFlowNode for unsafe | test.py:53:17:53:28 | ControlFlowNode for Attribute | test.py:55:21:55:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:53:17:53:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:62:21:62:26 | ControlFlowNode for unsafe | test.py:60:17:60:28 | ControlFlowNode for Attribute | test.py:62:21:62:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:60:17:60:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:69:21:69:26 | ControlFlowNode for unsafe | test.py:67:17:67:28 | ControlFlowNode for Attribute | test.py:69:21:69:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:67:17:67:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:74:17:74:28 | ControlFlowNode for Attribute | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:74:17:74:28 | ControlFlowNode for Attribute | A user-provided value |
diff --git a/python/ql/test/query-tests/Security/CWE-601/test.py b/python/ql/test/query-tests/Security/CWE-601/test.py
index dfc9e37281d..680038576e1 100644
--- a/python/ql/test/query-tests/Security/CWE-601/test.py
+++ b/python/ql/test/query-tests/Security/CWE-601/test.py
@@ -15,14 +15,14 @@ def ok():
untrusted = request.args.get('target', '')
safe = "https://safe.com/"
safe += untrusted
- return redirect(safe, code=302)
+ return redirect(safe, code=302) # FP
@app.route('/ok2')
def ok2():
untrusted = request.args.get('target', '')
safe = "https://safe.com/" + untrusted
- return redirect(safe, code=302)
+ return redirect(safe, code=302) # FP
@app.route('/ok3')
@@ -43,7 +43,7 @@ def ok4():
def ok5():
untrusted = request.args.get('target', '')
safe = "https://safe.com/%s" % untrusted
- return redirect(safe, code=302)
+ return redirect(safe, code=302) # FP
# Check that our sanitizer is not too broad
@@ -73,4 +73,4 @@ def not_ok3():
def not_ok4():
untrusted = request.args.get('target', '')
unsafe = "%s?login=success" % untrusted
- return redirect(unsafe, code=302) # Missing result
+ return redirect(unsafe, code=302)
From 37aa9b9d060dceb2a6b8f080f0fa78655454b6d5 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 16:08:10 +0100
Subject: [PATCH 020/429] Python: Add prefix sanitizer on URL redirect query
This doesn't cover 100% of what we want to, but matches what we used to.
---
.../src/semmle/python/security/dataflow/UrlRedirect.qll | 9 +++++++++
.../query-tests/Security/CWE-601/UrlRedirect.expected | 8 --------
python/ql/test/query-tests/Security/CWE-601/test.py | 4 ++--
3 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll b/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
index d43df8417b0..7017bf8aa29 100644
--- a/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
+++ b/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
@@ -22,6 +22,15 @@ class UrlRedirectConfiguration extends TaintTracking::Configuration {
sink = any(HTTP::Server::HttpRedirectResponse e).getRedirectLocation()
}
+ override predicate isSanitizer(DataFlow::Node node) {
+ // Url redirection is a problem only if the user controls the prefix of the URL.
+ // This is a copy of the taint-sanitizer from the old points-to query, which doesn't
+ // cover formatting.
+ exists(BinaryExprNode string_concat | string_concat.getOp() instanceof Add |
+ string_concat.getRight() = node.asCfgNode()
+ )
+ }
+
override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) {
guard instanceof StringConstCompare
}
diff --git a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
index e1bcf5948d9..1e108f545ca 100644
--- a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
+++ b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
@@ -1,7 +1,5 @@
edges
| test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target |
-| test.py:15:17:15:28 | ControlFlowNode for Attribute | test.py:18:21:18:24 | ControlFlowNode for safe |
-| test.py:23:17:23:28 | ControlFlowNode for Attribute | test.py:25:21:25:24 | ControlFlowNode for safe |
| test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe |
| test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe |
| test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe |
@@ -12,10 +10,6 @@ edges
nodes
| test.py:7:14:7:25 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:8:21:8:26 | ControlFlowNode for target | semmle.label | ControlFlowNode for target |
-| test.py:15:17:15:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| test.py:18:21:18:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
-| test.py:23:17:23:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| test.py:25:21:25:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
| test.py:30:17:30:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:32:21:32:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
| test.py:37:17:37:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
@@ -32,8 +26,6 @@ nodes
| test.py:76:21:76:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
#select
| test.py:8:21:8:26 | ControlFlowNode for target | test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | ControlFlowNode for Attribute | A user-provided value |
-| test.py:18:21:18:24 | ControlFlowNode for safe | test.py:15:17:15:28 | ControlFlowNode for Attribute | test.py:18:21:18:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:15:17:15:28 | ControlFlowNode for Attribute | A user-provided value |
-| test.py:25:21:25:24 | ControlFlowNode for safe | test.py:23:17:23:28 | ControlFlowNode for Attribute | test.py:25:21:25:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:23:17:23:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:32:21:32:24 | ControlFlowNode for safe | test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:39:21:39:24 | ControlFlowNode for safe | test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:46:21:46:24 | ControlFlowNode for safe | test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:44:17:44:28 | ControlFlowNode for Attribute | A user-provided value |
diff --git a/python/ql/test/query-tests/Security/CWE-601/test.py b/python/ql/test/query-tests/Security/CWE-601/test.py
index 680038576e1..a2aee6562b2 100644
--- a/python/ql/test/query-tests/Security/CWE-601/test.py
+++ b/python/ql/test/query-tests/Security/CWE-601/test.py
@@ -15,14 +15,14 @@ def ok():
untrusted = request.args.get('target', '')
safe = "https://safe.com/"
safe += untrusted
- return redirect(safe, code=302) # FP
+ return redirect(safe, code=302)
@app.route('/ok2')
def ok2():
untrusted = request.args.get('target', '')
safe = "https://safe.com/" + untrusted
- return redirect(safe, code=302) # FP
+ return redirect(safe, code=302)
@app.route('/ok3')
From 526ccdd2279c20c4de657be44f2c2cba31462633 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 19 Jan 2021 16:21:27 +0100
Subject: [PATCH 021/429] Python: Add safe example from qhelp to qltests
---
.../query-tests/Security/CWE-601/UrlRedirect.expected | 8 ++++----
python/ql/test/query-tests/Security/CWE-601/test.py | 7 +++++++
2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
index 1e108f545ca..53098240671 100644
--- a/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
+++ b/python/ql/test/query-tests/Security/CWE-601/UrlRedirect.expected
@@ -3,10 +3,10 @@ edges
| test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe |
| test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe |
| test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe |
-| test.py:53:17:53:28 | ControlFlowNode for Attribute | test.py:55:21:55:26 | ControlFlowNode for unsafe |
| test.py:60:17:60:28 | ControlFlowNode for Attribute | test.py:62:21:62:26 | ControlFlowNode for unsafe |
| test.py:67:17:67:28 | ControlFlowNode for Attribute | test.py:69:21:69:26 | ControlFlowNode for unsafe |
| test.py:74:17:74:28 | ControlFlowNode for Attribute | test.py:76:21:76:26 | ControlFlowNode for unsafe |
+| test.py:81:17:81:28 | ControlFlowNode for Attribute | test.py:83:21:83:26 | ControlFlowNode for unsafe |
nodes
| test.py:7:14:7:25 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:8:21:8:26 | ControlFlowNode for target | semmle.label | ControlFlowNode for target |
@@ -16,20 +16,20 @@ nodes
| test.py:39:21:39:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
| test.py:44:17:44:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:46:21:46:24 | ControlFlowNode for safe | semmle.label | ControlFlowNode for safe |
-| test.py:53:17:53:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
-| test.py:55:21:55:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
| test.py:60:17:60:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:62:21:62:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
| test.py:67:17:67:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:69:21:69:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
| test.py:74:17:74:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
| test.py:76:21:76:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
+| test.py:81:17:81:28 | ControlFlowNode for Attribute | semmle.label | ControlFlowNode for Attribute |
+| test.py:83:21:83:26 | ControlFlowNode for unsafe | semmle.label | ControlFlowNode for unsafe |
#select
| test.py:8:21:8:26 | ControlFlowNode for target | test.py:7:14:7:25 | ControlFlowNode for Attribute | test.py:8:21:8:26 | ControlFlowNode for target | Untrusted URL redirection due to $@. | test.py:7:14:7:25 | ControlFlowNode for Attribute | A user-provided value |
| test.py:32:21:32:24 | ControlFlowNode for safe | test.py:30:17:30:28 | ControlFlowNode for Attribute | test.py:32:21:32:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:30:17:30:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:39:21:39:24 | ControlFlowNode for safe | test.py:37:17:37:28 | ControlFlowNode for Attribute | test.py:39:21:39:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:37:17:37:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:46:21:46:24 | ControlFlowNode for safe | test.py:44:17:44:28 | ControlFlowNode for Attribute | test.py:46:21:46:24 | ControlFlowNode for safe | Untrusted URL redirection due to $@. | test.py:44:17:44:28 | ControlFlowNode for Attribute | A user-provided value |
-| test.py:55:21:55:26 | ControlFlowNode for unsafe | test.py:53:17:53:28 | ControlFlowNode for Attribute | test.py:55:21:55:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:53:17:53:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:62:21:62:26 | ControlFlowNode for unsafe | test.py:60:17:60:28 | ControlFlowNode for Attribute | test.py:62:21:62:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:60:17:60:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:69:21:69:26 | ControlFlowNode for unsafe | test.py:67:17:67:28 | ControlFlowNode for Attribute | test.py:69:21:69:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:67:17:67:28 | ControlFlowNode for Attribute | A user-provided value |
| test.py:76:21:76:26 | ControlFlowNode for unsafe | test.py:74:17:74:28 | ControlFlowNode for Attribute | test.py:76:21:76:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:74:17:74:28 | ControlFlowNode for Attribute | A user-provided value |
+| test.py:83:21:83:26 | ControlFlowNode for unsafe | test.py:81:17:81:28 | ControlFlowNode for Attribute | test.py:83:21:83:26 | ControlFlowNode for unsafe | Untrusted URL redirection due to $@. | test.py:81:17:81:28 | ControlFlowNode for Attribute | A user-provided value |
diff --git a/python/ql/test/query-tests/Security/CWE-601/test.py b/python/ql/test/query-tests/Security/CWE-601/test.py
index a2aee6562b2..381eb3a4bae 100644
--- a/python/ql/test/query-tests/Security/CWE-601/test.py
+++ b/python/ql/test/query-tests/Security/CWE-601/test.py
@@ -46,6 +46,13 @@ def ok5():
return redirect(safe, code=302) # FP
+@app.route('/const-str-compare')
+def const_str_compare():
+ target = request.args.get('target', '')
+ if target == "example.com/":
+ return redirect(target, code=302)
+
+
# Check that our sanitizer is not too broad
@app.route('/not_ok1')
From 5c6f5b7b33311d5a0072361c0828af0d3077a1a2 Mon Sep 17 00:00:00 2001
From: Luke Cartey <5377966+lcartey@users.noreply.github.com>
Date: Wed, 20 Jan 2021 16:53:03 +0000
Subject: [PATCH 022/429] Java: Track taint through Spring Java bean getters on
super types
---
.../code/java/dataflow/internal/TaintTrackingUtil.qll | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
index b627d59783f..35f820f9521 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
@@ -345,7 +345,9 @@ private predicate taintPreservingQualifierToMethod(Method m) {
m.getDeclaringType() instanceof TypeUri and
m.hasName("toURL")
or
- m instanceof GetterMethod and m.getDeclaringType() instanceof SpringUntrustedDataType
+ m instanceof GetterMethod and
+ m.getDeclaringType().getASubtype*() instanceof SpringUntrustedDataType and
+ not m.getDeclaringType() instanceof TypeObject
or
m.getDeclaringType() instanceof SpringHttpEntity and
m.getName().regexpMatch("getBody|getHeaders")
@@ -684,7 +686,8 @@ private class FormatterCallable extends TaintPreservingCallable {
(
this.hasName(["format", "out", "toString"])
or
- this.(Constructor)
+ this
+ .(Constructor)
.getParameterType(0)
.(RefType)
.getASourceSupertype*()
From 48083d657a3d5552c334ec7aa126456dce30da3a Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 21 Jan 2021 13:40:58 +0100
Subject: [PATCH 023/429] Python: Apply code-review suggestion
Co-authored-by: yoff
---
python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll b/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
index 7017bf8aa29..27c1dabab98 100644
--- a/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
+++ b/python/ql/src/semmle/python/security/dataflow/UrlRedirect.qll
@@ -24,7 +24,7 @@ class UrlRedirectConfiguration extends TaintTracking::Configuration {
override predicate isSanitizer(DataFlow::Node node) {
// Url redirection is a problem only if the user controls the prefix of the URL.
- // This is a copy of the taint-sanitizer from the old points-to query, which doesn't
+ // TODO: This is a copy of the taint-sanitizer from the old points-to query, which doesn't
// cover formatting.
exists(BinaryExprNode string_concat | string_concat.getOp() instanceof Add |
string_concat.getRight() = node.asCfgNode()
From 2f86937e5ae5f58693f55a32856b9cdfe475321a Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 21 Jan 2021 13:44:56 +0100
Subject: [PATCH 024/429] Python: Remove unused param in test code
---
.../library-tests/frameworks/django-v1/response_test.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
index 69cdd899c8d..f214cfb63d3 100644
--- a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
@@ -21,7 +21,7 @@ def or__redirect(request):
next = request.GET.get("next")
return HttpResponseRedirect(next) # $HttpResponse mimetype=text/html HttpRedirectResponse redirectLocation=next
-def information_exposure_through_redirect(request, as_kw=False, perm_redirect=False):
+def information_exposure_through_redirect(request, as_kw=False):
# This is a contrived example, but possible
private = "private"
next = request.GET.get("next")
From 7a76a5134e7e01537231fc6f7a23dd51965ad913 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Thu, 21 Jan 2021 14:04:11 +0100
Subject: [PATCH 025/429] Python: Add redirect modeling for Tornado
After making https://github.com/github/codeql/pull/4995, I realized how easy
this would be :D
Will need to do some manual merge-conflict handling, but it should be all good
:)
---
.../src/semmle/python/frameworks/Tornado.qll | 38 +++++++++++++++++++
.../library-tests/frameworks/tornado/basic.py | 2 +-
2 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll
index 995fb19747d..29c91654b4a 100644
--- a/python/ql/src/semmle/python/frameworks/Tornado.qll
+++ b/python/ql/src/semmle/python/frameworks/Tornado.qll
@@ -216,6 +216,17 @@ private module Tornado {
/** Gets a reference to one of the methods `get_arguments`, `get_body_arguments`, `get_query_arguments`. */
DataFlow::Node argumentsMethod() { result = argumentsMethod(DataFlow::TypeTracker::end()) }
+ /** Gets a reference the `redirect` method. */
+ private DataFlow::Node redirectMethod(DataFlow::TypeTracker t) {
+ t.startInAttr("redirect") and
+ result = instance()
+ or
+ exists(DataFlow::TypeTracker t2 | result = redirectMethod(t2).track(t2, t))
+ }
+
+ /** Gets a reference the `redirect` method. */
+ DataFlow::Node redirectMethod() { result = redirectMethod(DataFlow::TypeTracker::end()) }
+
private class AdditionalTaintStep extends TaintTracking::AdditionalTaintStep {
override predicate step(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// Method access
@@ -540,4 +551,31 @@ private module Tornado {
not result = this.getArg(0)
}
}
+
+ // ---------------------------------------------------------------------------
+ // Response modeling
+ // ---------------------------------------------------------------------------
+ /**
+ * A call to `tornado.web.RequestHandler.write` method.
+ *
+ * See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write
+ */
+ private class TornadoRequestHandlerRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
+ DataFlow::CfgNode {
+ override CallNode node;
+
+ TornadoRequestHandlerRedirectCall() {
+ node.getFunction() = tornado::web::RequestHandler::redirectMethod().asCfgNode()
+ }
+
+ override DataFlow::Node getRedirectLocation() {
+ result.asCfgNode() in [node.getArg(0), node.getArgByName("url")]
+ }
+
+ override DataFlow::Node getBody() { none() }
+
+ override string getMimetypeDefault() { none() }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
+ }
}
diff --git a/python/ql/test/experimental/library-tests/frameworks/tornado/basic.py b/python/ql/test/experimental/library-tests/frameworks/tornado/basic.py
index 6733de040b1..24b37d74a1b 100644
--- a/python/ql/test/experimental/library-tests/frameworks/tornado/basic.py
+++ b/python/ql/test/experimental/library-tests/frameworks/tornado/basic.py
@@ -25,7 +25,7 @@ class RedirectHandler(tornado.web.RequestHandler):
req = self.request
h = req.headers
url = h["url"]
- self.redirect(url)
+ self.redirect(url) # $ HttpRedirectResponse HttpResponse redirectLocation=url
class BaseReverseInheritance(tornado.web.RequestHandler):
From 9071ba2f99ef3f09732999b517a72d45b30c210e Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Mon, 25 Jan 2021 00:06:19 +0300
Subject: [PATCH 026/429] Add files via upload
---
...ctingAndHandlingMemoryAllocationErrors.cpp | 32 +++++++
...ingAndHandlingMemoryAllocationErrors.qhelp | 29 ++++++
...ectingAndHandlingMemoryAllocationErrors.ql | 93 +++++++++++++++++++
3 files changed, 154 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
new file mode 100644
index 00000000000..6fc3bc2f893
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
@@ -0,0 +1,32 @@
+// BAD: no memory allocation errors are detected
+void f(const int *array, std::size_t size) noexcept {
+ int *copy = new int[size];
+ std::memcpy(copy, array, size * sizeof(*copy));
+ // ...
+ delete [] copy;
+}
+// GOOD: memory allocation errors are detected
+void f(const int *array, std::size_t size) noexcept {
+ int *copy;
+ try {
+ copy = new int[size];
+ } catch(std::bad_alloc) {
+ // Handle error
+ return;
+ }
+ // At this point, copy has been initialized to allocated memory
+ std::memcpy(copy, array, size * sizeof(*copy));
+ // ...
+ delete [] copy;
+}
+// GOOD: memory allocation errors are detected
+void f(const int *array, std::size_t size) noexcept {
+ int *copy = new (std::nothrow) int[size];
+ if (!copy) {
+ // Handle error
+ return;
+ }
+ std::memcpy(copy, array, size * sizeof(*copy));
+ // ...
+ delete [] copy;
+}
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
new file mode 100644
index 00000000000..4b7941a5846
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
@@ -0,0 +1,29 @@
+
+
+
+
when using the new operator to allocate memory, you need to pay attention to the different way of detecting errors. so ::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error. the programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.
+
+
Loss of detection probably refers to use cases where memory allocation using your own solutions with strong nesting. It is also possible when using a buffer in the form of fields of different structures with the same names.
+
+
+
+
+
We recommend using the error detection method, depending on the selected memory allocation method..
+
+
+
+
The following file demonstrates various approaches to detecting memory allocation errors using the new operator.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
new file mode 100644
index 00000000000..383c8a1f128
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
@@ -0,0 +1,93 @@
+/**
+ * @name Сonfusion In Detecting And Handling Memory Allocation Errors
+ * @description --::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error.
+ * --the programmer can get confused when check the error that occurs when allocating memory incorrectly.
+ * --Making a call of this type may result in a zero byte being written just outside the buffer.
+ * @kind problem
+ * @id cpp/detect-and-handle-memory-allocation-errors
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-570
+ */
+
+import cpp
+
+/**
+ * Lookup if condition compare with 0
+ */
+class IfCompareWithZero extends IfStmt {
+ IfCompareWithZero() {
+ this.getCondition().(EQExpr).getAChild().getValue() = "0"
+ or
+ this.getCondition().(NEExpr).getAChild().getValue() = "0" and
+ this.hasElse()
+ }
+}
+
+/**
+ * lookup for calls to `operator new`, with incorrect error handling.
+ */
+class WrongCheckErrorOperatorNew extends FunctionCall {
+ Expr exp;
+
+ WrongCheckErrorOperatorNew() {
+ this = exp.(NewOrNewArrayExpr).getAChild().(FunctionCall) and
+ (
+ this.getTarget().hasGlobalOrStdName("operator new")
+ or
+ this.getTarget().hasGlobalOrStdName("operator new[]")
+ )
+ }
+
+ /**
+ * Holds if handler `try ... catch` exists.
+ */
+ predicate isExistsTryCatchBlock() {
+ exists(TryStmt tb, AssignExpr aex, Initializer it |
+ tb.getAChild*() = exp
+ or
+ exp = it.getExpr() and
+ tb.getAChild*().(DeclStmt).getADeclaration() = it.getDeclaration()
+ or
+ aex.getAChild*() = exp and
+ tb.getAChild*().(AssignExpr) = aex
+ )
+ }
+
+ /**
+ * Holds if results call `operator new` check in `operator if`.
+ */
+ predicate isExistsIfCondition() {
+ exists(IfCompareWithZero ifc, AssignExpr aex, Initializer it |
+ // call `operator new` directly from the condition of `operator if`.
+ this = ifc.getCondition().getAChild()
+ or
+ // check results call `operator new` with variable appropriation
+ postDominates(ifc, this) and
+ aex.getAChild() = exp and
+ ifc.getCondition().getAChild().(VariableAccess).getTarget() =
+ aex.getLValue().(VariableAccess).getTarget()
+ or
+ // check results call `operator new` with declaration variable
+ postDominates(ifc, this) and
+ exp = it.getExpr() and
+ it.getDeclaration() = ifc.getCondition().getAChild().(VariableAccess).getTarget()
+ )
+ }
+
+ /**
+ * Holds if `(std::nothrow)` exists in call `operator new`.
+ */
+ predicate isExistsNothrow() { this.getAChild().toString() = "nothrow" }
+}
+
+from WrongCheckErrorOperatorNew op
+where
+ // use call `operator new` with `(std::nothrow)` and checking error using `try ... catch` block and not `operator if`
+ op.isExistsNothrow() and not op.isExistsIfCondition() and op.isExistsTryCatchBlock()
+ or
+ // use call `operator new` without `(std::nothrow)` and checking error using `operator if` and not `try ... catch` block
+ not op.isExistsNothrow() and not op.isExistsTryCatchBlock() and op.isExistsIfCondition()
+select op, "memory allocation error check is incorrect or missing"
From 20e19ec4677c1e414fceb17d16de31327967fe5a Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Mon, 25 Jan 2021 00:09:55 +0300
Subject: [PATCH 027/429] Add files via upload
---
...AndHandlingMemoryAllocationErrors.expected | 5 +
...ingAndHandlingMemoryAllocationErrors.qlref | 1 +
.../CWE/CWE-570/semmle/tests/test.cpp | 97 +++++++++++++++++++
3 files changed, 103 insertions(+)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected
new file mode 100644
index 00000000000..80e82cff212
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.expected
@@ -0,0 +1,5 @@
+| test.cpp:30:15:30:26 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:38:9:38:20 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:50:13:50:38 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:51:22:51:47 | call to operator new[] | memory allocation error check is incorrect or missing |
+| test.cpp:53:18:53:43 | call to operator new[] | memory allocation error check is incorrect or missing |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref
new file mode 100644
index 00000000000..fc3252ef122
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/WrongInDetectingAndHandlingMemoryAllocationErrors.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
new file mode 100644
index 00000000000..6f03f896024
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
@@ -0,0 +1,97 @@
+#define NULL ((void*)0)
+class exception {};
+
+namespace std{
+ struct nothrow_t {};
+ typedef unsigned long size_t;
+ class bad_alloc{
+ const char* what() const throw();
+ };
+ extern const std::nothrow_t nothrow;
+}
+
+using namespace std;
+
+void* operator new(std::size_t _Size);
+void* operator new[](std::size_t _Size);
+void* operator new( std::size_t count, const std::nothrow_t& tag );
+void* operator new[]( std::size_t count, const std::nothrow_t& tag );
+
+void badNew_0_0()
+{
+ while (true) {
+ new int[100];
+ if(!(new int[100]))
+ return;
+ }
+}
+void badNew_0_1()
+{
+ int * i = new int[100];
+ if(i == 0)
+ return;
+ if(!i)
+ return;
+ if(i == NULL)
+ return;
+ int * j;
+ j = new int[100];
+ if(j == 0)
+ return;
+ if(!j)
+ return;
+ if(j == NULL)
+ return;
+}
+void badNew_1_0()
+{
+ try {
+ while (true) {
+ new(std::nothrow) int[100];
+ int* p = new(std::nothrow) int[100];
+ int* p1;
+ p1 = new(std::nothrow) int[100];
+ }
+ } catch (const exception &){//const std::bad_alloc& e) {
+// std::cout << e.what() << '\n';
+ }
+}
+void badNew_1_1()
+{
+ while (true) {
+ int* p = new(std::nothrow) int[100];
+ new(std::nothrow) int[100];
+ }
+}
+
+void goodNew_0_0()
+{
+ try {
+ while (true) {
+ new int[100];
+ }
+ } catch (const exception &){//const std::bad_alloc& e) {
+// std::cout << e.what() << '\n';
+ }
+}
+
+void goodNew_1_0()
+{
+ while (true) {
+ int* p = new(std::nothrow) int[100];
+ if (p == nullptr) {
+// std::cout << "Allocation returned nullptr\n";
+ break;
+ }
+ int* p1;
+ p1 = new(std::nothrow) int[100];
+ if (p1 == nullptr) {
+// std::cout << "Allocation returned nullptr\n";
+ break;
+ }
+ if (new(std::nothrow) int[100] == nullptr) {
+// std::cout << "Allocation returned nullptr\n";
+ break;
+ }
+ }
+}
From 9ae503a5a820ad535a949e1d13b9f368aea7a1eb Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Mon, 25 Jan 2021 00:30:35 +0300
Subject: [PATCH 028/429] Add files via upload
---
...emoryLocationAfterEndOfBufferUsingStrlen.c | 9 ++++++
...yLocationAfterEndOfBufferUsingStrlen.qhelp | 31 +++++++++++++++++++
...moryLocationAfterEndOfBufferUsingStrlen.ql | 25 +++++++++++++++
3 files changed, 65 insertions(+)
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
create mode 100644 cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c
new file mode 100644
index 00000000000..ba78d4b97d1
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.c
@@ -0,0 +1,9 @@
+// BAD: if buffer does not have a terminal zero, then access outside the allocated memory is possible.
+
+buffer[strlen(buffer)] = 0;
+
+
+// GOOD: we will eliminate dangerous behavior if we use a different method of calculating the length.
+size_t len;
+...
+buffer[len] = 0
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
new file mode 100644
index 00000000000..372a3e1d1c4
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
@@ -0,0 +1,31 @@
+
+
+
+
Potentially dangerous use of the strlen function to calculate the length of a string.
+The expression buffer[strlen(buffer)] = 0 is potentially dangerous, if the variable buffer does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
+If terminal zero is present, then the specified expression is meaningless.
+
+
False positives include heavily nested strlen. This situation is unlikely.
+
+
+
+
+
We recommend using another method for calculating the string length
+
+
+
+
The following example demonstrates an erroneous and corrected use of the strlen function.
+
+
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
new file mode 100644
index 00000000000..80c7f2104ab
--- /dev/null
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
@@ -0,0 +1,25 @@
+/**
+ * @name Access Of Memory Location After End Of Buffer
+ * @description --The expression buffer [strlen (buffer)] = 0 is potentially dangerous, if the variable buffer does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
+ * --If terminal zero is present, then the specified expression is meaningless.
+ * --We recommend using another method for calculating the string length.
+ * @kind problem
+ * @id cpp/access-memory-location-after-end-buffer
+ * @problem.severity warning
+ * @precision medium
+ * @tags correctness
+ * security
+ * external/cwe/cwe-788
+ */
+
+import cpp
+import semmle.code.cpp.valuenumbering.HashCons
+
+from FunctionCall fc, AssignExpr expr, ArrayExpr exprarr
+where
+ fc.getTarget().hasGlobalOrStdName("strlen") and
+ exprarr = expr.getLValue() and
+ expr.getRValue().getValue().toInt() = 0 and
+ exprarr.getArrayOffset() = fc and
+ hashCons(fc.getArgument(0)) = hashCons(exprarr.getArrayBase())
+select expr, "use a different method to calculate the length."
From b899229298218b20c34540219088f0bb06170728 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Mon, 25 Jan 2021 00:33:54 +0300
Subject: [PATCH 029/429] Add files via upload
---
...cationAfterEndOfBufferUsingStrlen.expected | 9 ++++
...yLocationAfterEndOfBufferUsingStrlen.qlref | 1 +
.../Security/CWE/CWE-788/semmle/tests/test.c | 44 +++++++++++++++++++
3 files changed, 54 insertions(+)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
new file mode 100644
index 00000000000..82d3e224993
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
@@ -0,0 +1,9 @@
+| test.c:13:3:13:24 | ... = ... | use a different method to calculate the length. |
+| test.c:14:3:14:40 | ... = ... | use a different method to calculate the length. |
+| test.c:15:3:15:40 | ... = ... | use a different method to calculate the length. |
+| test.c:16:3:16:44 | ... = ... | use a different method to calculate the length. |
+| test.c:17:3:17:44 | ... = ... | use a different method to calculate the length. |
+| test.c:18:3:18:48 | ... = ... | use a different method to calculate the length. |
+| test.c:19:3:19:48 | ... = ... | use a different method to calculate the length. |
+| test.c:20:3:20:50 | ... = ... | use a different method to calculate the length. |
+| test.c:21:3:21:50 | ... = ... | use a different method to calculate the length. |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
new file mode 100644
index 00000000000..6ba005d087a
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
new file mode 100644
index 00000000000..84487670bf7
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
@@ -0,0 +1,44 @@
+struct buffers
+{
+ unsigned char buff1[50];
+ unsigned char *buff2;
+} globalBuff1,*globalBuff2,globalBuff1_c,*globalBuff2_c;
+
+
+void badFunc0(){
+ unsigned char buff1[12];
+ struct buffers buffAll;
+ struct buffers * buffAll1;
+
+ buff1[strlen(buff1)]=0;
+ buffAll.buff1[strlen(buffAll.buff1)]=0;
+ buffAll.buff2[strlen(buffAll.buff2)]=0;
+ buffAll1->buff1[strlen(buffAll1->buff1)]=0;
+ buffAll1->buff2[strlen(buffAll1->buff2)]=0;
+ globalBuff1.buff1[strlen(globalBuff1.buff1)]=0;
+ globalBuff1.buff2[strlen(globalBuff1.buff2)]=0;
+ globalBuff2->buff1[strlen(globalBuff2->buff1)]=0;
+ globalBuff2->buff2[strlen(globalBuff2->buff2)]=0;
+}
+void noBadFunc0(){
+ unsigned char buff1[12],buff1_c[12];
+ struct buffers buffAll,buffAll_c;
+ struct buffers * buffAll1,*buffAll1_c;
+
+ buff1[strlen(buff1_c)]=0;
+ buffAll.buff1[strlen(buffAll_c.buff1)]=0;
+ buffAll.buff2[strlen(buffAll.buff1)]=0;
+ buffAll1->buff1[strlen(buffAll1_c->buff1)]=0;
+ buffAll1->buff2[strlen(buffAll1->buff1)]=0;
+ globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0;
+ globalBuff1.buff2[strlen(globalBuff1.buff1)]=0;
+ globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0;
+ globalBuff2->buff2[strlen(globalBuff2->buff1)]=0;
+}
+void goodFunc0(){
+ unsigned char buffer[12];
+ int i;
+ for(i = 0; i < 6; i++)
+ buffer[i] = 'A';
+ buffer[i]=0;
+}
From de0bbc8826982276d00bf9edc655443801e3ca5c Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Tue, 26 Jan 2021 23:47:07 +0300
Subject: [PATCH 030/429] Apply suggestions from code review
Co-authored-by: Mathias Vorreiter Pedersen
---
.../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp | 2 +-
.../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql | 5 ++---
2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
index 372a3e1d1c4..51424ce7619 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qhelp
@@ -17,7 +17,7 @@ If terminal zero is present, then the specified expression is meaningless.
The following example demonstrates an erroneous and corrected use of the strlen function.
-
+
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
index 80c7f2104ab..a1d4a169b20 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
@@ -1,8 +1,7 @@
/**
* @name Access Of Memory Location After End Of Buffer
- * @description --The expression buffer [strlen (buffer)] = 0 is potentially dangerous, if the variable buffer does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
- * --If terminal zero is present, then the specified expression is meaningless.
- * --We recommend using another method for calculating the string length.
+ * @description The expression `buffer [strlen (buffer)] = 0` is potentially dangerous, if the variable `buffer` does not have a terminal zero, then access beyond the bounds of the allocated memory is possible, which will lead to undefined behavior.
+ * If terminal zero is present, then the specified expression is meaningless.
* @kind problem
* @id cpp/access-memory-location-after-end-buffer
* @problem.severity warning
From fc9d2190574dcf0ab87dcb876a17530493fc35fc Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Tue, 26 Jan 2021 23:50:54 +0300
Subject: [PATCH 031/429] Update
AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
---
.../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
index a1d4a169b20..77fc29719f6 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
@@ -12,13 +12,12 @@
*/
import cpp
-import semmle.code.cpp.valuenumbering.HashCons
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
from FunctionCall fc, AssignExpr expr, ArrayExpr exprarr
where
- fc.getTarget().hasGlobalOrStdName("strlen") and
exprarr = expr.getLValue() and
expr.getRValue().getValue().toInt() = 0 and
exprarr.getArrayOffset() = fc and
- hashCons(fc.getArgument(0)) = hashCons(exprarr.getArrayBase())
-select expr, "use a different method to calculate the length."
+ globalValueNumber(fc.getArgument(0)) = globalValueNumber(exprarr.getArrayBase())
+select expr, "potential unsafe or redundant assignment."
From 636fe73f400782951db790ee8bd4f826f0686fd1 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Tue, 26 Jan 2021 23:52:18 +0300
Subject: [PATCH 032/429] Update
AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
---
.../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
index 77fc29719f6..3e1cfdd6396 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
@@ -14,7 +14,7 @@
import cpp
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
-from FunctionCall fc, AssignExpr expr, ArrayExpr exprarr
+from StrlenCall fc, AssignExpr expr, ArrayExpr exprarr
where
exprarr = expr.getLValue() and
expr.getRValue().getValue().toInt() = 0 and
From 9a85b761a17b56d1c4b5a02e5cb26998fac590ff Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 12:46:10 +0300
Subject: [PATCH 033/429] Update test.c
---
.../Security/CWE/CWE-788/semmle/tests/test.c | 36 +++++++++----------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
index 84487670bf7..ce07a2ddba2 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
@@ -10,30 +10,30 @@ void badFunc0(){
struct buffers buffAll;
struct buffers * buffAll1;
- buff1[strlen(buff1)]=0;
- buffAll.buff1[strlen(buffAll.buff1)]=0;
- buffAll.buff2[strlen(buffAll.buff2)]=0;
- buffAll1->buff1[strlen(buffAll1->buff1)]=0;
- buffAll1->buff2[strlen(buffAll1->buff2)]=0;
- globalBuff1.buff1[strlen(globalBuff1.buff1)]=0;
- globalBuff1.buff2[strlen(globalBuff1.buff2)]=0;
- globalBuff2->buff1[strlen(globalBuff2->buff1)]=0;
- globalBuff2->buff2[strlen(globalBuff2->buff2)]=0;
+ buff1[strlen(buff1)]=0; // BAD
+ buffAll.buff1[strlen(buffAll.buff1)]=0; // BAD
+ buffAll.buff2[strlen(buffAll.buff2)]=0; // BAD
+ buffAll1->buff1[strlen(buffAll1->buff1)]=0; // BAD
+ buffAll1->buff2[strlen(buffAll1->buff2)]=0; // BAD
+ globalBuff1.buff1[strlen(globalBuff1.buff1)]=0; // BAD
+ globalBuff1.buff2[strlen(globalBuff1.buff2)]=0; // BAD
+ globalBuff2->buff1[strlen(globalBuff2->buff1)]=0; // BAD
+ globalBuff2->buff2[strlen(globalBuff2->buff2)]=0; // BAD
}
void noBadFunc0(){
unsigned char buff1[12],buff1_c[12];
struct buffers buffAll,buffAll_c;
struct buffers * buffAll1,*buffAll1_c;
- buff1[strlen(buff1_c)]=0;
- buffAll.buff1[strlen(buffAll_c.buff1)]=0;
- buffAll.buff2[strlen(buffAll.buff1)]=0;
- buffAll1->buff1[strlen(buffAll1_c->buff1)]=0;
- buffAll1->buff2[strlen(buffAll1->buff1)]=0;
- globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0;
- globalBuff1.buff2[strlen(globalBuff1.buff1)]=0;
- globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0;
- globalBuff2->buff2[strlen(globalBuff2->buff1)]=0;
+ buff1[strlen(buff1_c)]=0; // GOOD
+ buffAll.buff1[strlen(buffAll_c.buff1)]=0; // GOOD
+ buffAll.buff2[strlen(buffAll.buff1)]=0; // GOOD
+ buffAll1->buff1[strlen(buffAll1_c->buff1)]=0; // GOOD
+ buffAll1->buff2[strlen(buffAll1->buff1)]=0; // GOOD
+ globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0; // GOOD
+ globalBuff1.buff2[strlen(globalBuff1.buff1)]=0; // GOOD
+ globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0; // GOOD
+ globalBuff2->buff2[strlen(globalBuff2->buff1)]=0; // GOOD
}
void goodFunc0(){
unsigned char buffer[12];
From 885d26805febf0b1fe7e253f6065f31f83291c03 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 12:47:51 +0300
Subject: [PATCH 034/429] Update
AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
---
...ocationAfterEndOfBufferUsingStrlen.expected | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
index 82d3e224993..c2f02dd203e 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
@@ -1,9 +1,9 @@
-| test.c:13:3:13:24 | ... = ... | use a different method to calculate the length. |
-| test.c:14:3:14:40 | ... = ... | use a different method to calculate the length. |
-| test.c:15:3:15:40 | ... = ... | use a different method to calculate the length. |
-| test.c:16:3:16:44 | ... = ... | use a different method to calculate the length. |
-| test.c:17:3:17:44 | ... = ... | use a different method to calculate the length. |
-| test.c:18:3:18:48 | ... = ... | use a different method to calculate the length. |
-| test.c:19:3:19:48 | ... = ... | use a different method to calculate the length. |
-| test.c:20:3:20:50 | ... = ... | use a different method to calculate the length. |
-| test.c:21:3:21:50 | ... = ... | use a different method to calculate the length. |
+| test.c:13:3:13:24 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:14:3:14:40 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:15:3:15:40 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:16:3:16:44 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:17:3:17:44 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:18:3:18:48 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:19:3:19:48 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:20:3:20:50 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:21:3:21:50 | ... = ... | potential unsafe or redundant assignment. |
From bdba7e14fe293225269ae2932a4cb539b453bedd Mon Sep 17 00:00:00 2001
From: intrigus
Date: Wed, 27 Jan 2021 11:54:40 +0100
Subject: [PATCH 035/429] Java: Switch to data flow
---
.../CWE/CWE-295/JxBrowserWithoutCertValidation.ql | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
index 6aee64ed010..71a050a20ce 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
@@ -9,7 +9,7 @@
import java
import semmle.code.java.security.Encryption
-import semmle.code.java.dataflow.TaintTracking
+import semmle.code.java.dataflow.DataFlow
/*
* This query is version specific to JxBrowser < 6.24. The version is indirectly detected.
@@ -57,8 +57,8 @@ private class JxBrowserSafeLoadHandler extends RefType {
}
}
-private class JxBrowserTaintTracking extends TaintTracking::Configuration {
- JxBrowserTaintTracking() { this = "JxBrowserTaintTracking" }
+private class JxBrowserFlowConfiguration extends DataFlow::Configuration {
+ JxBrowserFlowConfiguration() { this = "JxBrowserFlowConfiguration" }
override predicate isSource(DataFlow::Node src) {
exists(ClassInstanceExpr newJxBrowser | newJxBrowser.getConstructedType() instanceof JxBrowser |
@@ -74,7 +74,7 @@ private class JxBrowserTaintTracking extends TaintTracking::Configuration {
}
}
-from JxBrowserTaintTracking cfg, DataFlow::Node src
+from JxBrowserFlowConfiguration cfg, DataFlow::Node src
where
cfg.isSource(src) and
not cfg.hasFlow(src, _) and
From d3e6e594b2b4efc1d81616cc8bfbe2f3e6aff7d5 Mon Sep 17 00:00:00 2001
From: intrigus
Date: Wed, 27 Jan 2021 11:57:32 +0100
Subject: [PATCH 036/429] Java: Improve QLDoc
---
.../Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
index 71a050a20ce..4046f4e9606 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-295/JxBrowserWithoutCertValidation.ql
@@ -57,6 +57,10 @@ private class JxBrowserSafeLoadHandler extends RefType {
}
}
+/**
+ * Models flow from the source `new Browser()` to a sink `browser.setLoadHandler(loadHandler)` where `loadHandler`
+ * has been determined to be safe.
+ */
private class JxBrowserFlowConfiguration extends DataFlow::Configuration {
JxBrowserFlowConfiguration() { this = "JxBrowserFlowConfiguration" }
From 8737c1442b9f52d5f1c3a29e2762db0b850873eb Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 14:48:23 +0300
Subject: [PATCH 037/429] Update
WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
---
...ctingAndHandlingMemoryAllocationErrors.cpp | 55 +++++++++----------
1 file changed, 27 insertions(+), 28 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
index 6fc3bc2f893..0232fc131eb 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
@@ -1,32 +1,31 @@
-// BAD: no memory allocation errors are detected
-void f(const int *array, std::size_t size) noexcept {
- int *copy = new int[size];
- std::memcpy(copy, array, size * sizeof(*copy));
- // ...
- delete [] copy;
+// BAD: on memory allocation error, the program terminates.
+void badFunction(const int *source, std::size_t length) noexcept {
+ int * dest = new int[length];
+ std::memset(dest, 0, length);
+// ..
}
-// GOOD: memory allocation errors are detected
-void f(const int *array, std::size_t size) noexcept {
- int *copy;
+// GOOD: memory allocation error will be handled.
+void goodFunction(const int *source, std::size_t length) noexcept {
try {
- copy = new int[size];
- } catch(std::bad_alloc) {
- // Handle error
- return;
- }
- // At this point, copy has been initialized to allocated memory
- std::memcpy(copy, array, size * sizeof(*copy));
- // ...
- delete [] copy;
+ int * dest = new int[length];
+ } catch(std::bad_alloc)
+ std::memset(dest, 0, length);
+// ..
}
-// GOOD: memory allocation errors are detected
-void f(const int *array, std::size_t size) noexcept {
- int *copy = new (std::nothrow) int[size];
- if (!copy) {
- // Handle error
- return;
- }
- std::memcpy(copy, array, size * sizeof(*copy));
- // ...
- delete [] copy;
+// BAD: memory allocation error will not be handled.
+void badFunction(const int *source, std::size_t length) noexcept {
+ try {
+ int * dest = new (std::nothrow) int[length];
+ } catch(std::bad_alloc)
+ std::memset(dest, 0, length);
+// ..
+}
+// GOOD: memory allocation error will be handled.
+void goodFunction(const int *source, std::size_t length) noexcept {
+ int * dest = new (std::nothrow) int[length];
+ if (!dest) {
+ return;
+ }
+ std::memset(dest, 0, length);
+// ..
}
From bec00643968bad1ae39d53e0256111187569b907 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 14:54:47 +0300
Subject: [PATCH 038/429] Update test.cpp
---
.../CWE/CWE-570/semmle/tests/test.cpp | 26 +++++++++----------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
index 6f03f896024..e4aa8cf2976 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-570/semmle/tests/test.cpp
@@ -20,14 +20,14 @@ void* operator new[]( std::size_t count, const std::nothrow_t& tag );
void badNew_0_0()
{
while (true) {
- new int[100];
- if(!(new int[100]))
+ new int[100]; // BAD [NOT DETECTED]
+ if(!(new int[100])) // BAD [NOT DETECTED]
return;
}
}
void badNew_0_1()
{
- int * i = new int[100];
+ int * i = new int[100]; // BAD
if(i == 0)
return;
if(!i)
@@ -35,7 +35,7 @@ void badNew_0_1()
if(i == NULL)
return;
int * j;
- j = new int[100];
+ j = new int[100]; // BAD
if(j == 0)
return;
if(!j)
@@ -47,10 +47,10 @@ void badNew_1_0()
{
try {
while (true) {
- new(std::nothrow) int[100];
- int* p = new(std::nothrow) int[100];
+ new(std::nothrow) int[100]; // BAD
+ int* p = new(std::nothrow) int[100]; // BAD
int* p1;
- p1 = new(std::nothrow) int[100];
+ p1 = new(std::nothrow) int[100]; // BAD
}
} catch (const exception &){//const std::bad_alloc& e) {
// std::cout << e.what() << '\n';
@@ -59,8 +59,8 @@ void badNew_1_0()
void badNew_1_1()
{
while (true) {
- int* p = new(std::nothrow) int[100];
- new(std::nothrow) int[100];
+ int* p = new(std::nothrow) int[100]; // BAD [NOT DETECTED]
+ new(std::nothrow) int[100]; // BAD [NOT DETECTED]
}
}
@@ -68,7 +68,7 @@ void goodNew_0_0()
{
try {
while (true) {
- new int[100];
+ new int[100]; // GOOD
}
} catch (const exception &){//const std::bad_alloc& e) {
// std::cout << e.what() << '\n';
@@ -78,18 +78,18 @@ void goodNew_0_0()
void goodNew_1_0()
{
while (true) {
- int* p = new(std::nothrow) int[100];
+ int* p = new(std::nothrow) int[100]; // GOOD
if (p == nullptr) {
// std::cout << "Allocation returned nullptr\n";
break;
}
int* p1;
- p1 = new(std::nothrow) int[100];
+ p1 = new(std::nothrow) int[100]; // GOOD
if (p1 == nullptr) {
// std::cout << "Allocation returned nullptr\n";
break;
}
- if (new(std::nothrow) int[100] == nullptr) {
+ if (new(std::nothrow) int[100] == nullptr) { // GOOD
// std::cout << "Allocation returned nullptr\n";
break;
}
From 25de82c78cd5c19fd1957a8926ba7e395edf2815 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 15:05:01 +0300
Subject: [PATCH 039/429] Apply suggestions from code review
Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
---
.../WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
index 4b7941a5846..7be7fcd5b35 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.qhelp
@@ -3,18 +3,18 @@
"qhelp.dtd">
-
when using the new operator to allocate memory, you need to pay attention to the different way of detecting errors. so ::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error. the programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.
+
When using the new operator to allocate memory, you need to pay attention to the different ways of detecting errors. ::operator new(std::size_t) throws an exception on error, whereas ::operator new(std::size_t, const std::nothrow_t &) returns zero on error. The programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.
Loss of detection probably refers to use cases where memory allocation using your own solutions with strong nesting. It is also possible when using a buffer in the form of fields of different structures with the same names.
-
We recommend using the error detection method, depending on the selected memory allocation method..
+
Use the correct error detection method corresponding with the memory allocation.
-
The following file demonstrates various approaches to detecting memory allocation errors using the new operator.
+
The following example demonstrates various approaches to detecting memory allocation errors using the new operator.
When using the new operator to allocate memory, you need to pay attention to the different ways of detecting errors. ::operator new(std::size_t) throws an exception on error, whereas ::operator new(std::size_t, const std::nothrow_t &) returns zero on error. The programmer can get confused and check the error that occurs when allocating memory incorrectly. That can lead to an unhandled program termination or to a violation of the program logic.
-
Loss of detection probably refers to use cases where memory allocation using your own solutions with strong nesting. It is also possible when using a buffer in the form of fields of different structures with the same names.
-
From 16d058f49865acb62d36c1a9b1af0aa6810d9eac Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 15:06:57 +0300
Subject: [PATCH 041/429] Update
WrongInDetectingAndHandlingMemoryAllocationErrors.ql
---
.../CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql | 1 -
1 file changed, 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
index 383c8a1f128..c32c14c1dd0 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
@@ -2,7 +2,6 @@
* @name Сonfusion In Detecting And Handling Memory Allocation Errors
* @description --::operator new(std::size_t) throws an exception on error, and ::operator new(std::size_t, const std::nothrow_t &) returns zero on error.
* --the programmer can get confused when check the error that occurs when allocating memory incorrectly.
- * --Making a call of this type may result in a zero byte being written just outside the buffer.
* @kind problem
* @id cpp/detect-and-handle-memory-allocation-errors
* @problem.severity warning
From bdfdcbd6735a841c4329a26b15bc4130c10e8f67 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Wed, 27 Jan 2021 15:48:18 +0300
Subject: [PATCH 042/429] Update
WrongInDetectingAndHandlingMemoryAllocationErrors.ql
---
...rongInDetectingAndHandlingMemoryAllocationErrors.ql | 10 ++--------
1 file changed, 2 insertions(+), 8 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
index c32c14c1dd0..e165d61985b 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
@@ -44,14 +44,8 @@ class WrongCheckErrorOperatorNew extends FunctionCall {
* Holds if handler `try ... catch` exists.
*/
predicate isExistsTryCatchBlock() {
- exists(TryStmt tb, AssignExpr aex, Initializer it |
- tb.getAChild*() = exp
- or
- exp = it.getExpr() and
- tb.getAChild*().(DeclStmt).getADeclaration() = it.getDeclaration()
- or
- aex.getAChild*() = exp and
- tb.getAChild*().(AssignExpr) = aex
+ exists(TryStmt ts |
+ this.getEnclosingStmt() = ts.getStmt().getAChild*()
)
}
From 6a93099b645e32e7598999ddbcf0303f65e657fa Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Thu, 28 Jan 2021 03:02:53 +0000
Subject: [PATCH 043/429] Simplify the query and update qldoc
---
.../experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp | 2 +-
.../src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
index 9241b52cf19..ee4afabbed6 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
@@ -26,7 +26,7 @@
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 602dae21ad7..9563c755410 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -202,13 +202,11 @@ where
sink.getNode().asExpr() = va and
exists(BasicAuthFlowConfig bc, DataFlow::PathNode source2, DataFlow::PathNode sink2 |
bc.hasFlowPath(source2, sink2) and
- source2.getNode().asExpr().(CompileTimeConstantExpr).getStringValue() = "simple" and
sink2.getNode().asExpr() = va
) and
not exists(SSLFlowConfig sc, DataFlow::PathNode source3, DataFlow::PathNode sink3 |
sc.hasFlowPath(source3, sink3) and
- source3.getNode().asExpr().(CompileTimeConstantExpr).getStringValue() = "ssl" and
- sink3.getNode().asExpr() = va.getVariable().getAnAccess()
+ sink3.getNode().asExpr() = va
)
select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
"LDAP connection string"
From cfc950f8039ca780b87a6889b2913e6702961b4e Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Thu, 7 Jan 2021 21:18:51 +0000
Subject: [PATCH 044/429] Query for weak encryption: Insufficient key size
---
.../CWE/CWE-326/InsufficientKeySize.java | 37 +++++
.../CWE/CWE-326/InsufficientKeySize.qhelp | 33 ++++
.../CWE/CWE-326/InsufficientKeySize.ql | 155 ++++++++++++++++++
.../semmle/code/java/security/Encryption.qll | 12 ++
.../CWE-326/InsufficientKeySize.expected | 4 +
.../security/CWE-326/InsufficientKeySize.java | 41 +++++
.../CWE-326/InsufficientKeySize.qlref | 1 +
7 files changed, 283 insertions(+)
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
create mode 100644 java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
create mode 100644 java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
new file mode 100644
index 00000000000..6ccf025c244
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
@@ -0,0 +1,37 @@
+public class InsufficientKeySize {
+ public void CryptoMethod() {
+ KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
+ // BAD: Key size is less than 128
+ keyGen1.init(64);
+
+ KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
+ // GOOD: Key size is no less than 128
+ keyGen2.init(128);
+
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
+ // BAD: Key size is less than 2048
+ keyPairGen1.initialize(1024);
+
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
+ // GOOD: Key size is no less than 2048
+ keyPairGen2.initialize(2048);
+
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
+ // BAD: Key size is less than 2048
+ keyPairGen3.initialize(1024);
+
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
+ // GOOD: Key size is no less than 2048
+ keyPairGen4.initialize(2048);
+
+ KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 224
+ ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
+ keyPairGen5.initialize(ecSpec1);
+
+ KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
+ // GOOD: Key size is no less than 224
+ ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
+ keyPairGen6.initialize(ecSpec2);
+ }
+}
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
new file mode 100644
index 00000000000..8de21f1c90a
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
@@ -0,0 +1,33 @@
+
+
+
+
This rule finds uses of encryption algorithms with too small a key size. Encryption algorithms
+are vulnerable to brute force attack when too small a key size is used.
+
+
+
+
The key should be at least 2048-bit long when using RSA and DSA encryption, 224-bit long when using EC encryption, and 128-bit long when using
+symmetric encryption.
+
+
+
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
new file mode 100644
index 00000000000..882d997f1b8
--- /dev/null
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -0,0 +1,155 @@
+/**
+ * @name Weak encryption: Insufficient key size
+ * @description Finds uses of encryption algorithms with too small a key size
+ * @kind problem
+ * @id java/insufficient-key-size
+ * @tags security
+ * external/cwe/cwe-326
+ */
+
+import java
+import semmle.code.java.security.Encryption
+import semmle.code.java.dataflow.TaintTracking
+
+/** The Java class `javax.crypto.KeyGenerator`. */
+class KeyGenerator extends RefType {
+ KeyGenerator() { this.hasQualifiedName("javax.crypto", "KeyGenerator") }
+}
+
+/** The Java class `javax.crypto.KeyGenerator`. */
+class KeyPairGenerator extends RefType {
+ KeyPairGenerator() { this.hasQualifiedName("java.security", "KeyPairGenerator") }
+}
+
+/** The Java class `java.security.spec.ECGenParameterSpec`. */
+class ECGenParameterSpec extends RefType {
+ ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
+}
+
+/** The `init` method declared in `javax.crypto.KeyGenerator`. */
+class KeyGeneratorInitMethod extends Method {
+ KeyGeneratorInitMethod() {
+ getDeclaringType() instanceof KeyGenerator and
+ hasName("init")
+ }
+}
+
+/** The `initialize` method declared in `java.security.KeyPairGenerator`. */
+class KeyPairGeneratorInitMethod extends Method {
+ KeyPairGeneratorInitMethod() {
+ getDeclaringType() instanceof KeyPairGenerator and
+ hasName("initialize")
+ }
+}
+
+/** Taint configuration tracking flow from a key generator to a `init` method call. */
+class CryptoKeyGeneratorConfiguration extends TaintTracking::Configuration {
+ CryptoKeyGeneratorConfiguration() { this = "CryptoKeyGeneratorConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(JavaxCryptoKeyGenerator jcg | jcg = source.asExpr())
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/** Taint configuration tracking flow from a keypair generator to a `initialize` method call. */
+class KeyPairGeneratorConfiguration extends TaintTracking::Configuration {
+ KeyPairGeneratorConfiguration() { this = "KeyPairGeneratorConfiguration" }
+
+ override predicate isSource(DataFlow::Node source) {
+ exists(JavaSecurityKeyPairGenerator jkg | jkg = source.asExpr())
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(MethodAccess ma |
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ sink.asExpr() = ma.getQualifier()
+ )
+ }
+}
+
+/** Holds if an AES `KeyGenerator` is initialized with an insufficient key size. */
+predicate incorrectUseOfAES(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyGeneratorInitMethod and
+ exists(
+ JavaxCryptoKeyGenerator jcg, CryptoKeyGeneratorConfiguration cc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jcg.getAlgoSpec().(StringLiteral).getValue() = "AES" and
+ source.getNode().asExpr() = jcg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ cc.hasFlowPath(source, dest)
+ ) and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 128 and
+ msg = "Key size should be at least 128 bits for AES encryption."
+}
+
+/** Holds if a DSA `KeyPairGenerator` is initialized with an insufficient key size. */
+predicate incorrectUseOfDSA(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorConfiguration kc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue() = "DSA" and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest)
+ ) and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ msg = "Key size should be at least 2048 bits for DSA encryption."
+}
+
+/** Holds if a RSA `KeyPairGenerator` is initialized with an insufficient key size. */
+predicate incorrectUseOfRSA(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorConfiguration kc, DataFlow::PathNode source,
+ DataFlow::PathNode dest
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue() = "RSA" and
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest)
+ ) and
+ ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
+ msg = "Key size should be at least 2048 bits for RSA encryption."
+}
+
+/** Holds if an EC `KeyPairGenerator` is initialized with an insufficient key size. */
+predicate incorrectUseOfEC(MethodAccess ma, string msg) {
+ ma.getMethod() instanceof KeyPairGeneratorInitMethod and
+ exists(
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorConfiguration kc, DataFlow::PathNode source,
+ DataFlow::PathNode dest, ClassInstanceExpr cie
+ |
+ jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and //ECC variants such as ECDH and ECDSA
+ source.getNode().asExpr() = jpg and
+ dest.getNode().asExpr() = ma.getQualifier() and
+ kc.hasFlowPath(source, dest) and
+ exists(VariableAssign va |
+ ma.getArgument(0).(VarAccess).getVariable() = va.getDestVar() and
+ va.getSource() = cie and
+ cie.getArgument(0)
+ .(StringLiteral)
+ .getRepresentedString()
+ .regexpCapture(".*[a-zA-Z]+([0-9]+)[a-zA-Z]+.*", 1)
+ .toInt() < 224
+ )
+ ) and
+ msg = "Key size should be at least 224 bits for EC encryption."
+}
+
+from Expr e, string msg
+where
+ incorrectUseOfAES(e, msg) or
+ incorrectUseOfDSA(e, msg) or
+ incorrectUseOfRSA(e, msg) or
+ incorrectUseOfEC(e, msg)
+select e, msg
diff --git a/java/ql/src/semmle/code/java/security/Encryption.qll b/java/ql/src/semmle/code/java/security/Encryption.qll
index d48a5c6c715..b133fd3c523 100644
--- a/java/ql/src/semmle/code/java/security/Encryption.qll
+++ b/java/ql/src/semmle/code/java/security/Encryption.qll
@@ -304,3 +304,15 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
+
+/** Method call to the Java class `java.security.KeyPairGenerator`. */
+class JavaSecurityKeyPairGenerator extends JavaxCryptoAlgoSpec {
+ JavaSecurityKeyPairGenerator() {
+ exists(Method m | m.getAReference() = this |
+ m.getDeclaringType().getQualifiedName() = "java.security.KeyPairGenerator" and
+ m.getName() = "getInstance"
+ )
+ }
+
+ override Expr getAlgoSpec() { result = this.(MethodAccess).getArgument(0) }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
new file mode 100644
index 00000000000..b47469aed5d
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
@@ -0,0 +1,4 @@
+| InsufficientKeySize.java:9:9:9:24 | init(...) | Key size should be at least 128 bits for AES encryption. |
+| InsufficientKeySize.java:17:9:17:36 | initialize(...) | Key size should be at least 2048 bits for RSA encryption. |
+| InsufficientKeySize.java:25:9:25:36 | initialize(...) | Key size should be at least 2048 bits for DSA encryption. |
+| InsufficientKeySize.java:34:9:34:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
new file mode 100644
index 00000000000..13f74b6fac3
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
@@ -0,0 +1,41 @@
+import java.security.KeyPairGenerator;
+import java.security.spec.ECGenParameterSpec;
+import javax.crypto.KeyGenerator;
+
+public class InsufficientKeySize {
+ public void CryptoMethod() {
+ KeyGenerator keyGen1 = KeyGenerator.getInstance("AES");
+ // BAD: Key size is less than 128
+ keyGen1.init(64);
+
+ KeyGenerator keyGen2 = KeyGenerator.getInstance("AES");
+ // GOOD: Key size is no less than 128
+ keyGen2.init(128);
+
+ KeyPairGenerator keyPairGen1 = KeyPairGenerator.getInstance("RSA");
+ // BAD: Key size is less than 2048
+ keyPairGen1.initialize(1024);
+
+ KeyPairGenerator keyPairGen2 = KeyPairGenerator.getInstance("RSA");
+ // GOOD: Key size is no less than 2048
+ keyPairGen2.initialize(2048);
+
+ KeyPairGenerator keyPairGen3 = KeyPairGenerator.getInstance("DSA");
+ // BAD: Key size is less than 2048
+ keyPairGen3.initialize(1024);
+
+ KeyPairGenerator keyPairGen4 = KeyPairGenerator.getInstance("DSA");
+ // GOOD: Key size is no less than 2048
+ keyPairGen4.initialize(2048);
+
+ KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 224
+ ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
+ keyPairGen5.initialize(ecSpec1);
+
+ KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
+ // GOOD: Key size is no less than 224
+ ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
+ keyPairGen6.initialize(ecSpec2);
+ }
+}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref
new file mode 100644
index 00000000000..2b35cd6921e
--- /dev/null
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
\ No newline at end of file
From cbaee937d0ab8962278be494e36eaad5ab24693f Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 8 Jan 2021 21:56:34 +0000
Subject: [PATCH 045/429] Optimize the query
---
.../CWE/CWE-326/InsufficientKeySize.qhelp | 6 +-
.../CWE/CWE-326/InsufficientKeySize.ql | 91 ++++++++-----------
.../semmle/code/java/security/Encryption.qll | 14 ++-
.../CWE-326/InsufficientKeySize.expected | 4 +
.../security/CWE-326/InsufficientKeySize.java | 21 ++++-
5 files changed, 77 insertions(+), 59 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
index 8de21f1c90a..10ec6adc9df 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
@@ -6,7 +6,7 @@ are vulnerable to brute force attack when too small a key size is used.
-
The key should be at least 2048-bit long when using RSA and DSA encryption, 224-bit long when using EC encryption, and 128-bit long when using
+
The key should be at least 2048 bits long when using RSA and DSA encryption, 224 bits long when using EC encryption, and 128 bits long when using
symmetric encryption.
\ No newline at end of file
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
index 882d997f1b8..df265f267f7 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -11,16 +11,6 @@ import java
import semmle.code.java.security.Encryption
import semmle.code.java.dataflow.TaintTracking
-/** The Java class `javax.crypto.KeyGenerator`. */
-class KeyGenerator extends RefType {
- KeyGenerator() { this.hasQualifiedName("javax.crypto", "KeyGenerator") }
-}
-
-/** The Java class `javax.crypto.KeyGenerator`. */
-class KeyPairGenerator extends RefType {
- KeyPairGenerator() { this.hasQualifiedName("java.security", "KeyPairGenerator") }
-}
-
/** The Java class `java.security.spec.ECGenParameterSpec`. */
class ECGenParameterSpec extends RefType {
ECGenParameterSpec() { this.hasQualifiedName("java.security.spec", "ECGenParameterSpec") }
@@ -42,9 +32,19 @@ class KeyPairGeneratorInitMethod extends Method {
}
}
+/** Returns the key size in the EC algorithm string */
+bindingset[algorithm]
+int getECKeySize(string algorithm) {
+ algorithm.matches("sec%") and // specification such as "secp256r1"
+ result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
+ result = algorithm.regexpCapture("X9.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+}
+
/** Taint configuration tracking flow from a key generator to a `init` method call. */
-class CryptoKeyGeneratorConfiguration extends TaintTracking::Configuration {
- CryptoKeyGeneratorConfiguration() { this = "CryptoKeyGeneratorConfiguration" }
+class KeyGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyGeneratorInitConfiguration() { this = "KeyGeneratorInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
exists(JavaxCryptoKeyGenerator jcg | jcg = source.asExpr())
@@ -59,8 +59,8 @@ class CryptoKeyGeneratorConfiguration extends TaintTracking::Configuration {
}
/** Taint configuration tracking flow from a keypair generator to a `initialize` method call. */
-class KeyPairGeneratorConfiguration extends TaintTracking::Configuration {
- KeyPairGeneratorConfiguration() { this = "KeyPairGeneratorConfiguration" }
+class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
+ KeyPairGeneratorInitConfiguration() { this = "KeyPairGeneratorInitConfiguration" }
override predicate isSource(DataFlow::Node source) {
exists(JavaSecurityKeyPairGenerator jkg | jkg = source.asExpr())
@@ -75,10 +75,10 @@ class KeyPairGeneratorConfiguration extends TaintTracking::Configuration {
}
/** Holds if an AES `KeyGenerator` is initialized with an insufficient key size. */
-predicate incorrectUseOfAES(MethodAccess ma, string msg) {
+predicate hasShortAESKey(MethodAccess ma, string msg) {
ma.getMethod() instanceof KeyGeneratorInitMethod and
exists(
- JavaxCryptoKeyGenerator jcg, CryptoKeyGeneratorConfiguration cc, DataFlow::PathNode source,
+ JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
DataFlow::PathNode dest
|
jcg.getAlgoSpec().(StringLiteral).getValue() = "AES" and
@@ -90,66 +90,55 @@ predicate incorrectUseOfAES(MethodAccess ma, string msg) {
msg = "Key size should be at least 128 bits for AES encryption."
}
-/** Holds if a DSA `KeyPairGenerator` is initialized with an insufficient key size. */
-predicate incorrectUseOfDSA(MethodAccess ma, string msg) {
+/** Holds if an asymmetric `KeyPairGenerator` is initialized with an insufficient key size. */
+bindingset[type]
+predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorConfiguration kc, DataFlow::PathNode source,
- DataFlow::PathNode dest
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest
|
- jpg.getAlgoSpec().(StringLiteral).getValue() = "DSA" and
+ jpg.getAlgoSpec().(StringLiteral).getValue() = type and
source.getNode().asExpr() = jpg and
dest.getNode().asExpr() = ma.getQualifier() and
kc.hasFlowPath(source, dest)
) and
ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
- msg = "Key size should be at least 2048 bits for DSA encryption."
+ msg = "Key size should be at least 2048 bits for " + type + " encryption."
+}
+
+/** Holds if a DSA `KeyPairGenerator` is initialized with an insufficient key size. */
+predicate hasShortDSAKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "DSA")
}
/** Holds if a RSA `KeyPairGenerator` is initialized with an insufficient key size. */
-predicate incorrectUseOfRSA(MethodAccess ma, string msg) {
- ma.getMethod() instanceof KeyPairGeneratorInitMethod and
- exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorConfiguration kc, DataFlow::PathNode source,
- DataFlow::PathNode dest
- |
- jpg.getAlgoSpec().(StringLiteral).getValue() = "RSA" and
- source.getNode().asExpr() = jpg and
- dest.getNode().asExpr() = ma.getQualifier() and
- kc.hasFlowPath(source, dest)
- ) and
- ma.getArgument(0).(IntegerLiteral).getIntValue() < 2048 and
- msg = "Key size should be at least 2048 bits for RSA encryption."
+predicate hasShortRSAKeyPair(MethodAccess ma, string msg) {
+ hasShortAsymmetricKeyPair(ma, msg, "RSA")
}
/** Holds if an EC `KeyPairGenerator` is initialized with an insufficient key size. */
-predicate incorrectUseOfEC(MethodAccess ma, string msg) {
+predicate hasShortECKeyPair(MethodAccess ma, string msg) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
exists(
- JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorConfiguration kc, DataFlow::PathNode source,
- DataFlow::PathNode dest, ClassInstanceExpr cie
+ JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
+ DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
|
jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and //ECC variants such as ECDH and ECDSA
source.getNode().asExpr() = jpg and
dest.getNode().asExpr() = ma.getQualifier() and
kc.hasFlowPath(source, dest) and
- exists(VariableAssign va |
- ma.getArgument(0).(VarAccess).getVariable() = va.getDestVar() and
- va.getSource() = cie and
- cie.getArgument(0)
- .(StringLiteral)
- .getRepresentedString()
- .regexpCapture(".*[a-zA-Z]+([0-9]+)[a-zA-Z]+.*", 1)
- .toInt() < 224
- )
+ DataFlow::localExprFlow(cie, ma.getArgument(0)) and
+ ma.getArgument(0).getType() instanceof ECGenParameterSpec and
+ getECKeySize(cie.getArgument(0).(StringLiteral).getRepresentedString()) < 224
) and
msg = "Key size should be at least 224 bits for EC encryption."
}
from Expr e, string msg
where
- incorrectUseOfAES(e, msg) or
- incorrectUseOfDSA(e, msg) or
- incorrectUseOfRSA(e, msg) or
- incorrectUseOfEC(e, msg)
+ hasShortAESKey(e, msg) or
+ hasShortDSAKeyPair(e, msg) or
+ hasShortRSAKeyPair(e, msg) or
+ hasShortECKeyPair(e, msg)
select e, msg
diff --git a/java/ql/src/semmle/code/java/security/Encryption.qll b/java/ql/src/semmle/code/java/security/Encryption.qll
index b133fd3c523..9c10569d8c1 100644
--- a/java/ql/src/semmle/code/java/security/Encryption.qll
+++ b/java/ql/src/semmle/code/java/security/Encryption.qll
@@ -38,6 +38,16 @@ class HostnameVerifier extends RefType {
HostnameVerifier() { hasQualifiedName("javax.net.ssl", "HostnameVerifier") }
}
+/** The Java class `javax.crypto.KeyGenerator`. */
+class KeyGenerator extends RefType {
+ KeyGenerator() { this.hasQualifiedName("javax.crypto", "KeyGenerator") }
+}
+
+/** The Java class `java.security.KeyPairGenerator`. */
+class KeyPairGenerator extends RefType {
+ KeyPairGenerator() { this.hasQualifiedName("java.security", "KeyPairGenerator") }
+}
+
/** The `verify` method of the class `javax.net.ssl.HostnameVerifier`. */
class HostnameVerifierVerify extends Method {
HostnameVerifierVerify() {
@@ -248,7 +258,7 @@ class JavaxCryptoSecretKey extends JavaxCryptoAlgoSpec {
class JavaxCryptoKeyGenerator extends JavaxCryptoAlgoSpec {
JavaxCryptoKeyGenerator() {
exists(Method m | m.getAReference() = this |
- m.getDeclaringType().getQualifiedName() = "javax.crypto.KeyGenerator" and
+ m.getDeclaringType() instanceof KeyGenerator and
m.getName() = "getInstance"
)
}
@@ -309,7 +319,7 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
class JavaSecurityKeyPairGenerator extends JavaxCryptoAlgoSpec {
JavaSecurityKeyPairGenerator() {
exists(Method m | m.getAReference() = this |
- m.getDeclaringType().getQualifiedName() = "java.security.KeyPairGenerator" and
+ m.getDeclaringType() instanceof KeyPairGenerator and
m.getName() = "getInstance"
)
}
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
index b47469aed5d..f350495d28f 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
@@ -2,3 +2,7 @@
| InsufficientKeySize.java:17:9:17:36 | initialize(...) | Key size should be at least 2048 bits for RSA encryption. |
| InsufficientKeySize.java:25:9:25:36 | initialize(...) | Key size should be at least 2048 bits for DSA encryption. |
| InsufficientKeySize.java:34:9:34:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
+| InsufficientKeySize.java:38:9:38:67 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
+| InsufficientKeySize.java:48:9:48:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
+| InsufficientKeySize.java:53:9:53:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
+| InsufficientKeySize.java:58:9:58:40 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
index 13f74b6fac3..80d49cdf5f1 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
@@ -34,8 +34,27 @@ public class InsufficientKeySize {
keyPairGen5.initialize(ecSpec1);
KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 224
+ keyPairGen6.initialize(new ECGenParameterSpec("secp112r1"));
+
+ KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
// GOOD: Key size is no less than 224
ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
- keyPairGen6.initialize(ecSpec2);
+ keyPairGen7.initialize(ecSpec2);
+
+ KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 224
+ ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2");
+ keyPairGen8.initialize(ecSpec3);
+
+ KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 224
+ ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3");
+ keyPairGen9.initialize(ecSpec4);
+
+ KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 224
+ ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
+ keyPairGen10.initialize(ecSpec5);
}
}
From 058f3af4b21bfe3dec4bdcbb84f3bd26c5830dca Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Sat, 9 Jan 2021 13:48:29 +0000
Subject: [PATCH 046/429] Refactor the hasShortSymmetricKey method
---
.../Security/CWE/CWE-326/InsufficientKeySize.ql | 14 +++++++++-----
.../security/CWE-326/InsufficientKeySize.java | 5 +++++
2 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
index df265f267f7..169e1e58a69 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -39,7 +39,7 @@ int getECKeySize(string algorithm) {
result = algorithm.regexpCapture("sec[p|t](\\d+)[a-zA-Z].*", 1).toInt()
or
algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
- result = algorithm.regexpCapture("X9.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
/** Taint configuration tracking flow from a key generator to a `init` method call. */
@@ -74,22 +74,26 @@ class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
}
}
-/** Holds if an AES `KeyGenerator` is initialized with an insufficient key size. */
-predicate hasShortAESKey(MethodAccess ma, string msg) {
+/** Holds if a symmetric `KeyGenerator` is initialized with an insufficient key size. */
+bindingset[type]
+predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyGeneratorInitMethod and
exists(
JavaxCryptoKeyGenerator jcg, KeyGeneratorInitConfiguration cc, DataFlow::PathNode source,
DataFlow::PathNode dest
|
- jcg.getAlgoSpec().(StringLiteral).getValue() = "AES" and
+ jcg.getAlgoSpec().(StringLiteral).getValue() = type and
source.getNode().asExpr() = jcg and
dest.getNode().asExpr() = ma.getQualifier() and
cc.hasFlowPath(source, dest)
) and
ma.getArgument(0).(IntegerLiteral).getIntValue() < 128 and
- msg = "Key size should be at least 128 bits for AES encryption."
+ msg = "Key size should be at least 128 bits for " + type + " encryption."
}
+/** Holds if an AES `KeyGenerator` is initialized with an insufficient key size. */
+predicate hasShortAESKey(MethodAccess ma, string msg) { hasShortSymmetricKey(ma, msg, "AES") }
+
/** Holds if an asymmetric `KeyPairGenerator` is initialized with an insufficient key size. */
bindingset[type]
predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
index 80d49cdf5f1..45b7c610df1 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
@@ -56,5 +56,10 @@ public class InsufficientKeySize {
// BAD: Key size is less than 224
ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
keyPairGen10.initialize(ecSpec5);
+
+ KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
+ // GOOD: Key size is no less than 224
+ ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1");
+ keyPairGen11.initialize(ecSpec6);
}
}
From 2ac7b4bab42c4146fb9f629291d049efa43d52e8 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Mon, 11 Jan 2021 12:59:00 +0000
Subject: [PATCH 047/429] Update qldoc
---
.../Security/CWE/CWE-326/InsufficientKeySize.ql | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
index 169e1e58a69..c925ef0a966 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -74,7 +74,7 @@ class KeyPairGeneratorInitConfiguration extends TaintTracking::Configuration {
}
}
-/** Holds if a symmetric `KeyGenerator` is initialized with an insufficient key size. */
+/** Holds if a symmetric `KeyGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
bindingset[type]
predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyGeneratorInitMethod and
@@ -91,10 +91,10 @@ predicate hasShortSymmetricKey(MethodAccess ma, string msg, string type) {
msg = "Key size should be at least 128 bits for " + type + " encryption."
}
-/** Holds if an AES `KeyGenerator` is initialized with an insufficient key size. */
+/** Holds if an AES `KeyGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
predicate hasShortAESKey(MethodAccess ma, string msg) { hasShortSymmetricKey(ma, msg, "AES") }
-/** Holds if an asymmetric `KeyPairGenerator` is initialized with an insufficient key size. */
+/** Holds if an asymmetric `KeyPairGenerator` implementing encryption algorithm `type` and initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
bindingset[type]
predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
@@ -111,24 +111,24 @@ predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
msg = "Key size should be at least 2048 bits for " + type + " encryption."
}
-/** Holds if a DSA `KeyPairGenerator` is initialized with an insufficient key size. */
+/** Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
predicate hasShortDSAKeyPair(MethodAccess ma, string msg) {
hasShortAsymmetricKeyPair(ma, msg, "DSA")
}
-/** Holds if a RSA `KeyPairGenerator` is initialized with an insufficient key size. */
+/** Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
predicate hasShortRSAKeyPair(MethodAccess ma, string msg) {
hasShortAsymmetricKeyPair(ma, msg, "RSA")
}
-/** Holds if an EC `KeyPairGenerator` is initialized with an insufficient key size. */
+/** Holds if an EC `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
predicate hasShortECKeyPair(MethodAccess ma, string msg) {
ma.getMethod() instanceof KeyPairGeneratorInitMethod and
exists(
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
DataFlow::PathNode source, DataFlow::PathNode dest, ClassInstanceExpr cie
|
- jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and //ECC variants such as ECDH and ECDSA
+ jpg.getAlgoSpec().(StringLiteral).getValue().matches("EC%") and // ECC variants such as ECDH and ECDSA
source.getNode().asExpr() = jpg and
dest.getNode().asExpr() = ma.getQualifier() and
kc.hasFlowPath(source, dest) and
From ab7d257569907903cf6731fa10f64c348ad25b46 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Fri, 15 Jan 2021 13:11:32 +0000
Subject: [PATCH 048/429] Add more cases and change EC to 256 bits
---
.../CWE/CWE-326/InsufficientKeySize.ql | 11 +++--
.../CWE-326/InsufficientKeySize.expected | 13 +++---
.../security/CWE-326/InsufficientKeySize.java | 42 +++++++++++++++----
3 files changed, 50 insertions(+), 16 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
index c925ef0a966..41242a44805 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.ql
@@ -40,6 +40,9 @@ int getECKeySize(string algorithm) {
or
algorithm.matches("X9.62%") and //specification such as "X9.62 prime192v2"
result = algorithm.regexpCapture("X9\\.62 .*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
+ or
+ (algorithm.matches("prime%") or algorithm.matches("c2tnb%")) and //specification such as "prime192v2"
+ result = algorithm.regexpCapture(".*[a-zA-Z](\\d+)[a-zA-Z].*", 1).toInt()
}
/** Taint configuration tracking flow from a key generator to a `init` method call. */
@@ -102,7 +105,7 @@ predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
JavaSecurityKeyPairGenerator jpg, KeyPairGeneratorInitConfiguration kc,
DataFlow::PathNode source, DataFlow::PathNode dest
|
- jpg.getAlgoSpec().(StringLiteral).getValue() = type and
+ jpg.getAlgoSpec().(StringLiteral).getValue().toUpperCase() = type and
source.getNode().asExpr() = jpg and
dest.getNode().asExpr() = ma.getQualifier() and
kc.hasFlowPath(source, dest)
@@ -113,7 +116,7 @@ predicate hasShortAsymmetricKeyPair(MethodAccess ma, string msg, string type) {
/** Holds if a DSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
predicate hasShortDSAKeyPair(MethodAccess ma, string msg) {
- hasShortAsymmetricKeyPair(ma, msg, "DSA")
+ hasShortAsymmetricKeyPair(ma, msg, "DSA") or hasShortAsymmetricKeyPair(ma, msg, "DH")
}
/** Holds if a RSA `KeyPairGenerator` initialized by `ma` uses an insufficient key size. `msg` provides a human-readable description of the problem. */
@@ -134,9 +137,9 @@ predicate hasShortECKeyPair(MethodAccess ma, string msg) {
kc.hasFlowPath(source, dest) and
DataFlow::localExprFlow(cie, ma.getArgument(0)) and
ma.getArgument(0).getType() instanceof ECGenParameterSpec and
- getECKeySize(cie.getArgument(0).(StringLiteral).getRepresentedString()) < 224
+ getECKeySize(cie.getArgument(0).(StringLiteral).getRepresentedString()) < 256
) and
- msg = "Key size should be at least 224 bits for EC encryption."
+ msg = "Key size should be at least 256 bits for EC encryption."
}
from Expr e, string msg
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
index f350495d28f..421335b84ff 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.expected
@@ -1,8 +1,11 @@
| InsufficientKeySize.java:9:9:9:24 | init(...) | Key size should be at least 128 bits for AES encryption. |
| InsufficientKeySize.java:17:9:17:36 | initialize(...) | Key size should be at least 2048 bits for RSA encryption. |
| InsufficientKeySize.java:25:9:25:36 | initialize(...) | Key size should be at least 2048 bits for DSA encryption. |
-| InsufficientKeySize.java:34:9:34:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
-| InsufficientKeySize.java:38:9:38:67 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
-| InsufficientKeySize.java:48:9:48:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
-| InsufficientKeySize.java:53:9:53:39 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
-| InsufficientKeySize.java:58:9:58:40 | initialize(...) | Key size should be at least 224 bits for EC encryption. |
+| InsufficientKeySize.java:34:9:34:39 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:38:9:38:67 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:48:9:48:39 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:53:9:53:39 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:58:9:58:40 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:68:9:68:40 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:78:9:78:40 | initialize(...) | Key size should be at least 256 bits for EC encryption. |
+| InsufficientKeySize.java:87:9:87:37 | initialize(...) | Key size should be at least 2048 bits for DH encryption. |
diff --git a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
index 45b7c610df1..df46d6e69a9 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
+++ b/java/ql/test/experimental/query-tests/security/CWE-326/InsufficientKeySize.java
@@ -29,37 +29,65 @@ public class InsufficientKeySize {
keyPairGen4.initialize(2048);
KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 224
+ // BAD: Key size is less than 256
ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
keyPairGen5.initialize(ecSpec1);
KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 224
+ // BAD: Key size is less than 256
keyPairGen6.initialize(new ECGenParameterSpec("secp112r1"));
KeyPairGenerator keyPairGen7 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 224
+ // GOOD: Key size is no less than 256
ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
keyPairGen7.initialize(ecSpec2);
KeyPairGenerator keyPairGen8 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 224
+ // BAD: Key size is less than 256
ECGenParameterSpec ecSpec3 = new ECGenParameterSpec("X9.62 prime192v2");
keyPairGen8.initialize(ecSpec3);
KeyPairGenerator keyPairGen9 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 224
+ // BAD: Key size is less than 256
ECGenParameterSpec ecSpec4 = new ECGenParameterSpec("X9.62 c2tnb191v3");
keyPairGen9.initialize(ecSpec4);
KeyPairGenerator keyPairGen10 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 224
+ // BAD: Key size is less than 256
ECGenParameterSpec ecSpec5 = new ECGenParameterSpec("sect163k1");
keyPairGen10.initialize(ecSpec5);
KeyPairGenerator keyPairGen11 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 224
+ // GOOD: Key size is no less than 256
ECGenParameterSpec ecSpec6 = new ECGenParameterSpec("X9.62 c2tnb359v1");
keyPairGen11.initialize(ecSpec6);
+
+ KeyPairGenerator keyPairGen12 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 256
+ ECGenParameterSpec ecSpec7 = new ECGenParameterSpec("prime192v2");
+ keyPairGen12.initialize(ecSpec7);
+
+ KeyPairGenerator keyPairGen13 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is no less than 256
+ ECGenParameterSpec ecSpec8 = new ECGenParameterSpec("prime256v1");
+ keyPairGen13.initialize(ecSpec8);
+
+ KeyPairGenerator keyPairGen14 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is less than 256
+ ECGenParameterSpec ecSpec9 = new ECGenParameterSpec("c2tnb191v1");
+ keyPairGen14.initialize(ecSpec9);
+
+ KeyPairGenerator keyPairGen15 = KeyPairGenerator.getInstance("EC");
+ // BAD: Key size is no less than 256
+ ECGenParameterSpec ecSpec10 = new ECGenParameterSpec("c2tnb431r1");
+ keyPairGen15.initialize(ecSpec10);
+
+ KeyPairGenerator keyPairGen16 = KeyPairGenerator.getInstance("dh");
+ // BAD: Key size is less than 2048
+ keyPairGen16.initialize(1024);
+
+ KeyPairGenerator keyPairGen17 = KeyPairGenerator.getInstance("DH");
+ // GOOD: Key size is no less than 2048
+ keyPairGen17.initialize(2048);
}
}
From 8880b38b1f65f601a9aadc8002dc9731bfe97763 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Thu, 28 Jan 2021 15:28:15 +0300
Subject: [PATCH 049/429] Rename
cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
to
cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
---
.../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/{ => AccessOfMemoryLocationAfterEndOfBufferUsingStrlen}/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref (100%)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
similarity index 100%
rename from cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
rename to cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
From f65ec97ac210db005941e0292adee295296da837 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Thu, 28 Jan 2021 15:28:34 +0300
Subject: [PATCH 050/429] Rename
cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
to
cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c
---
.../test.c | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/{ => AccessOfMemoryLocationAfterEndOfBufferUsingStrlen}/test.c (100%)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c
similarity index 100%
rename from cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
rename to cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c
From 8ed28157e118a44c29c2aade759f2a18a6e09727 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Thu, 28 Jan 2021 15:28:52 +0300
Subject: [PATCH 051/429] Rename
cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
to
cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
---
.../AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/{ => AccessOfMemoryLocationAfterEndOfBufferUsingStrlen}/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected (100%)
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
similarity index 100%
rename from cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
rename to cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
From c8eeb5f73e70c567dc062c7a3b143faa8063ec1a Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Fri, 29 Jan 2021 11:51:15 +0300
Subject: [PATCH 052/429] Update
WrongInDetectingAndHandlingMemoryAllocationErrors.ql
---
.../WrongInDetectingAndHandlingMemoryAllocationErrors.ql | 3 +++
1 file changed, 3 insertions(+)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
index e165d61985b..04a284d7846 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
@@ -22,6 +22,9 @@ class IfCompareWithZero extends IfStmt {
or
this.getCondition().(NEExpr).getAChild().getValue() = "0" and
this.hasElse()
+ or
+ this.getCondition().(NEExpr).getAChild().getValue() = "0" and
+ this.getThen().getAChild*() instanceof ReturnStmt
}
}
From bdbf5a4fae29511be7d64039847a1a70a7e61545 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Fri, 29 Jan 2021 13:41:45 +0300
Subject: [PATCH 053/429] Apply suggestions from code review
Co-authored-by: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
---
.../WrongInDetectingAndHandlingMemoryAllocationErrors.cpp | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
index 0232fc131eb..df69886e97b 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.cpp
@@ -8,7 +8,9 @@ void badFunction(const int *source, std::size_t length) noexcept {
void goodFunction(const int *source, std::size_t length) noexcept {
try {
int * dest = new int[length];
- } catch(std::bad_alloc)
+ } catch(std::bad_alloc) {
+ // ...
+ }
std::memset(dest, 0, length);
// ..
}
@@ -16,7 +18,9 @@ void goodFunction(const int *source, std::size_t length) noexcept {
void badFunction(const int *source, std::size_t length) noexcept {
try {
int * dest = new (std::nothrow) int[length];
- } catch(std::bad_alloc)
+ } catch(std::bad_alloc) {
+ // ...
+ }
std::memset(dest, 0, length);
// ..
}
From 191962f64c6ac49134b388a755d5237cfc1f305e Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Fri, 29 Jan 2021 12:01:38 +0100
Subject: [PATCH 054/429] C#: Add data flow 'getARuntimeTarget' predicate to
'FunctionPointerCall'
---
.../internal/FunctionPointerDataFlow.qll | 205 ++++++++++++++++++
.../ql/src/semmle/code/csharp/exprs/Call.qll | 17 ++
.../functionpointers/FunctionPointerFlow.cs | 83 +++++++
.../FunctionPointerFlow.expected | 9 +
.../functionpointers/FunctionPointerFlow.ql | 5 +
5 files changed, 319 insertions(+)
create mode 100755 csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll
create mode 100644 csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.cs
create mode 100644 csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.expected
create mode 100644 csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.ql
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll
new file mode 100755
index 00000000000..495beb93a22
--- /dev/null
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll
@@ -0,0 +1,205 @@
+/**
+ * INTERNAL: Do not use.
+ *
+ * Provides classes for resolving function pointer calls.
+ */
+
+import csharp
+private import dotnet
+private import semmle.code.csharp.dataflow.CallContext
+private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
+private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
+private import semmle.code.csharp.dataflow.internal.DataFlowPublic
+private import semmle.code.csharp.dataflow.FlowSummary
+private import semmle.code.csharp.dispatch.Dispatch
+private import semmle.code.csharp.frameworks.system.linq.Expressions
+
+/** A source of flow for a function pointer expression. */
+private class FunctionPointerFlowSource extends DataFlow::ExprNode {
+ Callable c;
+
+ FunctionPointerFlowSource() {
+ this.getExpr() =
+ any(Expr e |
+ c = e.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
+ )
+ }
+
+ /** Gets the callable that is referenced in this function pointer flow source. */
+ Callable getCallable() { result = c }
+}
+
+/** A sink of flow for a function pointer expression. */
+abstract private class FunctionPointerFlowSink extends DataFlow::Node {
+ /**
+ * Gets an actual run-time target of this function pointer call in the given call
+ * context, if any. The call context records the *last* call required to
+ * resolve the target, if any.
+ *
+ * See examples in `DelegateFlowSink`.
+ */
+ cached
+ Callable getARuntimeTarget(CallContext context) {
+ exists(FunctionPointerFlowSource fptrfs |
+ flowsFrom(this, fptrfs, _, context) and
+ result = fptrfs.getCallable()
+ )
+ }
+}
+
+/** A function pointer call expression. */
+class FunctionPointerCallExpr extends FunctionPointerFlowSink, DataFlow::ExprNode {
+ FunctionPointerCall fptrc;
+
+ FunctionPointerCallExpr() { this.getExpr() = fptrc.getFunctionPointerExpr() }
+
+ /** Gets the function pointer call that this expression belongs to. */
+ FunctionPointerCall getFunctionPointerCall() { result = fptrc }
+}
+
+/** A non-function pointer call. */
+private class NonFunctionPointerCall extends Expr {
+ private DispatchCall dc;
+
+ NonFunctionPointerCall() { this = dc.getCall() }
+
+ /**
+ * Gets a run-time target of this call. A target is always a source
+ * declaration, and if the callable has both CIL and source code, only
+ * the source code version is returned.
+ */
+ Callable getARuntimeTarget() { result = getCallableForDataFlow(dc.getADynamicTarget()) }
+
+ /** Gets the `i`th argument of this call. */
+ Expr getArgument(int i) { result = dc.getArgument(i) }
+}
+
+private class NormalReturnNode extends Node {
+ NormalReturnNode() { this.(ReturnNode).getKind() instanceof NormalReturnKind }
+}
+
+/**
+ * Holds if data can flow (inter-procedurally) to function pointer `sink` from
+ * `node`. This predicate searches backwards from `sink` to `node`.
+ *
+ * The parameter `isReturned` indicates whether the path from `sink` to
+ * `node` goes through a returned expression. The call context `lastCall`
+ * records the last call on the path from `node` to `sink`, if any.
+ */
+private predicate flowsFrom(
+ FunctionPointerFlowSink sink, DataFlow::Node node, boolean isReturned, CallContext lastCall
+) {
+ // Base case
+ sink = node and
+ isReturned = false and
+ lastCall instanceof EmptyCallContext
+ or
+ // Local flow
+ exists(DataFlow::Node mid | flowsFrom(sink, mid, isReturned, lastCall) |
+ LocalFlow::localFlowStepCommon(node, mid)
+ or
+ exists(Ssa::Definition def |
+ LocalFlow::localSsaFlowStep(def, node, mid) and
+ LocalFlow::usesInstanceField(def)
+ )
+ )
+ or
+ // Flow through static field or property
+ exists(DataFlow::Node mid |
+ flowsFrom(sink, mid, _, _) and
+ jumpStep(node, mid) and
+ isReturned = false and
+ lastCall instanceof EmptyCallContext
+ )
+ or
+ // Flow into a callable (non-function pointer call)
+ exists(ParameterNode mid, CallContext prevLastCall, NonFunctionPointerCall call, Parameter p |
+ flowsFrom(sink, mid, isReturned, prevLastCall) and
+ isReturned = false and
+ p = mid.getParameter() and
+ flowIntoNonFunctionPointerCall(call, node.asExpr(), p) and
+ lastCall = getLastCall(prevLastCall, call, p.getPosition())
+ )
+ or
+ // Flow into a callable (function pointer call)
+ exists(
+ ParameterNode mid, CallContext prevLastCall, FunctionPointerCall call, Callable c, Parameter p,
+ int i
+ |
+ flowsFrom(sink, mid, isReturned, prevLastCall) and
+ isReturned = false and
+ flowIntoFunctionPointerCall(call, c, node.asExpr(), i) and
+ c.getParameter(i) = p and
+ p = mid.getParameter() and
+ lastCall = getLastCall(prevLastCall, call, i)
+ )
+ or
+ // Flow out of a callable (non-function pointer call).
+ exists(DataFlow::ExprNode mid |
+ flowsFrom(sink, mid, _, lastCall) and
+ isReturned = true and
+ flowOutOfNonFunctionPointerCall(mid.getExpr(), node)
+ )
+ or
+ // Flow out of a callable (function pointer call).
+ exists(DataFlow::ExprNode mid |
+ flowsFrom(sink, mid, _, _) and
+ isReturned = true and
+ flowOutOfFunctionPointerCall(mid.getExpr(), node, lastCall)
+ )
+}
+
+/**
+ * Gets the last call when tracking flow into `call`. The context
+ * `prevLastCall` is the previous last call, so the result is the
+ * previous call if it exists, otherwise `call` is the last call.
+ */
+bindingset[call, i]
+private CallContext getLastCall(CallContext prevLastCall, Expr call, int i) {
+ prevLastCall instanceof EmptyCallContext and
+ result.(ArgumentCallContext).isArgument(call, i)
+ or
+ prevLastCall instanceof ArgumentCallContext and
+ result = prevLastCall
+}
+
+pragma[noinline]
+private predicate flowIntoNonFunctionPointerCall(
+ NonFunctionPointerCall call, Expr arg, DotNet::Parameter p
+) {
+ exists(DotNet::Callable callable, int i |
+ callable = call.getARuntimeTarget() and
+ p = callable.getAParameter() and
+ arg = call.getArgument(i) and
+ i = p.getPosition()
+ )
+}
+
+pragma[noinline]
+private predicate flowIntoFunctionPointerCall(FunctionPointerCall call, Callable c, Expr arg, int i) {
+ exists(FunctionPointerFlowSource fptrfs, FunctionPointerCallExpr fptrce |
+ // the call context is irrelevant because the function pointer call
+ // itself will be the context
+ flowsFrom(fptrce, fptrfs, _, _) and
+ arg = call.getArgument(i) and
+ c = fptrfs.getCallable() and
+ call = fptrce.getFunctionPointerCall()
+ )
+}
+
+pragma[noinline]
+private predicate flowOutOfNonFunctionPointerCall(NonFunctionPointerCall call, NormalReturnNode ret) {
+ call.getARuntimeTarget() = ret.getEnclosingCallable()
+}
+
+pragma[noinline]
+private predicate flowOutOfFunctionPointerCall(
+ FunctionPointerCall call, NormalReturnNode ret, CallContext lastCall
+) {
+ exists(FunctionPointerFlowSource fptrfs, FunctionPointerCallExpr fptrce, Callable c |
+ flowsFrom(fptrce, fptrfs, _, lastCall) and
+ ret.getEnclosingCallable() = c and
+ c = fptrfs.getCallable() and
+ call = fptrce.getFunctionPointerCall()
+ )
+}
diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
index a5348040d54..eb04d14d015 100644
--- a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
+++ b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
@@ -8,6 +8,7 @@ import Expr
import semmle.code.csharp.Callable
import semmle.code.csharp.dataflow.CallContext as CallContext
private import semmle.code.csharp.dataflow.internal.DelegateDataFlow
+private import semmle.code.csharp.dataflow.internal.FunctionPointerDataFlow
private import semmle.code.csharp.dispatch.Dispatch
private import dotnet
@@ -618,8 +619,24 @@ class DelegateCall extends Call, @delegate_invocation_expr {
class FunctionPointerCall extends Call, @function_pointer_invocation_expr {
override Callable getTarget() { none() }
+ /**
+ * Gets a potential run-time target of this function pointer call in the given
+ * call context `cc`.
+ */
+ Callable getARuntimeTarget(CallContext::CallContext cc) {
+ exists(FunctionPointerCallExpr call |
+ this = call.getFunctionPointerCall() and
+ result = call.getARuntimeTarget(cc)
+ )
+ }
+
+ override Callable getARuntimeTarget() { result = getARuntimeTarget(_) }
+
override Expr getRuntimeArgument(int i) { result = getArgument(i) }
+ /** Gets the function pointer expression of this call. */
+ Expr getFunctionPointerExpr() { result = this.getChild(-1) }
+
override string toString() { result = "function pointer call" }
override string getAPrimaryQlClass() { result = "FunctionPointerCall" }
diff --git a/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.cs b/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.cs
new file mode 100644
index 00000000000..0ebbef4942d
--- /dev/null
+++ b/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.cs
@@ -0,0 +1,83 @@
+using System;
+
+unsafe class FunctionPointerFlow
+{
+ public static void Log1(int i) { }
+ public static void Log2(int i) { }
+ public static void Log3(int i) { }
+ public static void Log4(int i) { }
+ public static void Log5(int i) { }
+ public static void Log6(int i) { }
+
+ public static void M2(delegate* a)
+ {
+ a(1);
+ a = &Log3;
+ a(2);
+ }
+
+ public void M3()
+ {
+ M2(&Log1);
+ }
+
+ public static void M4(delegate* a)
+ {
+ M2(a);
+ }
+
+ public void M5()
+ {
+ M4(&Log2);
+ }
+
+ public delegate* M6()
+ {
+ return &Log4;
+ }
+
+ public void M7()
+ {
+ M6()(0);
+ }
+
+ public void M8()
+ {
+ static void LocalFunction(int i) { };
+ M2(&LocalFunction);
+ }
+
+ private static delegate* field = &Log5;
+
+ public void M9()
+ {
+ field(1);
+ }
+
+ public void M10(delegate*, void> aa, delegate* a)
+ {
+ aa(a);
+ }
+
+ public void M11()
+ {
+ M10(&M4, &Log6);
+ }
+
+ public void M16(delegate*, void> aa, Action a)
+ {
+ aa(a);
+ }
+
+ public static void M17(Action a)
+ {
+ a(0);
+ a = _ => { };
+ a(0);
+ }
+
+ public void M18()
+ {
+ M16(&M17, (i) => { });
+ }
+}
diff --git a/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.expected b/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.expected
new file mode 100644
index 00000000000..ad2bfb887c0
--- /dev/null
+++ b/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.expected
@@ -0,0 +1,9 @@
+| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:5:24:5:27 | Log1 | FunctionPointerFlow.cs:21:12:21:16 | &... |
+| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:6:24:6:27 | Log2 | FunctionPointerFlow.cs:26:12:26:12 | access to parameter a |
+| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:10:24:10:27 | Log6 | FunctionPointerFlow.cs:26:12:26:12 | access to parameter a |
+| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:46:9:46:44 | LocalFunction | FunctionPointerFlow.cs:47:12:47:25 | &... |
+| FunctionPointerFlow.cs:16:9:16:12 | function pointer call | FunctionPointerFlow.cs:7:24:7:27 | Log3 | file://:0:0:0:0 | |
+| FunctionPointerFlow.cs:41:9:41:15 | function pointer call | FunctionPointerFlow.cs:8:24:8:27 | Log4 | file://:0:0:0:0 | |
+| FunctionPointerFlow.cs:54:9:54:16 | function pointer call | FunctionPointerFlow.cs:9:24:9:27 | Log5 | file://:0:0:0:0 | |
+| FunctionPointerFlow.cs:59:9:59:13 | function pointer call | FunctionPointerFlow.cs:24:24:24:25 | M4 | FunctionPointerFlow.cs:64:13:64:15 | &... |
+| FunctionPointerFlow.cs:69:9:69:13 | function pointer call | FunctionPointerFlow.cs:72:24:72:26 | M17 | FunctionPointerFlow.cs:81:13:81:16 | &... |
diff --git a/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.ql b/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.ql
new file mode 100644
index 00000000000..3434d068329
--- /dev/null
+++ b/csharp/ql/test/library-tests/dataflow/functionpointers/FunctionPointerFlow.ql
@@ -0,0 +1,5 @@
+import csharp
+
+query predicate fptrCall(FunctionPointerCall fptrc, Callable c, CallContext::CallContext cc) {
+ c = fptrc.getARuntimeTarget(cc)
+}
From 91152d3a65dcdbc989602c57379bbb5462a95303 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Fri, 29 Jan 2021 12:02:11 +0100
Subject: [PATCH 055/429] Add additional tests to delegate call data flow
---
.../dataflow/delegates/DelegateFlow.cs | 20 +++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
index 9479cca940d..68a91d02698 100644
--- a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
+++ b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
@@ -4,11 +4,11 @@ class DelegateFlow
{
void M1(int i) { }
- void M2(Action a)
+ static void M2(Action a)
{
a(0);
a = _ => { };
- a(0);
+ a(1);
}
void M3()
@@ -108,4 +108,20 @@ class DelegateFlow
}
public delegate void MyDelegate();
+
+ public unsafe void M16(delegate*, void> fnptr, Action a)
+ {
+ fnptr(a);
+ }
+
+ public unsafe void M17()
+ {
+ M16(&M2, (i) => {}); // MISSING: a(0) in M2 is calling this lambda
+ }
+
+ public unsafe void M18()
+ {
+ delegate*, void> fnptr = &M2;
+ fnptr((i) => {}); // MISSING: a(0) in M2 is calling this lambda
+ }
}
From 76c9b6466e78481c7e6f0cc75ef1224b9a407c06 Mon Sep 17 00:00:00 2001
From: Luke Cartey <5377966+lcartey@users.noreply.github.com>
Date: Fri, 29 Jan 2021 11:27:30 +0000
Subject: [PATCH 056/429] Reformat TaintTrackingUtil.qll with more recent
CodeQL CLI
---
.../semmle/code/java/dataflow/internal/TaintTrackingUtil.qll | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
index 35f820f9521..69189d949f1 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll
@@ -686,8 +686,7 @@ private class FormatterCallable extends TaintPreservingCallable {
(
this.hasName(["format", "out", "toString"])
or
- this
- .(Constructor)
+ this.(Constructor)
.getParameterType(0)
.(RefType)
.getASourceSupertype*()
From 92a5a2a06a7d3e7df4662182d5f23277830476a4 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Fri, 29 Jan 2021 13:34:19 +0100
Subject: [PATCH 057/429] C++: Solve merge conflicts by merging the two test.c
test files.
---
...cationAfterEndOfBufferUsingStrlen.expected | 9 ++++
...yLocationAfterEndOfBufferUsingStrlen.qlref | 0
...cationAfterEndOfBufferUsingStrlen.expected | 9 ----
.../test.c | 44 ------------------
.../Security/CWE/CWE-788/semmle/tests/test.c | 45 +++++++++++++++++++
5 files changed, 54 insertions(+), 53 deletions(-)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
rename cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/{AccessOfMemoryLocationAfterEndOfBufferUsingStrlen => }/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref (100%)
delete mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
delete mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
new file mode 100644
index 00000000000..103afd8ffd9
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
@@ -0,0 +1,9 @@
+| test.c:42:3:42:24 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:43:3:43:40 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:44:3:44:40 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:45:3:45:44 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:46:3:46:44 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:47:3:47:48 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:48:3:48:48 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:49:3:49:50 | ... = ... | potential unsafe or redundant assignment. |
+| test.c:50:3:50:50 | ... = ... | potential unsafe or redundant assignment. |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
similarity index 100%
rename from cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
rename to cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.qlref
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
deleted file mode 100644
index c2f02dd203e..00000000000
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.expected
+++ /dev/null
@@ -1,9 +0,0 @@
-| test.c:13:3:13:24 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:14:3:14:40 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:15:3:15:40 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:16:3:16:44 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:17:3:17:44 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:18:3:18:48 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:19:3:19:48 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:20:3:20:50 | ... = ... | potential unsafe or redundant assignment. |
-| test.c:21:3:21:50 | ... = ... | potential unsafe or redundant assignment. |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c
deleted file mode 100644
index ce07a2ddba2..00000000000
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen/test.c
+++ /dev/null
@@ -1,44 +0,0 @@
-struct buffers
-{
- unsigned char buff1[50];
- unsigned char *buff2;
-} globalBuff1,*globalBuff2,globalBuff1_c,*globalBuff2_c;
-
-
-void badFunc0(){
- unsigned char buff1[12];
- struct buffers buffAll;
- struct buffers * buffAll1;
-
- buff1[strlen(buff1)]=0; // BAD
- buffAll.buff1[strlen(buffAll.buff1)]=0; // BAD
- buffAll.buff2[strlen(buffAll.buff2)]=0; // BAD
- buffAll1->buff1[strlen(buffAll1->buff1)]=0; // BAD
- buffAll1->buff2[strlen(buffAll1->buff2)]=0; // BAD
- globalBuff1.buff1[strlen(globalBuff1.buff1)]=0; // BAD
- globalBuff1.buff2[strlen(globalBuff1.buff2)]=0; // BAD
- globalBuff2->buff1[strlen(globalBuff2->buff1)]=0; // BAD
- globalBuff2->buff2[strlen(globalBuff2->buff2)]=0; // BAD
-}
-void noBadFunc0(){
- unsigned char buff1[12],buff1_c[12];
- struct buffers buffAll,buffAll_c;
- struct buffers * buffAll1,*buffAll1_c;
-
- buff1[strlen(buff1_c)]=0; // GOOD
- buffAll.buff1[strlen(buffAll_c.buff1)]=0; // GOOD
- buffAll.buff2[strlen(buffAll.buff1)]=0; // GOOD
- buffAll1->buff1[strlen(buffAll1_c->buff1)]=0; // GOOD
- buffAll1->buff2[strlen(buffAll1->buff1)]=0; // GOOD
- globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0; // GOOD
- globalBuff1.buff2[strlen(globalBuff1.buff1)]=0; // GOOD
- globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0; // GOOD
- globalBuff2->buff2[strlen(globalBuff2->buff1)]=0; // GOOD
-}
-void goodFunc0(){
- unsigned char buffer[12];
- int i;
- for(i = 0; i < 6; i++)
- buffer[i] = 'A';
- buffer[i]=0;
-}
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
index c3347e1fd65..d986bb3b13c 100644
--- a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-788/semmle/tests/test.c
@@ -26,3 +26,48 @@ void workFunction_2_1(char *s) {
strncat(buf, s, len-strlen(buf)-1); // GOOD
strncat(buf, s, len-strlen(buf)); // GOOD
}
+
+struct buffers
+{
+ unsigned char buff1[50];
+ unsigned char *buff2;
+} globalBuff1,*globalBuff2,globalBuff1_c,*globalBuff2_c;
+
+
+void badFunc0(){
+ unsigned char buff1[12];
+ struct buffers buffAll;
+ struct buffers * buffAll1;
+
+ buff1[strlen(buff1)]=0; // BAD
+ buffAll.buff1[strlen(buffAll.buff1)]=0; // BAD
+ buffAll.buff2[strlen(buffAll.buff2)]=0; // BAD
+ buffAll1->buff1[strlen(buffAll1->buff1)]=0; // BAD
+ buffAll1->buff2[strlen(buffAll1->buff2)]=0; // BAD
+ globalBuff1.buff1[strlen(globalBuff1.buff1)]=0; // BAD
+ globalBuff1.buff2[strlen(globalBuff1.buff2)]=0; // BAD
+ globalBuff2->buff1[strlen(globalBuff2->buff1)]=0; // BAD
+ globalBuff2->buff2[strlen(globalBuff2->buff2)]=0; // BAD
+}
+void noBadFunc0(){
+ unsigned char buff1[12],buff1_c[12];
+ struct buffers buffAll,buffAll_c;
+ struct buffers * buffAll1,*buffAll1_c;
+
+ buff1[strlen(buff1_c)]=0; // GOOD
+ buffAll.buff1[strlen(buffAll_c.buff1)]=0; // GOOD
+ buffAll.buff2[strlen(buffAll.buff1)]=0; // GOOD
+ buffAll1->buff1[strlen(buffAll1_c->buff1)]=0; // GOOD
+ buffAll1->buff2[strlen(buffAll1->buff1)]=0; // GOOD
+ globalBuff1.buff1[strlen(globalBuff1_c.buff1)]=0; // GOOD
+ globalBuff1.buff2[strlen(globalBuff1.buff1)]=0; // GOOD
+ globalBuff2->buff1[strlen(globalBuff2_c->buff1)]=0; // GOOD
+ globalBuff2->buff2[strlen(globalBuff2->buff1)]=0; // GOOD
+}
+void goodFunc0(){
+ unsigned char buffer[12];
+ int i;
+ for(i = 0; i < 6; i++)
+ buffer[i] = 'A';
+ buffer[i]=0;
+}
From ff2f2b57925b0ce89efecfac6747cfee09898a1a Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Fri, 29 Jan 2021 15:36:11 +0100
Subject: [PATCH 058/429] Python: Add django.shortcuts.redirect test
---
.../library-tests/frameworks/django-v1/response_test.py | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
index f214cfb63d3..4ce50f98f81 100644
--- a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
@@ -1,4 +1,5 @@
from django.http.response import HttpResponse, HttpResponseRedirect, HttpResponsePermanentRedirect, JsonResponse, HttpResponseNotFound
+import django.shortcuts
# Not an XSS sink, since the Content-Type is not "text/html"
# FP reported in https://github.com/github/codeql-python-team/issues/38
@@ -48,6 +49,11 @@ def redirect_through_normal_response(request):
return resp
+def redirect_shortcut(request):
+ next = request.GET.get("next")
+ return django.shortcuts.redirect(next) # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next
+
+
# Ensure that simple subclasses are still vuln to XSS
def xss__not_found(request):
return HttpResponseNotFound(request.GET.get("name")) # $HttpResponse mimetype=text/html responseBody=Attribute()
From 9c01aa23045f869da6a1cf049e8fa1768a961fba Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Fri, 29 Jan 2021 15:41:00 +0100
Subject: [PATCH 059/429] Python: Add modeling for django.shortcuts.redirect
---
.../src/semmle/python/frameworks/Django.qll | 86 ++++++++++++++++++-
.../frameworks/django-v1/response_test.py | 2 +-
2 files changed, 86 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Django.qll b/python/ql/src/semmle/python/frameworks/Django.qll
index 4251b515322..375debe0a17 100644
--- a/python/ql/src/semmle/python/frameworks/Django.qll
+++ b/python/ql/src/semmle/python/frameworks/Django.qll
@@ -35,7 +35,7 @@ private module Django {
* WARNING: Only holds for a few predefined attributes.
*/
private DataFlow::Node django_attr(DataFlow::TypeTracker t, string attr_name) {
- attr_name in ["db", "urls", "http", "conf"] and
+ attr_name in ["db", "urls", "http", "conf", "shortcuts"] and
(
t.start() and
result = DataFlow::importNode("django" + "." + attr_name)
@@ -1649,6 +1649,62 @@ private module Django {
}
}
}
+
+ // -------------------------------------------------------------------------
+ // django.shortcuts
+ // -------------------------------------------------------------------------
+ /** Gets a reference to the `django.shortcuts` module. */
+ DataFlow::Node shortcuts() { result = django_attr("shortcuts") }
+
+ /** Provides models for the `django.shortcuts` module */
+ module shortcuts {
+ /**
+ * Gets a reference to the attribute `attr_name` of the `django.shortcuts` module.
+ * WARNING: Only holds for a few predefined attributes.
+ */
+ private DataFlow::Node shortcuts_attr(DataFlow::TypeTracker t, string attr_name) {
+ attr_name in ["redirect"] and
+ (
+ t.start() and
+ result = DataFlow::importNode("django.shortcuts" + "." + attr_name)
+ or
+ t.startInAttr(attr_name) and
+ result = shortcuts()
+ )
+ or
+ // Due to bad performance when using normal setup with `shortcuts_attr(t2, attr_name).track(t2, t)`
+ // we have inlined that code and forced a join
+ exists(DataFlow::TypeTracker t2 |
+ exists(DataFlow::StepSummary summary |
+ shortcuts_attr_first_join(t2, attr_name, result, summary) and
+ t = t2.append(summary)
+ )
+ )
+ }
+
+ pragma[nomagic]
+ private predicate shortcuts_attr_first_join(
+ DataFlow::TypeTracker t2, string attr_name, DataFlow::Node res,
+ DataFlow::StepSummary summary
+ ) {
+ DataFlow::StepSummary::step(shortcuts_attr(t2, attr_name), res, summary)
+ }
+
+ /**
+ * Gets a reference to the attribute `attr_name` of the `django.shortcuts` module.
+ * WARNING: Only holds for a few predefined attributes.
+ */
+ private DataFlow::Node shortcuts_attr(string attr_name) {
+ result = shortcuts_attr(DataFlow::TypeTracker::end(), attr_name)
+ }
+
+ /**
+ * Gets a reference to the `django.shortcuts.redirect` function
+ *
+ * See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
+ */
+ DataFlow::Node redirect() { result = shortcuts_attr("redirect") }
+ }
}
// ---------------------------------------------------------------------------
@@ -1956,4 +2012,32 @@ private module Django {
)
}
}
+
+ // ---------------------------------------------------------------------------
+ // django.shortcuts.redirect
+ // ---------------------------------------------------------------------------
+ /**
+ * A call to `django.shortcuts.redirect`.
+ *
+ * Note: This works differently depending on what argument is used.
+ * _One_ option is to redirect to a full URL.
+ *
+ * See https://docs.djangoproject.com/en/3.1/topics/http/shortcuts/#redirect
+ */
+ private class DjangoShortcutsRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
+ DataFlow::CfgNode {
+ override CallNode node;
+
+ DjangoShortcutsRedirectCall() { node.getFunction() = django::shortcuts::redirect().asCfgNode() }
+
+ override DataFlow::Node getRedirectLocation() {
+ result.asCfgNode() in [node.getArg(0), node.getArgByName("to")]
+ }
+
+ override DataFlow::Node getBody() { none() }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
+
+ override string getMimetypeDefault() { none() }
+ }
}
diff --git a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
index 4ce50f98f81..99dc97624aa 100644
--- a/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/django-v1/response_test.py
@@ -51,7 +51,7 @@ def redirect_through_normal_response(request):
def redirect_shortcut(request):
next = request.GET.get("next")
- return django.shortcuts.redirect(next) # $ MISSING: HttpResponse HttpRedirectResponse redirectLocation=next
+ return django.shortcuts.redirect(next) # $ HttpResponse HttpRedirectResponse redirectLocation=next
# Ensure that simple subclasses are still vuln to XSS
From ef831bb16ff77e3a8a1b40f3792135f08f973d05 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Fri, 29 Jan 2021 16:21:39 +0100
Subject: [PATCH 060/429] Python: Fix tornado redirect QLdoc
---
python/ql/src/semmle/python/frameworks/Tornado.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll
index 29c91654b4a..f9a537861a0 100644
--- a/python/ql/src/semmle/python/frameworks/Tornado.qll
+++ b/python/ql/src/semmle/python/frameworks/Tornado.qll
@@ -556,9 +556,9 @@ private module Tornado {
// Response modeling
// ---------------------------------------------------------------------------
/**
- * A call to `tornado.web.RequestHandler.write` method.
+ * A call to `tornado.web.RequestHandler.redirect` method.
*
- * See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write
+ * See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.redirect
*/
private class TornadoRequestHandlerRedirectCall extends HTTP::Server::HttpRedirectResponse::Range,
DataFlow::CfgNode {
From 182d435dc600afa85ac95a5ca16bb7fb26c4bba7 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 18 Jan 2021 14:58:21 +0100
Subject: [PATCH 061/429] Python: Replace comprehension read-step by for
read-step. Add a version targetting sequence nodes.
---
.../dataflow/new/internal/DataFlowPrivate.qll | 126 +++++++++++++-----
.../dataflow/coverage/dataflow.expected | 81 ++++++++++-
.../experimental/dataflow/coverage/test.py | 20 ++-
.../dataflow/regression/dataflow.expected | 2 +
.../experimental/dataflow/regression/test.py | 4 +-
5 files changed, 188 insertions(+), 45 deletions(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index c4f236b0996..38478c6bc47 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -161,15 +161,6 @@ module EssaFlow {
nodeFrom.(CfgNode).getNode() =
nodeTo.(EssaNode).getVar().getDefinition().(AssignmentDefinition).getValue()
or
- // Definition
- // `[a, b] = iterable`
- // nodeFrom = `iterable`, cfg node
- // nodeTo = `TIterableSequence([a, b])`
- exists(UnpackingAssignmentDirectTarget target |
- nodeFrom.asExpr() = target.getValue() and
- nodeTo = TIterableSequenceNode(target)
- )
- or
// With definition
// `with f(42) as x:`
// nodeFrom is `f(42)`, cfg node
@@ -1045,7 +1036,7 @@ predicate readStep(Node nodeFrom, Content c, Node nodeTo) {
or
popReadStep(nodeFrom, c, nodeTo)
or
- comprehensionReadStep(nodeFrom, c, nodeTo)
+ forReadStep(nodeFrom, c, nodeTo)
or
attributeReadStep(nodeFrom, c, nodeTo)
or
@@ -1142,9 +1133,15 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
* also transfer other content, but only tuple content is further read from `sequence` into its elements.
*
* The strategy is then via several read-, store-, and flow steps:
- * 1. [Flow] Content is transferred from `iterable` to `TIterableSequence(sequence)` via a
+ * 1. a) [Flow] Content is transferred from `iterable` to `TIterableSequence(sequence)` via a
* flow step. From here, everything happens on the LHS.
*
+ * b) [Read] If the unpacking happens inside a for as in
+ * ```python
+ * for sequence in iterable
+ * ```
+ * then content is read from `iterable` to `TIterableSequence(sequence)`.
+ *
* 2. [Flow] Content is transferred from `TIterableSequence(sequence)` to `sequence` via a
* flow step. (Here only tuple content is relevant.)
*
@@ -1179,7 +1176,7 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
* Looking at the content propagation to `a`:
* `["a", SOURCE]`: [ListElementContent]
*
- * --Step 1-->
+ * --Step 1a-->
*
* `TIterableSequence((a, b))`: [ListElementContent]
*
@@ -1205,7 +1202,7 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
*
* `["a", [SOURCE]]`: [ListElementContent; ListElementContent]
*
- * --Step 1-->
+ * --Step 1a-->
*
* `TIterableSequence((a, [b, *c]))`: [ListElementContent; ListElementContent]
*
@@ -1238,13 +1235,53 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
* `c`: [ListElementContent]
*/
module UnpackingAssignment {
+ /**
+ * The target of a `for`, e.g. `x` in `for x in list` or in `[42 for x in list]`.
+ * This class also records the source, which in both above cases is `list`.
+ * This class abstracts away the differing representations of comprehensions and
+ * for statements.
+ */
+ class ForTarget extends ControlFlowNode {
+ Expr source;
+
+ ForTarget() {
+ exists(For for |
+ source = for.getIter() and
+ this.getNode() = for.getTarget() and
+ not for = any(Comp comp).getNthInnerLoop(0)
+ )
+ or
+ exists(Comp comp |
+ source = comp.getIterable() and
+ this.getNode() = comp.getIterationVariable(0).getAStore()
+ )
+ }
+
+ Expr getSource() { result = source }
+ }
+
+ /** The LHS of an assignemnt, it also records the assigned value. */
+ class AssignmentTarget extends ControlFlowNode {
+ Expr value;
+
+ AssignmentTarget() {
+ exists(Assign assign | this.getNode() = assign.getATarget() | value = assign.getValue())
+ }
+
+ Expr getValue() { result = value }
+ }
+
/** A direct (or top-level) target of an unpacking assignment. */
class UnpackingAssignmentDirectTarget extends ControlFlowNode {
Expr value;
UnpackingAssignmentDirectTarget() {
this instanceof SequenceNode and
- exists(Assign assign | this.getNode() = assign.getATarget() | value = assign.getValue())
+ (
+ value = this.(AssignmentTarget).getValue()
+ or
+ value = this.(ForTarget).getSource()
+ )
}
Expr getValue() { result = value }
@@ -1268,11 +1305,38 @@ module UnpackingAssignment {
ControlFlowNode getAnElement() { result = this.getElement(_) }
}
+ /**
+ * Step 1a
+ * Data flows from `iterable` to `TIterableSequence(sequence)`
+ */
+ predicate unpackingAssignmentAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
+ exists(AssignmentTarget target |
+ nodeFrom.asExpr() = target.getValue() and
+ nodeTo = TIterableSequenceNode(target)
+ )
+ }
+
+ /**
+ * Step 1b
+ * Data is read from `iterable` to `TIterableSequence(sequence)`
+ */
+ predicate unpackingAssignmentForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
+ exists(ForTarget target |
+ nodeFrom.asExpr() = target.getSource() and
+ nodeTo = TIterableSequenceNode(target.(SequenceNode))
+ ) and
+ (
+ c instanceof ListElementContent
+ or
+ c instanceof SetElementContent
+ )
+ }
+
/**
* Step 2
* Data flows from `TIterableSequence(sequence)` to `sequence`
*/
- predicate unpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
+ predicate unpackingAssignmentTupleFlowStep(Node nodeFrom, Node nodeTo) {
exists(UnpackingAssignmentSequenceTarget target |
nodeFrom = TIterableSequenceNode(target) and
nodeTo.asCfgNode() = target
@@ -1376,6 +1440,8 @@ module UnpackingAssignment {
/** All read steps associated with unpacking assignment. */
predicate unpackingAssignmentReadStep(Node nodeFrom, Content c, Node nodeTo) {
+ unpackingAssignmentForReadStep(nodeFrom, c, nodeTo)
+ or
unpackingAssignmentElementReadStep(nodeFrom, c, nodeTo)
or
unpackingAssignmentConvertingReadStep(nodeFrom, c, nodeTo)
@@ -1387,6 +1453,13 @@ module UnpackingAssignment {
or
unpackingAssignmentConvertingStoreStep(nodeFrom, c, nodeTo)
}
+
+ /** All flow steps associated with unpacking assignment. */
+ predicate unpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
+ unpackingAssignmentAssignmentFlowStep(nodeFrom, nodeTo)
+ or
+ unpackingAssignmentTupleFlowStep(nodeFrom, nodeTo)
+ }
}
import UnpackingAssignment
@@ -1425,25 +1498,10 @@ predicate popReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
)
}
-/** Data flows from a iterated sequence to the variable iterating over the sequence. */
-predicate comprehensionReadStep(CfgNode nodeFrom, Content c, EssaNode nodeTo) {
- // Comprehension
- // `[x+1 for x in l]`
- // nodeFrom is `l`, cfg node
- // nodeTo is `x`, essa var
- // c denotes element of list or set
- exists(Comp comp |
- // outermost for
- nodeFrom.getNode().getNode() = comp.getIterable() and
- nodeTo.getVar().getDefinition().(AssignmentDefinition).getDefiningNode().getNode() =
- comp.getIterationVariable(0).getAStore()
- or
- // an inner for
- exists(int n | n > 0 |
- nodeFrom.getNode().getNode() = comp.getNthInnerLoop(n).getIter() and
- nodeTo.getVar().getDefinition().(AssignmentDefinition).getDefiningNode().getNode() =
- comp.getNthInnerLoop(n).getTarget()
- )
+predicate forReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
+ exists(ForTarget target |
+ nodeFrom.asExpr() = target.getSource() and
+ nodeTo.asVar().(EssaNodeDefinition).getDefiningNode() = target
) and
(
c instanceof ListElementContent
diff --git a/python/ql/test/experimental/dataflow/coverage/dataflow.expected b/python/ql/test/experimental/dataflow/coverage/dataflow.expected
index ef7ebb797cd..623b86e2a1d 100644
--- a/python/ql/test/experimental/dataflow/coverage/dataflow.expected
+++ b/python/ql/test/experimental/dataflow/coverage/dataflow.expected
@@ -285,7 +285,38 @@ edges
| test.py:639:12:639:13 | ControlFlowNode for a2 [List element] | test.py:639:12:639:16 | ControlFlowNode for Subscript |
| test.py:640:10:640:11 | ControlFlowNode for a2 [List element] | test.py:640:10:640:14 | ControlFlowNode for Subscript |
| test.py:647:19:647:24 | ControlFlowNode for SOURCE | test.py:648:10:648:10 | ControlFlowNode for a |
-| test.py:739:16:739:21 | ControlFlowNode for SOURCE | test.py:742:10:742:36 | ControlFlowNode for return_from_inner_scope() |
+| test.py:655:10:655:51 | ControlFlowNode for List [List element, Tuple element at index 0] | test.py:656:16:656:17 | ControlFlowNode for tl [List element, Tuple element at index 0] |
+| test.py:655:12:655:17 | ControlFlowNode for SOURCE | test.py:655:12:655:28 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:655:12:655:28 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:655:10:655:51 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:655:33:655:38 | ControlFlowNode for SOURCE | test.py:655:33:655:49 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:655:33:655:49 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:655:10:655:51 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:656:9:656:9 | SSA variable x | test.py:657:14:657:14 | ControlFlowNode for x |
+| test.py:656:9:656:11 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:656:9:656:9 | SSA variable x |
+| test.py:656:9:656:11 | IterableSequence [Tuple element at index 0] | test.py:656:9:656:11 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:656:16:656:17 | ControlFlowNode for tl [List element, Tuple element at index 0] | test.py:656:9:656:11 | IterableSequence [Tuple element at index 0] |
+| test.py:663:10:663:51 | ControlFlowNode for List [List element, Tuple element at index 0] | test.py:664:17:664:18 | ControlFlowNode for tl [List element, Tuple element at index 0] |
+| test.py:663:12:663:17 | ControlFlowNode for SOURCE | test.py:663:12:663:28 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:663:12:663:28 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:663:10:663:51 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:663:33:663:38 | ControlFlowNode for SOURCE | test.py:663:33:663:49 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:663:33:663:49 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:663:10:663:51 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:664:9:664:10 | IterableElement | test.py:664:9:664:10 | SSA variable x [List element] |
+| test.py:664:9:664:10 | SSA variable x [List element] | test.py:666:14:666:14 | ControlFlowNode for x [List element] |
+| test.py:664:9:664:12 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:664:9:664:10 | IterableElement |
+| test.py:664:9:664:12 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:664:12:664:12 | SSA variable y |
+| test.py:664:9:664:12 | IterableSequence [Tuple element at index 0] | test.py:664:9:664:12 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:664:12:664:12 | SSA variable y | test.py:667:16:667:16 | ControlFlowNode for y |
+| test.py:664:17:664:18 | ControlFlowNode for tl [List element, Tuple element at index 0] | test.py:664:9:664:12 | IterableSequence [Tuple element at index 0] |
+| test.py:666:14:666:14 | ControlFlowNode for x [List element] | test.py:666:14:666:17 | ControlFlowNode for Subscript |
+| test.py:672:10:672:51 | ControlFlowNode for List [List element, Tuple element at index 0] | test.py:673:19:673:20 | ControlFlowNode for tl [List element, Tuple element at index 0] |
+| test.py:672:12:672:17 | ControlFlowNode for SOURCE | test.py:672:12:672:28 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:672:12:672:28 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:672:10:672:51 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:672:33:672:38 | ControlFlowNode for SOURCE | test.py:672:33:672:49 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:672:33:672:49 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:672:10:672:51 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:673:9:673:9 | SSA variable x | test.py:674:14:674:14 | ControlFlowNode for x |
+| test.py:673:9:673:14 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:673:9:673:9 | SSA variable x |
+| test.py:673:9:673:14 | IterableSequence [Tuple element at index 0] | test.py:673:9:673:14 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:673:19:673:20 | ControlFlowNode for tl [List element, Tuple element at index 0] | test.py:673:9:673:14 | IterableSequence [Tuple element at index 0] |
+| test.py:749:16:749:21 | ControlFlowNode for SOURCE | test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() |
nodes
| datamodel.py:38:6:38:17 | ControlFlowNode for f() | semmle.label | ControlFlowNode for f() |
| datamodel.py:38:8:38:13 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
@@ -628,8 +659,42 @@ nodes
| test.py:640:10:640:14 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| test.py:647:19:647:24 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
| test.py:648:10:648:10 | ControlFlowNode for a | semmle.label | ControlFlowNode for a |
-| test.py:739:16:739:21 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
-| test.py:742:10:742:36 | ControlFlowNode for return_from_inner_scope() | semmle.label | ControlFlowNode for return_from_inner_scope() |
+| test.py:655:10:655:51 | ControlFlowNode for List [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:655:12:655:17 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:655:12:655:28 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:655:33:655:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:655:33:655:49 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:656:9:656:9 | SSA variable x | semmle.label | SSA variable x |
+| test.py:656:9:656:11 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:656:9:656:11 | IterableSequence [Tuple element at index 0] | semmle.label | IterableSequence [Tuple element at index 0] |
+| test.py:656:16:656:17 | ControlFlowNode for tl [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for tl [List element, Tuple element at index 0] |
+| test.py:657:14:657:14 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| test.py:663:10:663:51 | ControlFlowNode for List [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:663:12:663:17 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:663:12:663:28 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:663:33:663:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:663:33:663:49 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:664:9:664:10 | IterableElement | semmle.label | IterableElement |
+| test.py:664:9:664:10 | SSA variable x [List element] | semmle.label | SSA variable x [List element] |
+| test.py:664:9:664:12 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:664:9:664:12 | IterableSequence [Tuple element at index 0] | semmle.label | IterableSequence [Tuple element at index 0] |
+| test.py:664:12:664:12 | SSA variable y | semmle.label | SSA variable y |
+| test.py:664:17:664:18 | ControlFlowNode for tl [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for tl [List element, Tuple element at index 0] |
+| test.py:666:14:666:14 | ControlFlowNode for x [List element] | semmle.label | ControlFlowNode for x [List element] |
+| test.py:666:14:666:17 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| test.py:667:16:667:16 | ControlFlowNode for y | semmle.label | ControlFlowNode for y |
+| test.py:672:10:672:51 | ControlFlowNode for List [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:672:12:672:17 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:672:12:672:28 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:672:33:672:38 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:672:33:672:49 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:673:9:673:9 | SSA variable x | semmle.label | SSA variable x |
+| test.py:673:9:673:14 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:673:9:673:14 | IterableSequence [Tuple element at index 0] | semmle.label | IterableSequence [Tuple element at index 0] |
+| test.py:673:19:673:20 | ControlFlowNode for tl [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for tl [List element, Tuple element at index 0] |
+| test.py:674:14:674:14 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
+| test.py:749:16:749:21 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() | semmle.label | ControlFlowNode for return_from_inner_scope() |
#select
| datamodel.py:38:6:38:17 | ControlFlowNode for f() | datamodel.py:38:8:38:13 | ControlFlowNode for SOURCE | datamodel.py:38:6:38:17 | ControlFlowNode for f() | Flow found |
| datamodel.py:71:6:71:24 | ControlFlowNode for Attribute() | datamodel.py:71:15:71:20 | ControlFlowNode for SOURCE | datamodel.py:71:6:71:24 | ControlFlowNode for Attribute() | Flow found |
@@ -724,4 +789,12 @@ nodes
| test.py:639:12:639:16 | ControlFlowNode for Subscript | test.py:606:31:606:36 | ControlFlowNode for SOURCE | test.py:639:12:639:16 | ControlFlowNode for Subscript | Flow found |
| test.py:640:10:640:14 | ControlFlowNode for Subscript | test.py:606:31:606:36 | ControlFlowNode for SOURCE | test.py:640:10:640:14 | ControlFlowNode for Subscript | Flow found |
| test.py:648:10:648:10 | ControlFlowNode for a | test.py:647:19:647:24 | ControlFlowNode for SOURCE | test.py:648:10:648:10 | ControlFlowNode for a | Flow found |
-| test.py:742:10:742:36 | ControlFlowNode for return_from_inner_scope() | test.py:739:16:739:21 | ControlFlowNode for SOURCE | test.py:742:10:742:36 | ControlFlowNode for return_from_inner_scope() | Flow found |
+| test.py:657:14:657:14 | ControlFlowNode for x | test.py:655:12:655:17 | ControlFlowNode for SOURCE | test.py:657:14:657:14 | ControlFlowNode for x | Flow found |
+| test.py:657:14:657:14 | ControlFlowNode for x | test.py:655:33:655:38 | ControlFlowNode for SOURCE | test.py:657:14:657:14 | ControlFlowNode for x | Flow found |
+| test.py:666:14:666:17 | ControlFlowNode for Subscript | test.py:663:12:663:17 | ControlFlowNode for SOURCE | test.py:666:14:666:17 | ControlFlowNode for Subscript | Flow found |
+| test.py:666:14:666:17 | ControlFlowNode for Subscript | test.py:663:33:663:38 | ControlFlowNode for SOURCE | test.py:666:14:666:17 | ControlFlowNode for Subscript | Flow found |
+| test.py:667:16:667:16 | ControlFlowNode for y | test.py:663:12:663:17 | ControlFlowNode for SOURCE | test.py:667:16:667:16 | ControlFlowNode for y | Flow found |
+| test.py:667:16:667:16 | ControlFlowNode for y | test.py:663:33:663:38 | ControlFlowNode for SOURCE | test.py:667:16:667:16 | ControlFlowNode for y | Flow found |
+| test.py:674:14:674:14 | ControlFlowNode for x | test.py:672:12:672:17 | ControlFlowNode for SOURCE | test.py:674:14:674:14 | ControlFlowNode for x | Flow found |
+| test.py:674:14:674:14 | ControlFlowNode for x | test.py:672:33:672:38 | ControlFlowNode for SOURCE | test.py:674:14:674:14 | ControlFlowNode for x | Flow found |
+| test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() | test.py:749:16:749:21 | ControlFlowNode for SOURCE | test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() | Flow found |
diff --git a/python/ql/test/experimental/dataflow/coverage/test.py b/python/ql/test/experimental/dataflow/coverage/test.py
index 6a9854903e5..542ab161ed6 100644
--- a/python/ql/test/experimental/dataflow/coverage/test.py
+++ b/python/ql/test/experimental/dataflow/coverage/test.py
@@ -192,7 +192,7 @@ def test_nested_comprehension_deep_with_local_flow():
def test_nested_comprehension_dict():
d = {"s": [SOURCE]}
x = [y for k, v in d.items() for y in v]
- SINK(x[0]) #$ MISSING:flow="SOURCE, l:-1 -> x[0]"
+ SINK(x[0]) #$ MISSING:flow="SOURCE, l:-2 -> x[0]"
def test_nested_comprehension_paren():
@@ -203,12 +203,12 @@ def test_nested_comprehension_paren():
# Iterable unpacking in comprehensions
def test_unpacking_comprehension():
x = [a for (a, b) in [(SOURCE, NONSOURCE)]]
- SINK(x[0]) # Flow missing
+ SINK(x[0]) #$ MISSING:flow="SOURCE, l:-1 -> x[0]"
def test_star_unpacking_comprehension():
x = [a[0] for (*a, b) in [(SOURCE, NONSOURCE)]]
- SINK(x[0]) # Flow missing
+ SINK(x[0]) #$ MISSING:flow="SOURCE, l:-1 -> x[0]"
# 6.2.8. Generator expressions
@@ -654,7 +654,7 @@ def test_iterable_repacking():
def test_iterable_unpacking_in_for():
tl = [(SOURCE, NONSOURCE), (SOURCE, NONSOURCE)]
for x,y in tl:
- SINK(x) #$ MISSING: flow="SOURCE, l:-2 -> x"
+ SINK(x) #$ flow="SOURCE, l:-2 -> x"
SINK_F(y)
@@ -663,8 +663,18 @@ def test_iterable_star_unpacking_in_for():
tl = [(SOURCE, NONSOURCE), (SOURCE, NONSOURCE)]
for *x,y in tl:
SINK_F(x)
- SINK(x[0]) #$ MISSING: flow="SOURCE, l:-3 -> x[0]"
+ SINK(x[0]) #$ flow="SOURCE, l:-3 -> x[0]"
+ SINK_F(y) #$ SPURIOUS: flow="SOURCE, l:-4 -> y" # FP here since we do not track the tuple lenght and so `*x` could be empty
+
+
+@expects(6)
+def test_iterable_star_unpacking_in_for_2():
+ tl = [(SOURCE, NONSOURCE), (SOURCE, NONSOURCE)]
+ for x,*y,z in tl:
+ SINK(x) #$ flow="SOURCE, l:-2 -> x"
SINK_F(y)
+ SINK_F(y[0])
+ SINK_F(z)
def test_deep_callgraph():
diff --git a/python/ql/test/experimental/dataflow/regression/dataflow.expected b/python/ql/test/experimental/dataflow/regression/dataflow.expected
index 2fe69282e37..dfce2c72737 100644
--- a/python/ql/test/experimental/dataflow/regression/dataflow.expected
+++ b/python/ql/test/experimental/dataflow/regression/dataflow.expected
@@ -21,3 +21,5 @@
| test.py:178:9:178:14 | ControlFlowNode for SOURCE | test.py:186:14:186:14 | ControlFlowNode for t |
| test.py:195:9:195:14 | ControlFlowNode for SOURCE | test.py:197:14:197:14 | ControlFlowNode for t |
| test.py:195:9:195:14 | ControlFlowNode for SOURCE | test.py:199:14:199:14 | ControlFlowNode for t |
+| test.py:202:10:202:15 | ControlFlowNode for SOURCE | test.py:204:14:204:14 | ControlFlowNode for i |
+| test.py:202:10:202:15 | ControlFlowNode for SOURCE | test.py:205:10:205:10 | ControlFlowNode for i |
diff --git a/python/ql/test/experimental/dataflow/regression/test.py b/python/ql/test/experimental/dataflow/regression/test.py
index aa620b6ba5f..80c9c7e5e34 100644
--- a/python/ql/test/experimental/dataflow/regression/test.py
+++ b/python/ql/test/experimental/dataflow/regression/test.py
@@ -201,8 +201,8 @@ def flow_through_type_test_if_no_class():
def flow_in_iteration():
t = [SOURCE]
for i in t:
- SINK(i) # Flow not found
- SINK(i) # Flow not found
+ SINK(i)
+ SINK(i)
def flow_in_generator():
seq = [SOURCE]
From 7f1affa122a1a2569533a97106674df37e3f82a3 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 29 Jan 2021 17:44:53 +0100
Subject: [PATCH 062/429] Python: UnpackingAssignment -> IterableUnpacking
---
.../dataflow/new/internal/DataFlowPrivate.qll | 44 +++++++++----------
1 file changed, 22 insertions(+), 22 deletions(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index 38478c6bc47..694243e0e81 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -201,7 +201,7 @@ module EssaFlow {
nodeFrom.asCfgNode() = nodeTo.asCfgNode().(IfExprNode).getAnOperand()
or
// Flow inside an unpacking assignment
- unpackingAssignmentFlowStep(nodeFrom, nodeTo)
+ iterableUnpackingFlowStep(nodeFrom, nodeTo)
or
// Overflow keyword argument
exists(CallNode call, CallableValue callable |
@@ -898,7 +898,7 @@ predicate storeStep(Node nodeFrom, Content c, Node nodeTo) {
or
comprehensionStoreStep(nodeFrom, c, nodeTo)
or
- unpackingAssignmentStoreStep(nodeFrom, c, nodeTo)
+ iterableUnpackingStoreStep(nodeFrom, c, nodeTo)
or
attributeStoreStep(nodeFrom, c, nodeTo)
or
@@ -1032,7 +1032,7 @@ predicate kwOverflowStoreStep(CfgNode nodeFrom, DictionaryElementContent c, Node
predicate readStep(Node nodeFrom, Content c, Node nodeTo) {
subscriptReadStep(nodeFrom, c, nodeTo)
or
- unpackingAssignmentReadStep(nodeFrom, c, nodeTo)
+ iterableUnpackingReadStep(nodeFrom, c, nodeTo)
or
popReadStep(nodeFrom, c, nodeTo)
or
@@ -1234,7 +1234,7 @@ predicate subscriptReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
*
* `c`: [ListElementContent]
*/
-module UnpackingAssignment {
+module IterableUnpacking {
/**
* The target of a `for`, e.g. `x` in `for x in list` or in `[42 for x in list]`.
* This class also records the source, which in both above cases is `list`.
@@ -1309,7 +1309,7 @@ module UnpackingAssignment {
* Step 1a
* Data flows from `iterable` to `TIterableSequence(sequence)`
*/
- predicate unpackingAssignmentAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
+ predicate iterableUnpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
exists(AssignmentTarget target |
nodeFrom.asExpr() = target.getValue() and
nodeTo = TIterableSequenceNode(target)
@@ -1320,7 +1320,7 @@ module UnpackingAssignment {
* Step 1b
* Data is read from `iterable` to `TIterableSequence(sequence)`
*/
- predicate unpackingAssignmentForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
+ predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
exists(ForTarget target |
nodeFrom.asExpr() = target.getSource() and
nodeTo = TIterableSequenceNode(target.(SequenceNode))
@@ -1336,7 +1336,7 @@ module UnpackingAssignment {
* Step 2
* Data flows from `TIterableSequence(sequence)` to `sequence`
*/
- predicate unpackingAssignmentTupleFlowStep(Node nodeFrom, Node nodeTo) {
+ predicate iterableUnpackingTupleFlowStep(Node nodeFrom, Node nodeTo) {
exists(UnpackingAssignmentSequenceTarget target |
nodeFrom = TIterableSequenceNode(target) and
nodeTo.asCfgNode() = target
@@ -1349,7 +1349,7 @@ module UnpackingAssignment {
* As `sequence` is modeled as a tuple, we will not read tuple content as that would allow
* crosstalk.
*/
- predicate unpackingAssignmentConvertingReadStep(Node nodeFrom, Content c, Node nodeTo) {
+ predicate iterableUnpackingConvertingReadStep(Node nodeFrom, Content c, Node nodeTo) {
exists(UnpackingAssignmentSequenceTarget target |
nodeFrom = TIterableSequenceNode(target) and
nodeTo = TIterableElementNode(target) and
@@ -1368,7 +1368,7 @@ module UnpackingAssignment {
* Content type is `TupleElementContent` with indices taken from the syntax.
* For instance, if `sequence` is `(a, *b, c)`, content is written to index 0, 1, and 2.
*/
- predicate unpackingAssignmentConvertingStoreStep(Node nodeFrom, Content c, Node nodeTo) {
+ predicate iterableUnpackingConvertingStoreStep(Node nodeFrom, Content c, Node nodeTo) {
exists(UnpackingAssignmentSequenceTarget target |
nodeFrom = TIterableElementNode(target) and
nodeTo.asCfgNode() = target and
@@ -1388,7 +1388,7 @@ module UnpackingAssignment {
*
* c) If the element is a starred variable, with control-flow node `v`, `toNode` is `TIterableElement(v)`.
*/
- predicate unpackingAssignmentElementReadStep(Node nodeFrom, Content c, Node nodeTo) {
+ predicate iterableUnpackingElementReadStep(Node nodeFrom, Content c, Node nodeTo) {
exists(
UnpackingAssignmentSequenceTarget target, int index, ControlFlowNode element, int starIndex
|
@@ -1430,7 +1430,7 @@ module UnpackingAssignment {
* Data flows from `TIterableElement(v)` to the essa variable for `v`, with
* content type `ListElementContent`.
*/
- predicate unpackingAssignmentStarredElementStoreStep(Node nodeFrom, Content c, Node nodeTo) {
+ predicate iterableUnpackingStarredElementStoreStep(Node nodeFrom, Content c, Node nodeTo) {
exists(ControlFlowNode starred | starred.getNode() instanceof Starred |
nodeFrom = TIterableElementNode(starred) and
nodeTo.asVar().getDefinition().(MultiAssignmentDefinition).getDefiningNode() = starred and
@@ -1439,30 +1439,30 @@ module UnpackingAssignment {
}
/** All read steps associated with unpacking assignment. */
- predicate unpackingAssignmentReadStep(Node nodeFrom, Content c, Node nodeTo) {
- unpackingAssignmentForReadStep(nodeFrom, c, nodeTo)
+ predicate iterableUnpackingReadStep(Node nodeFrom, Content c, Node nodeTo) {
+ iterableUnpackingForReadStep(nodeFrom, c, nodeTo)
or
- unpackingAssignmentElementReadStep(nodeFrom, c, nodeTo)
+ iterableUnpackingElementReadStep(nodeFrom, c, nodeTo)
or
- unpackingAssignmentConvertingReadStep(nodeFrom, c, nodeTo)
+ iterableUnpackingConvertingReadStep(nodeFrom, c, nodeTo)
}
/** All store steps associated with unpacking assignment. */
- predicate unpackingAssignmentStoreStep(Node nodeFrom, Content c, Node nodeTo) {
- unpackingAssignmentStarredElementStoreStep(nodeFrom, c, nodeTo)
+ predicate iterableUnpackingStoreStep(Node nodeFrom, Content c, Node nodeTo) {
+ iterableUnpackingStarredElementStoreStep(nodeFrom, c, nodeTo)
or
- unpackingAssignmentConvertingStoreStep(nodeFrom, c, nodeTo)
+ iterableUnpackingConvertingStoreStep(nodeFrom, c, nodeTo)
}
/** All flow steps associated with unpacking assignment. */
- predicate unpackingAssignmentFlowStep(Node nodeFrom, Node nodeTo) {
- unpackingAssignmentAssignmentFlowStep(nodeFrom, nodeTo)
+ predicate iterableUnpackingFlowStep(Node nodeFrom, Node nodeTo) {
+ iterableUnpackingAssignmentFlowStep(nodeFrom, nodeTo)
or
- unpackingAssignmentTupleFlowStep(nodeFrom, nodeTo)
+ iterableUnpackingTupleFlowStep(nodeFrom, nodeTo)
}
}
-import UnpackingAssignment
+import IterableUnpacking
/** Data flows from a sequence to a call to `pop` on the sequence. */
predicate popReadStep(CfgNode nodeFrom, Content c, CfgNode nodeTo) {
From 05a138694d41323f8141784187e8bae028246ecb Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Fri, 29 Jan 2021 21:12:44 +0100
Subject: [PATCH 063/429] Python: Fix crashing test
---
python/ql/test/experimental/dataflow/coverage/test.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/python/ql/test/experimental/dataflow/coverage/test.py b/python/ql/test/experimental/dataflow/coverage/test.py
index 542ab161ed6..0da8b40b274 100644
--- a/python/ql/test/experimental/dataflow/coverage/test.py
+++ b/python/ql/test/experimental/dataflow/coverage/test.py
@@ -672,8 +672,7 @@ def test_iterable_star_unpacking_in_for_2():
tl = [(SOURCE, NONSOURCE), (SOURCE, NONSOURCE)]
for x,*y,z in tl:
SINK(x) #$ flow="SOURCE, l:-2 -> x"
- SINK_F(y)
- SINK_F(y[0])
+ SINK_F(y) # The list itself is not tainted (and is here empty)
SINK_F(z)
From b7df18b97e75879ffed2f2c430072c05eba43584 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Sun, 31 Jan 2021 15:16:40 +0300
Subject: [PATCH 064/429] Update
AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
---
...ssOfMemoryLocationAfterEndOfBufferUsingStrlen.ql | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
index 3e1cfdd6396..012109074e9 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-788/AccessOfMemoryLocationAfterEndOfBufferUsingStrlen.ql
@@ -13,11 +13,22 @@
import cpp
import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+import semmle.code.cpp.dataflow.DataFlow
from StrlenCall fc, AssignExpr expr, ArrayExpr exprarr
where
exprarr = expr.getLValue() and
expr.getRValue().getValue().toInt() = 0 and
- exprarr.getArrayOffset() = fc and
+ globalValueNumber(exprarr.getArrayOffset()) = globalValueNumber(fc) and
+ not exists(Expr exptmp |
+ (
+ DataFlow::localExprFlow(fc, exptmp) or
+ exptmp.getAChild*() = fc.getArgument(0).(VariableAccess).getTarget().getAnAccess()
+ ) and
+ dominates(exptmp, expr) and
+ postDominates(exptmp, fc) and
+ not exptmp.getEnclosingStmt() = fc.getEnclosingStmt() and
+ not exptmp.getEnclosingStmt() = expr.getEnclosingStmt()
+ ) and
globalValueNumber(fc.getArgument(0)) = globalValueNumber(exprarr.getArrayBase())
select expr, "potential unsafe or redundant assignment."
From 2b946aee5a3ff0c7d5e284e2ff7527e4dd79cda6 Mon Sep 17 00:00:00 2001
From: ihsinme <61293369+ihsinme@users.noreply.github.com>
Date: Sun, 31 Jan 2021 15:21:54 +0300
Subject: [PATCH 065/429] Update
WrongInDetectingAndHandlingMemoryAllocationErrors.ql
---
.../WrongInDetectingAndHandlingMemoryAllocationErrors.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
index 04a284d7846..403d8388b17 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-570/WrongInDetectingAndHandlingMemoryAllocationErrors.ql
@@ -58,7 +58,7 @@ class WrongCheckErrorOperatorNew extends FunctionCall {
predicate isExistsIfCondition() {
exists(IfCompareWithZero ifc, AssignExpr aex, Initializer it |
// call `operator new` directly from the condition of `operator if`.
- this = ifc.getCondition().getAChild()
+ this = ifc.getCondition().getAChild*()
or
// check results call `operator new` with variable appropriation
postDominates(ifc, this) and
From 27fd46b8558cfd825baed747a90073aeb9f63ad1 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Mon, 1 Feb 2021 08:55:20 +0100
Subject: [PATCH 066/429] Python: Update test expectation
---
.../test/experimental/dataflow/coverage/dataflow.expected | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/python/ql/test/experimental/dataflow/coverage/dataflow.expected b/python/ql/test/experimental/dataflow/coverage/dataflow.expected
index 623b86e2a1d..978b810ea23 100644
--- a/python/ql/test/experimental/dataflow/coverage/dataflow.expected
+++ b/python/ql/test/experimental/dataflow/coverage/dataflow.expected
@@ -316,7 +316,7 @@ edges
| test.py:673:9:673:14 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:673:9:673:9 | SSA variable x |
| test.py:673:9:673:14 | IterableSequence [Tuple element at index 0] | test.py:673:9:673:14 | ControlFlowNode for Tuple [Tuple element at index 0] |
| test.py:673:19:673:20 | ControlFlowNode for tl [List element, Tuple element at index 0] | test.py:673:9:673:14 | IterableSequence [Tuple element at index 0] |
-| test.py:749:16:749:21 | ControlFlowNode for SOURCE | test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() |
+| test.py:748:16:748:21 | ControlFlowNode for SOURCE | test.py:751:10:751:36 | ControlFlowNode for return_from_inner_scope() |
nodes
| datamodel.py:38:6:38:17 | ControlFlowNode for f() | semmle.label | ControlFlowNode for f() |
| datamodel.py:38:8:38:13 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
@@ -693,8 +693,8 @@ nodes
| test.py:673:9:673:14 | IterableSequence [Tuple element at index 0] | semmle.label | IterableSequence [Tuple element at index 0] |
| test.py:673:19:673:20 | ControlFlowNode for tl [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for tl [List element, Tuple element at index 0] |
| test.py:674:14:674:14 | ControlFlowNode for x | semmle.label | ControlFlowNode for x |
-| test.py:749:16:749:21 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
-| test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() | semmle.label | ControlFlowNode for return_from_inner_scope() |
+| test.py:748:16:748:21 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:751:10:751:36 | ControlFlowNode for return_from_inner_scope() | semmle.label | ControlFlowNode for return_from_inner_scope() |
#select
| datamodel.py:38:6:38:17 | ControlFlowNode for f() | datamodel.py:38:8:38:13 | ControlFlowNode for SOURCE | datamodel.py:38:6:38:17 | ControlFlowNode for f() | Flow found |
| datamodel.py:71:6:71:24 | ControlFlowNode for Attribute() | datamodel.py:71:15:71:20 | ControlFlowNode for SOURCE | datamodel.py:71:6:71:24 | ControlFlowNode for Attribute() | Flow found |
@@ -797,4 +797,4 @@ nodes
| test.py:667:16:667:16 | ControlFlowNode for y | test.py:663:33:663:38 | ControlFlowNode for SOURCE | test.py:667:16:667:16 | ControlFlowNode for y | Flow found |
| test.py:674:14:674:14 | ControlFlowNode for x | test.py:672:12:672:17 | ControlFlowNode for SOURCE | test.py:674:14:674:14 | ControlFlowNode for x | Flow found |
| test.py:674:14:674:14 | ControlFlowNode for x | test.py:672:33:672:38 | ControlFlowNode for SOURCE | test.py:674:14:674:14 | ControlFlowNode for x | Flow found |
-| test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() | test.py:749:16:749:21 | ControlFlowNode for SOURCE | test.py:752:10:752:36 | ControlFlowNode for return_from_inner_scope() | Flow found |
+| test.py:751:10:751:36 | ControlFlowNode for return_from_inner_scope() | test.py:748:16:748:21 | ControlFlowNode for SOURCE | test.py:751:10:751:36 | ControlFlowNode for return_from_inner_scope() | Flow found |
From 2a9e66a6675409f60164f32e73f17e187bfa1f76 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Mon, 1 Feb 2021 11:17:04 +0100
Subject: [PATCH 067/429] Python: Fix problem after merge conflict
---
python/ql/src/semmle/python/frameworks/Tornado.qll | 2 ++
1 file changed, 2 insertions(+)
diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll
index 195bddf1552..1f5859c4945 100644
--- a/python/ql/src/semmle/python/frameworks/Tornado.qll
+++ b/python/ql/src/semmle/python/frameworks/Tornado.qll
@@ -586,6 +586,8 @@ private module Tornado {
override DataFlow::Node getBody() { none() }
override string getMimetypeDefault() { none() }
+
+ override DataFlow::Node getMimetypeOrContentTypeArg() { none() }
}
/**
From 3363f5e6dbbba32dada345a76238b73fc3e24732 Mon Sep 17 00:00:00 2001
From: CaptainFreak
Date: Mon, 1 Feb 2021 18:01:34 +0530
Subject: [PATCH 068/429] JS: add query for Express-HBS LFR
---
.../Security/CWE-073/ExpressHbsLFR.ql | 44 +++++++++++++++++++
.../documentation-examples/ExpressHbsLFR.js | 10 +++++
.../ExpressHbsLFR_fixed.js | 10 +++++
3 files changed, 64 insertions(+)
create mode 100644 javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql
create mode 100644 javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js
create mode 100644 javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js
diff --git a/javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql b/javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql
new file mode 100644
index 00000000000..de66932a7af
--- /dev/null
+++ b/javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql
@@ -0,0 +1,44 @@
+/**
+ * @name Express-Hbs Local File Read and Potential RCE
+ * @description Writing user input directly to res.render of ExpressJS used with Hbs can lead to LFR
+ * @kind path-problem
+ * @problem.severity error
+ * @precision high
+ * @id js/express-hbs-lfr
+ * @tags security
+ * external/cwe/cwe-073
+ * external/cwe/cwe-094
+ */
+
+import javascript
+import DataFlow
+import PathGraph
+import Express
+import semmle.javascript.DynamicPropertyAccess
+
+predicate isUsingHbsEngine() {
+ exists(MethodCallExpr method |
+ method.getMethodName() = "set" and
+ Express::appCreation().flowsToExpr(method.getReceiver()) and
+ method.getArgument(1).getStringValue().matches("hbs")
+ )
+}
+
+class HbsLFRTaint extends TaintTracking::Configuration {
+ HbsLFRTaint() { this = "HbsLFRTaint" }
+
+ override predicate isSource(Node node) { node instanceof RemoteFlowSource }
+
+ override predicate isSink(Node node) {
+ exists(MethodCallExpr mc |
+ Express::isResponse(mc.getReceiver()) and
+ mc.getMethodName() = "render" and
+ node.asExpr() = mc.getArgument(1) and
+ isUsingHbsEngine()
+ )
+ }
+}
+
+from HbsLFRTaint cfg, Node source, Node sink
+where cfg.hasFlow(source, sink)
+select source, sink
diff --git a/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js b/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js
new file mode 100644
index 00000000000..3e0e42f33ca
--- /dev/null
+++ b/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js
@@ -0,0 +1,10 @@
+var app = require('express')();
+app.set('view engine', 'hbs');
+
+app.post('/path', function(req, res) {
+ var bodyParameter = req.body.bodyParameter
+ var queryParameter = req.query.queryParameter
+
+ res.render('template', bodyParameter)
+ res.render('template', queryParameter)
+});
\ No newline at end of file
diff --git a/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js b/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js
new file mode 100644
index 00000000000..54e8e865a1d
--- /dev/null
+++ b/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js
@@ -0,0 +1,10 @@
+var app = require('express')();
+app.set('view engine', 'hbs');
+
+app.post('/path', function(req, res) {
+ var bodyParameter = req.body.bodyParameter
+ var queryParameter = req.query.queryParameter
+
+ res.render('template', {bodyParameter})
+ res.render('template', {queryParameter})
+});
\ No newline at end of file
From 7d62e33feb871d005dfb938056e1d1bce6af0ff7 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Mon, 1 Feb 2021 11:38:52 +0100
Subject: [PATCH 069/429] C#: Rework function pointer/delegate call DF
---
csharp/ql/src/Dead Code/DeadStoreOfLocal.ql | 2 +-
.../raw/internal/desugar/Delegate.qll | 4 +-
.../code/csharp/dataflow/CallContext.qll | 25 ++-
.../dataflow/internal/DelegateDataFlow.qll | 76 +++++--
.../internal/FunctionPointerDataFlow.qll | 205 ------------------
.../code/csharp/dataflow/internal/SsaImpl.qll | 2 +-
.../ql/src/semmle/code/csharp/exprs/Call.qll | 78 ++++---
.../semmle/code/csharp/metrics/Coupling.qll | 2 +-
.../dataflow/delegates/DelegateFlow.cs | 4 +-
.../dataflow/delegates/DelegateFlow.expected | 2 +
.../expressions/DelegateCall1.ql | 2 +-
.../expressions/DelegateCall2.ql | 2 +-
.../expressions/DelegateCall3.ql | 2 +-
.../library-tests/expressions/EventAccess3.ql | 2 +-
14 files changed, 133 insertions(+), 275 deletions(-)
delete mode 100755 csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll
diff --git a/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql b/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
index 2e8d1b92e02..4e214c6f6d0 100644
--- a/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
+++ b/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql
@@ -63,7 +63,7 @@ predicate mayEscape(LocalVariable v) {
exists(Callable c, Expr e, Expr succ | c = getACapturingCallableAncestor(v) |
e = getADelegateExpr(c) and
DataFlow::localExprFlow(e, succ) and
- not succ = any(DelegateCall dc).getDelegateExpr() and
+ not succ = any(DelegateCall dc).getExpr() and
not succ = any(Cast cast).getExpr() and
not succ = any(Call call | nonEscapingCall(call)).getAnArgument() and
not succ = any(AssignableDefinition ad | ad.getTarget() instanceof LocalVariable).getSource()
diff --git a/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Delegate.qll b/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Delegate.qll
index 939f14ba8fe..f7c80e497fa 100644
--- a/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Delegate.qll
+++ b/csharp/ql/src/experimental/ir/implementation/raw/internal/desugar/Delegate.qll
@@ -97,9 +97,7 @@ private class TranslatedDelegateInvokeCall extends TranslatedCompilerGeneratedCa
)
}
- override TranslatedExprBase getQualifier() {
- result = getTranslatedExpr(generatedBy.getDelegateExpr())
- }
+ override TranslatedExprBase getQualifier() { result = getTranslatedExpr(generatedBy.getExpr()) }
override Instruction getQualifierResult() { result = getQualifier().getResult() }
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll b/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll
index b0a6d9d98ed..aa501a58e0d 100755
--- a/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll
@@ -11,7 +11,8 @@ cached
private newtype TCallContext =
TEmptyCallContext() or
TArgNonDelegateCallContext(Expr arg) { exists(DispatchCall dc | arg = dc.getArgument(_)) } or
- TArgDelegateCallContext(DelegateCall dc, int i) { exists(dc.getArgument(i)) }
+ TArgDelegateCallContext(DelegateCall dc, int i) { exists(dc.getArgument(i)) } or
+ TArgFunctionPointerCallContext(FunctionPointerCall fptrc, int i) { exists(fptrc.getArgument(i)) }
/**
* A call context.
@@ -60,12 +61,14 @@ class NonDelegateCallArgumentCallContext extends ArgumentCallContext, TArgNonDel
override Location getLocation() { result = arg.getLocation() }
}
-/** An argument of a delegate call. */
-class DelegateCallArgumentCallContext extends ArgumentCallContext, TArgDelegateCallContext {
- DelegateCall dc;
+/** An argument of a delegate or function pointer call. */
+class DelegateLikeCallArgumentCallContext extends ArgumentCallContext {
+ DelegateLikeCall dc;
int arg;
- DelegateCallArgumentCallContext() { this = TArgDelegateCallContext(dc, arg) }
+ DelegateLikeCallArgumentCallContext() {
+ this = TArgDelegateCallContext(dc, arg) or this = TArgFunctionPointerCallContext(dc, arg)
+ }
override predicate isArgument(Expr call, int i) {
call = dc and
@@ -76,3 +79,15 @@ class DelegateCallArgumentCallContext extends ArgumentCallContext, TArgDelegateC
override Location getLocation() { result = dc.getArgument(arg).getLocation() }
}
+
+/** An argument of a delegate call. */
+class DelegateCallArgumentCallContext extends DelegateLikeCallArgumentCallContext,
+ TArgDelegateCallContext {
+ DelegateCallArgumentCallContext() { this = TArgDelegateCallContext(dc, arg) }
+}
+
+/** An argument of a function pointer call. */
+class FunctionPointerCallArgumentCallContext extends DelegateLikeCallArgumentCallContext,
+ TArgFunctionPointerCallContext {
+ FunctionPointerCallArgumentCallContext() { this = TArgFunctionPointerCallContext(dc, arg) }
+}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll
index 0001dd357b0..af70cb19654 100755
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll
@@ -14,8 +14,14 @@ private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.frameworks.system.linq.Expressions
+/** A source of flow for a delegate or function pointer expression. */
+private class DelegateLikeFlowSource extends DataFlow::ExprNode {
+ /** Gets the callable that is referenced in this delegate or function pointer flow source. */
+ Callable getCallable() { none() }
+}
+
/** A source of flow for a delegate expression. */
-private class DelegateFlowSource extends DataFlow::ExprNode {
+private class DelegateFlowSource extends DelegateLikeFlowSource {
Callable c;
DelegateFlowSource() {
@@ -27,11 +33,26 @@ private class DelegateFlowSource extends DataFlow::ExprNode {
}
/** Gets the callable that is referenced in this delegate flow source. */
- Callable getCallable() { result = c }
+ override Callable getCallable() { result = c }
}
-/** A sink of flow for a delegate expression. */
-abstract private class DelegateFlowSink extends DataFlow::Node {
+/** A source of flow for a function pointer expression. */
+private class FunctionPointerFlowSource extends DelegateLikeFlowSource {
+ Callable c;
+
+ FunctionPointerFlowSource() {
+ this.getExpr() =
+ any(Expr e |
+ c = e.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
+ )
+ }
+
+ /** Gets the callable that is referenced in this function pointer flow source. */
+ override Callable getCallable() { result = c }
+}
+
+/** A sink of flow for a delegate or function pointer expression. */
+abstract private class DelegateLikeFlowSink extends DataFlow::Node {
/**
* Gets an actual run-time target of this delegate call in the given call
* context, if any. The call context records the *last* call required to
@@ -85,25 +106,41 @@ abstract private class DelegateFlowSink extends DataFlow::Node {
*/
cached
Callable getARuntimeTarget(CallContext context) {
- exists(DelegateFlowSource dfs |
+ exists(DelegateLikeFlowSource dfs |
flowsFrom(this, dfs, _, context) and
result = dfs.getCallable()
)
}
}
+/** A delegate or function pointer call expression. */
+class DelegateLikeCallExpr extends DelegateLikeFlowSink, DataFlow::ExprNode {
+ /** Gets the delegate or function pointer call that this expression belongs to. */
+ DelegateLikeCall getCall() { none() }
+}
+
/** A delegate call expression. */
-class DelegateCallExpr extends DelegateFlowSink, DataFlow::ExprNode {
+class DelegateCallExpr extends DelegateLikeCallExpr {
DelegateCall dc;
- DelegateCallExpr() { this.getExpr() = dc.getDelegateExpr() }
+ DelegateCallExpr() { this.getExpr() = dc.getExpr() }
/** Gets the delegate call that this expression belongs to. */
- DelegateCall getDelegateCall() { result = dc }
+ override DelegateCall getCall() { result = dc }
+}
+
+/** A function pointer call expression. */
+class FunctionPointerCallExpr extends DelegateLikeCallExpr {
+ FunctionPointerCall fptrc;
+
+ FunctionPointerCallExpr() { this.getExpr() = fptrc.getExpr() }
+
+ /** Gets the function pointer call that this expression belongs to. */
+ override FunctionPointerCall getCall() { result = fptrc }
}
/** A parameter of delegate type belonging to a callable with a flow summary. */
-class SummaryDelegateParameterSink extends DelegateFlowSink, ParameterNode {
+class SummaryDelegateParameterSink extends DelegateLikeFlowSink, ParameterNode {
SummaryDelegateParameterSink() {
this.getType() instanceof SystemLinqExpressions::DelegateExtType and
this.isParameterOf(any(SummarizedCallable c), _)
@@ -111,7 +148,7 @@ class SummaryDelegateParameterSink extends DelegateFlowSink, ParameterNode {
}
/** A delegate expression that is added to an event. */
-class AddEventSource extends DelegateFlowSink, DataFlow::ExprNode {
+class AddEventSource extends DelegateLikeFlowSink, DataFlow::ExprNode {
AddEventExpr ae;
AddEventSource() { this.getExpr() = ae.getRValue() }
@@ -150,7 +187,7 @@ private class NormalReturnNode extends Node {
* records the last call on the path from `node` to `sink`, if any.
*/
private predicate flowsFrom(
- DelegateFlowSink sink, DataFlow::Node node, boolean isReturned, CallContext lastCall
+ DelegateLikeFlowSink sink, DataFlow::Node node, boolean isReturned, CallContext lastCall
) {
// Base case
sink = node and
@@ -188,7 +225,8 @@ private predicate flowsFrom(
or
// Flow into a callable (delegate call)
exists(
- ParameterNode mid, CallContext prevLastCall, DelegateCall call, Callable c, Parameter p, int i
+ ParameterNode mid, CallContext prevLastCall, DelegateLikeCall call, Callable c, Parameter p,
+ int i
|
flowsFrom(sink, mid, isReturned, prevLastCall) and
isReturned = false and
@@ -238,14 +276,14 @@ private predicate flowIntoNonDelegateCall(NonDelegateCall call, Expr arg, DotNet
}
pragma[noinline]
-private predicate flowIntoDelegateCall(DelegateCall call, Callable c, Expr arg, int i) {
- exists(DelegateFlowSource dfs, DelegateCallExpr dce |
+private predicate flowIntoDelegateCall(DelegateLikeCall call, Callable c, Expr arg, int i) {
+ exists(DelegateLikeFlowSource dfs, DelegateLikeCallExpr dce |
// the call context is irrelevant because the delegate call
// itself will be the context
flowsFrom(dce, dfs, _, _) and
arg = call.getArgument(i) and
c = dfs.getCallable() and
- call = dce.getDelegateCall()
+ call = dce.getCall()
)
}
@@ -255,11 +293,13 @@ private predicate flowOutOfNonDelegateCall(NonDelegateCall call, NormalReturnNod
}
pragma[noinline]
-private predicate flowOutOfDelegateCall(DelegateCall dc, NormalReturnNode ret, CallContext lastCall) {
- exists(DelegateFlowSource dfs, DelegateCallExpr dce, Callable c |
+private predicate flowOutOfDelegateCall(
+ DelegateLikeCall dc, NormalReturnNode ret, CallContext lastCall
+) {
+ exists(DelegateLikeFlowSource dfs, DelegateLikeCallExpr dce, Callable c |
flowsFrom(dce, dfs, _, lastCall) and
ret.getEnclosingCallable() = c and
c = dfs.getCallable() and
- dc = dce.getDelegateCall()
+ dc = dce.getCall()
)
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll
deleted file mode 100755
index 495beb93a22..00000000000
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/FunctionPointerDataFlow.qll
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * INTERNAL: Do not use.
- *
- * Provides classes for resolving function pointer calls.
- */
-
-import csharp
-private import dotnet
-private import semmle.code.csharp.dataflow.CallContext
-private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
-private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
-private import semmle.code.csharp.dataflow.internal.DataFlowPublic
-private import semmle.code.csharp.dataflow.FlowSummary
-private import semmle.code.csharp.dispatch.Dispatch
-private import semmle.code.csharp.frameworks.system.linq.Expressions
-
-/** A source of flow for a function pointer expression. */
-private class FunctionPointerFlowSource extends DataFlow::ExprNode {
- Callable c;
-
- FunctionPointerFlowSource() {
- this.getExpr() =
- any(Expr e |
- c = e.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
- )
- }
-
- /** Gets the callable that is referenced in this function pointer flow source. */
- Callable getCallable() { result = c }
-}
-
-/** A sink of flow for a function pointer expression. */
-abstract private class FunctionPointerFlowSink extends DataFlow::Node {
- /**
- * Gets an actual run-time target of this function pointer call in the given call
- * context, if any. The call context records the *last* call required to
- * resolve the target, if any.
- *
- * See examples in `DelegateFlowSink`.
- */
- cached
- Callable getARuntimeTarget(CallContext context) {
- exists(FunctionPointerFlowSource fptrfs |
- flowsFrom(this, fptrfs, _, context) and
- result = fptrfs.getCallable()
- )
- }
-}
-
-/** A function pointer call expression. */
-class FunctionPointerCallExpr extends FunctionPointerFlowSink, DataFlow::ExprNode {
- FunctionPointerCall fptrc;
-
- FunctionPointerCallExpr() { this.getExpr() = fptrc.getFunctionPointerExpr() }
-
- /** Gets the function pointer call that this expression belongs to. */
- FunctionPointerCall getFunctionPointerCall() { result = fptrc }
-}
-
-/** A non-function pointer call. */
-private class NonFunctionPointerCall extends Expr {
- private DispatchCall dc;
-
- NonFunctionPointerCall() { this = dc.getCall() }
-
- /**
- * Gets a run-time target of this call. A target is always a source
- * declaration, and if the callable has both CIL and source code, only
- * the source code version is returned.
- */
- Callable getARuntimeTarget() { result = getCallableForDataFlow(dc.getADynamicTarget()) }
-
- /** Gets the `i`th argument of this call. */
- Expr getArgument(int i) { result = dc.getArgument(i) }
-}
-
-private class NormalReturnNode extends Node {
- NormalReturnNode() { this.(ReturnNode).getKind() instanceof NormalReturnKind }
-}
-
-/**
- * Holds if data can flow (inter-procedurally) to function pointer `sink` from
- * `node`. This predicate searches backwards from `sink` to `node`.
- *
- * The parameter `isReturned` indicates whether the path from `sink` to
- * `node` goes through a returned expression. The call context `lastCall`
- * records the last call on the path from `node` to `sink`, if any.
- */
-private predicate flowsFrom(
- FunctionPointerFlowSink sink, DataFlow::Node node, boolean isReturned, CallContext lastCall
-) {
- // Base case
- sink = node and
- isReturned = false and
- lastCall instanceof EmptyCallContext
- or
- // Local flow
- exists(DataFlow::Node mid | flowsFrom(sink, mid, isReturned, lastCall) |
- LocalFlow::localFlowStepCommon(node, mid)
- or
- exists(Ssa::Definition def |
- LocalFlow::localSsaFlowStep(def, node, mid) and
- LocalFlow::usesInstanceField(def)
- )
- )
- or
- // Flow through static field or property
- exists(DataFlow::Node mid |
- flowsFrom(sink, mid, _, _) and
- jumpStep(node, mid) and
- isReturned = false and
- lastCall instanceof EmptyCallContext
- )
- or
- // Flow into a callable (non-function pointer call)
- exists(ParameterNode mid, CallContext prevLastCall, NonFunctionPointerCall call, Parameter p |
- flowsFrom(sink, mid, isReturned, prevLastCall) and
- isReturned = false and
- p = mid.getParameter() and
- flowIntoNonFunctionPointerCall(call, node.asExpr(), p) and
- lastCall = getLastCall(prevLastCall, call, p.getPosition())
- )
- or
- // Flow into a callable (function pointer call)
- exists(
- ParameterNode mid, CallContext prevLastCall, FunctionPointerCall call, Callable c, Parameter p,
- int i
- |
- flowsFrom(sink, mid, isReturned, prevLastCall) and
- isReturned = false and
- flowIntoFunctionPointerCall(call, c, node.asExpr(), i) and
- c.getParameter(i) = p and
- p = mid.getParameter() and
- lastCall = getLastCall(prevLastCall, call, i)
- )
- or
- // Flow out of a callable (non-function pointer call).
- exists(DataFlow::ExprNode mid |
- flowsFrom(sink, mid, _, lastCall) and
- isReturned = true and
- flowOutOfNonFunctionPointerCall(mid.getExpr(), node)
- )
- or
- // Flow out of a callable (function pointer call).
- exists(DataFlow::ExprNode mid |
- flowsFrom(sink, mid, _, _) and
- isReturned = true and
- flowOutOfFunctionPointerCall(mid.getExpr(), node, lastCall)
- )
-}
-
-/**
- * Gets the last call when tracking flow into `call`. The context
- * `prevLastCall` is the previous last call, so the result is the
- * previous call if it exists, otherwise `call` is the last call.
- */
-bindingset[call, i]
-private CallContext getLastCall(CallContext prevLastCall, Expr call, int i) {
- prevLastCall instanceof EmptyCallContext and
- result.(ArgumentCallContext).isArgument(call, i)
- or
- prevLastCall instanceof ArgumentCallContext and
- result = prevLastCall
-}
-
-pragma[noinline]
-private predicate flowIntoNonFunctionPointerCall(
- NonFunctionPointerCall call, Expr arg, DotNet::Parameter p
-) {
- exists(DotNet::Callable callable, int i |
- callable = call.getARuntimeTarget() and
- p = callable.getAParameter() and
- arg = call.getArgument(i) and
- i = p.getPosition()
- )
-}
-
-pragma[noinline]
-private predicate flowIntoFunctionPointerCall(FunctionPointerCall call, Callable c, Expr arg, int i) {
- exists(FunctionPointerFlowSource fptrfs, FunctionPointerCallExpr fptrce |
- // the call context is irrelevant because the function pointer call
- // itself will be the context
- flowsFrom(fptrce, fptrfs, _, _) and
- arg = call.getArgument(i) and
- c = fptrfs.getCallable() and
- call = fptrce.getFunctionPointerCall()
- )
-}
-
-pragma[noinline]
-private predicate flowOutOfNonFunctionPointerCall(NonFunctionPointerCall call, NormalReturnNode ret) {
- call.getARuntimeTarget() = ret.getEnclosingCallable()
-}
-
-pragma[noinline]
-private predicate flowOutOfFunctionPointerCall(
- FunctionPointerCall call, NormalReturnNode ret, CallContext lastCall
-) {
- exists(FunctionPointerFlowSource fptrfs, FunctionPointerCallExpr fptrce, Callable c |
- flowsFrom(fptrce, fptrfs, _, lastCall) and
- ret.getEnclosingCallable() = c and
- c = fptrfs.getCallable() and
- call = fptrce.getFunctionPointerCall()
- )
-}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
index 3014e33a63b..b29f12e279b 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
@@ -245,7 +245,7 @@ private module CallGraph {
* a library callable and `e` is a delegate argument.
*/
private predicate delegateCall(Call c, Expr e, boolean libraryDelegateCall) {
- c = any(DelegateCall dc | e = dc.getDelegateExpr()) and
+ c = any(DelegateCall dc | e = dc.getExpr()) and
libraryDelegateCall = false
or
c.getTarget().fromLibrary() and
diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
index eb04d14d015..941873d9afd 100644
--- a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
+++ b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
@@ -8,7 +8,6 @@ import Expr
import semmle.code.csharp.Callable
import semmle.code.csharp.dataflow.CallContext as CallContext
private import semmle.code.csharp.dataflow.internal.DelegateDataFlow
-private import semmle.code.csharp.dataflow.internal.FunctionPointerDataFlow
private import semmle.code.csharp.dispatch.Dispatch
private import dotnet
@@ -528,6 +527,39 @@ class MutatorOperatorCall extends OperatorCall {
predicate isPostfix() { mutator_invocation_mode(this, 2) }
}
+/**
+ * A function pointer or delegate call.
+ */
+abstract class DelegateLikeCall extends Call {
+ override Callable getTarget() { none() }
+
+ /**
+ * Gets a potential run-time target of this delegate or function pointer call in the given
+ * call context `cc`.
+ */
+ Callable getARuntimeTarget(CallContext::CallContext cc) { none() }
+
+ /**
+ * Gets the delegate or function pointer expression of this call. For example, the
+ * delegate expression of `X()` on line 5 is the access to the field `X` in
+ *
+ * ```csharp
+ * class A {
+ * Action X = () => { };
+ *
+ * void CallX() {
+ * X();
+ * }
+ * }
+ * ```
+ */
+ Expr getExpr() { result = this.getChild(-1) }
+
+ override Callable getARuntimeTarget() { result = getARuntimeTarget(_) }
+
+ override Expr getRuntimeArgument(int i) { result = getArgument(i) }
+}
+
/**
* A delegate call, for example `x()` on line 5 in
*
@@ -541,16 +573,14 @@ class MutatorOperatorCall extends OperatorCall {
* }
* ```
*/
-class DelegateCall extends Call, @delegate_invocation_expr {
- override Callable getTarget() { none() }
-
+class DelegateCall extends DelegateLikeCall, @delegate_invocation_expr {
/**
* Gets a potential run-time target of this delegate call in the given
* call context `cc`.
*/
- Callable getARuntimeTarget(CallContext::CallContext cc) {
+ override Callable getARuntimeTarget(CallContext::CallContext cc) {
exists(DelegateCallExpr call |
- this = call.getDelegateCall() and
+ this = call.getCall() and
result = call.getARuntimeTarget(cc)
)
or
@@ -568,7 +598,7 @@ class DelegateCall extends Call, @delegate_invocation_expr {
}
private AddEventSource getAnAddEventSource(Callable enclosingCallable) {
- this.getDelegateExpr().(EventAccess).getTarget() = result.getEvent() and
+ this.getExpr().(EventAccess).getTarget() = result.getEvent() and
enclosingCallable = result.getExpr().getEnclosingCallable()
}
@@ -580,25 +610,12 @@ class DelegateCall extends Call, @delegate_invocation_expr {
exists(Callable c | result = getAnAddEventSource(c) | c != this.getEnclosingCallable())
}
- override Callable getARuntimeTarget() { result = getARuntimeTarget(_) }
-
- override Expr getRuntimeArgument(int i) { result = getArgument(i) }
-
/**
- * Gets the delegate expression of this delegate call. For example, the
- * delegate expression of `X()` on line 5 is the access to the field `X` in
+ * DEPRECATED: use `getExpr` instead.
*
- * ```csharp
- * class A {
- * Action X = () => { };
- *
- * void CallX() {
- * X();
- * }
- * }
- * ```
+ * Gets the delegate expression of this call.
*/
- Expr getDelegateExpr() { result = this.getChild(-1) }
+ deprecated Expr getDelegateExpr() { result = this.getExpr() }
override string toString() { result = "delegate call" }
@@ -616,27 +633,18 @@ class DelegateCall extends Call, @delegate_invocation_expr {
* }
* ```
*/
-class FunctionPointerCall extends Call, @function_pointer_invocation_expr {
- override Callable getTarget() { none() }
-
+class FunctionPointerCall extends DelegateLikeCall, @function_pointer_invocation_expr {
/**
* Gets a potential run-time target of this function pointer call in the given
* call context `cc`.
*/
- Callable getARuntimeTarget(CallContext::CallContext cc) {
+ override Callable getARuntimeTarget(CallContext::CallContext cc) {
exists(FunctionPointerCallExpr call |
- this = call.getFunctionPointerCall() and
+ this = call.getCall() and
result = call.getARuntimeTarget(cc)
)
}
- override Callable getARuntimeTarget() { result = getARuntimeTarget(_) }
-
- override Expr getRuntimeArgument(int i) { result = getArgument(i) }
-
- /** Gets the function pointer expression of this call. */
- Expr getFunctionPointerExpr() { result = this.getChild(-1) }
-
override string toString() { result = "function pointer call" }
override string getAPrimaryQlClass() { result = "FunctionPointerCall" }
diff --git a/csharp/ql/src/semmle/code/csharp/metrics/Coupling.qll b/csharp/ql/src/semmle/code/csharp/metrics/Coupling.qll
index 272b56093f6..0da867e116a 100644
--- a/csharp/ql/src/semmle/code/csharp/metrics/Coupling.qll
+++ b/csharp/ql/src/semmle/code/csharp/metrics/Coupling.qll
@@ -78,7 +78,7 @@ predicate depends(ValueOrRefType t, ValueOrRefType u) {
or
exists(DelegateCall dc, DelegateType dt |
dc.getEnclosingCallable().getDeclaringType() = t and
- dc.getDelegateExpr().getType() = dt and
+ dc.getExpr().getType() = dt and
usesType(dt.getUnboundDeclaration(), u)
)
or
diff --git a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
index 68a91d02698..59c1c924a80 100644
--- a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
+++ b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.cs
@@ -116,12 +116,12 @@ class DelegateFlow
public unsafe void M17()
{
- M16(&M2, (i) => {}); // MISSING: a(0) in M2 is calling this lambda
+ M16(&M2, (i) => { });
}
public unsafe void M18()
{
delegate*, void> fnptr = &M2;
- fnptr((i) => {}); // MISSING: a(0) in M2 is calling this lambda
+ fnptr((i) => { });
}
}
diff --git a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
index 9b572b699d0..80ae0d0fe89 100644
--- a/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
+++ b/csharp/ql/test/library-tests/dataflow/delegates/DelegateFlow.expected
@@ -7,6 +7,8 @@ delegateCall
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:16:12:16:19 | (...) => ... | DelegateFlow.cs:16:12:16:19 | (...) => ... |
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:27:12:27:19 | (...) => ... | DelegateFlow.cs:22:12:22:12 | access to parameter a |
| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:98:9:98:37 | LocalFunction | DelegateFlow.cs:99:12:99:24 | delegate creation of type Action |
+| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:119:18:119:27 | (...) => ... | DelegateFlow.cs:114:15:114:15 | access to parameter a |
+| DelegateFlow.cs:9:9:9:12 | delegate call | DelegateFlow.cs:125:15:125:24 | (...) => ... | DelegateFlow.cs:125:15:125:24 | (...) => ... |
| DelegateFlow.cs:11:9:11:12 | delegate call | DelegateFlow.cs:10:13:10:20 | (...) => ... | file://:0:0:0:0 | |
| DelegateFlow.cs:33:9:33:13 | delegate call | DelegateFlow.cs:38:12:38:25 | (...) => ... | DelegateFlow.cs:38:12:38:25 | (...) => ... |
| DelegateFlow.cs:38:19:38:22 | delegate call | DelegateFlow.cs:5:10:5:11 | M1 | DelegateFlow.cs:33:12:33:12 | access to parameter a |
diff --git a/csharp/ql/test/library-tests/expressions/DelegateCall1.ql b/csharp/ql/test/library-tests/expressions/DelegateCall1.ql
index 430d2fdc43a..8d8f818a9e6 100644
--- a/csharp/ql/test/library-tests/expressions/DelegateCall1.ql
+++ b/csharp/ql/test/library-tests/expressions/DelegateCall1.ql
@@ -9,7 +9,7 @@ where
m.hasName("MainDelegateAndMethodAccesses") and
e.getEnclosingCallable() = m and
e.getNumberOfArguments() = 1 and
- e.getDelegateExpr() = a and
+ e.getExpr() = a and
a.getTarget().hasName("cd1") and
e.getArgument(0).getValue() = "-40"
select m, e, a
diff --git a/csharp/ql/test/library-tests/expressions/DelegateCall2.ql b/csharp/ql/test/library-tests/expressions/DelegateCall2.ql
index 4fb915f3086..fee6f1c703a 100644
--- a/csharp/ql/test/library-tests/expressions/DelegateCall2.ql
+++ b/csharp/ql/test/library-tests/expressions/DelegateCall2.ql
@@ -9,7 +9,7 @@ where
m.hasName("MainDelegateAndMethodAccesses") and
e.getEnclosingCallable() = m and
e.getNumberOfArguments() = 1 and
- e.getDelegateExpr() = a and
+ e.getExpr() = a and
a.getTarget().hasName("cd7") and
e.getArgument(0).(AddExpr).getRightOperand().(LocalVariableAccess).getTarget().hasName("x")
select m, e, a
diff --git a/csharp/ql/test/library-tests/expressions/DelegateCall3.ql b/csharp/ql/test/library-tests/expressions/DelegateCall3.ql
index 5702cad683a..b68b35dde45 100644
--- a/csharp/ql/test/library-tests/expressions/DelegateCall3.ql
+++ b/csharp/ql/test/library-tests/expressions/DelegateCall3.ql
@@ -8,7 +8,7 @@ from Method m, DelegateCall e, LocalVariableAccess a
where
m.hasName("MainDelegateAndMethodAccesses") and
e.getEnclosingCallable() = m and
- e.getDelegateExpr() = a and
+ e.getExpr() = a and
a.getTarget().hasName("cd7") and
a.getTarget().getType().(DelegateType).hasQualifiedName("Expressions.D")
select m, e, a
diff --git a/csharp/ql/test/library-tests/expressions/EventAccess3.ql b/csharp/ql/test/library-tests/expressions/EventAccess3.ql
index 741f973b221..56d5cb1beb9 100644
--- a/csharp/ql/test/library-tests/expressions/EventAccess3.ql
+++ b/csharp/ql/test/library-tests/expressions/EventAccess3.ql
@@ -11,7 +11,7 @@ where
e.getTarget().getName() = "Click" and
e.getTarget().getDeclaringType() = m.getDeclaringType() and
d.getEnclosingCallable() = m and
- d.getDelegateExpr() = e and
+ d.getExpr() = e and
d.getArgument(0) instanceof ThisAccess and
d.getArgument(1).(ParameterAccess).getTarget().hasName("e")
select m, d, e
From b8194bd1f806dc43e459d073690ead4d9cda2fa1 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Mon, 1 Feb 2021 14:38:59 +0100
Subject: [PATCH 070/429] Python: Add support for API graphs
Currently only supports the "use" side of things.
For the most part, this follows the corresponding implementation for
JavaScript. Major differences include:
- No `MkImportUse` nodes -- we just move directly from
`MkModuleImport` to its uses.
- Paths are no longer labelled by s-expressions, but rather by a
string that mirrors how you would access it in QL. This makes it very
easy to see how to access an API component -- simply look at its
`toString`!
This PR also extends `LocalSourceNode` to support looking up attribute
references and invocations of such nodes. This was again based on the
JavaScript equivalent (though without specific classes for
`InvokeNode` and the like, it's a bit more awkward to use).
---
python/ql/src/semmle/python/ApiGraphs.qll | 343 ++++++++++++++++++
.../dataflow/new/internal/DataFlowPublic.qll | 82 ++++-
2 files changed, 424 insertions(+), 1 deletion(-)
create mode 100644 python/ql/src/semmle/python/ApiGraphs.qll
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
new file mode 100644
index 00000000000..fbee343b623
--- /dev/null
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -0,0 +1,343 @@
+import python
+import semmle.python.dataflow.new.DataFlow
+
+module API {
+ class Node extends Impl::TApiNode {
+ /**
+ * Gets a data-flow node corresponding to a use of the API component represented by this node.
+ *
+ * For example, `import re; re.escape` is a use of the `escape` function from the
+ * `re` module, and `import re; re.escape("hello")` is a use of the return of that function.
+ *
+ * This includes indirect uses found via data flow, meaning that in
+ * ```python
+ * def f(x):
+ * pass
+ *
+ * f(obj.foo)
+ * ```
+ * both `obj.foo` and `x` are uses of the `foo` member from `obj`.
+ */
+ DataFlow::Node getAUse() {
+ exists(DataFlow::LocalSourceNode src | Impl::use(this, src) |
+ Impl::trackUseNode(src).flowsTo(result)
+ )
+ }
+
+ /**
+ * Gets an immediate use of the API component represented by this node.
+ *
+ * For example, `import re; re.escape` is a an immediate use of the `escape` member
+ * from the `re` module.
+ *
+ * Unlike `getAUse()`, this predicate only gets the immediate references, not the indirect uses
+ * found via data flow. This means that in `x = re.escape` only `re.escape` is a reference
+ * to the `escape` member of `re`, neither `x` nor any node that `x` flows to is a reference to
+ * this API component.
+ */
+ DataFlow::LocalSourceNode getAnImmediateUse() { Impl::use(this, result) }
+
+ /**
+ * Gets a call to the function represented by this API component.
+ */
+ DataFlow::Node getACall() { result = getReturn().getAnImmediateUse() }
+
+ /**
+ * Gets a node representing member `m` of this API component.
+ *
+ * For example, modules have an `exports` member representing their exports, and objects have
+ * their properties as members.
+ */
+ bindingset[m]
+ bindingset[result]
+ Node getMember(string m) { result = getASuccessor(Label::member(m)) }
+
+ /**
+ * Gets a node representing a member of this API component where the name of the member is
+ * not known statically.
+ */
+ Node getUnknownMember() { result = getASuccessor(Label::unknownMember()) }
+
+ /**
+ * Gets a node representing a member of this API component where the name of the member may
+ * or may not be known statically.
+ */
+ Node getAMember() {
+ result = getASuccessor(Label::member(_)) or
+ result = getUnknownMember()
+ }
+
+ /**
+ * Gets a node representing the result of the function represented by this node.
+ *
+ * This predicate may have multiple results when there are multiple invocations of this API component.
+ * Consider using `getACall()` if there is a need to distingiush between individual calls.
+ */
+ Node getReturn() { result = getASuccessor(Label::return()) }
+
+ /**
+ * Gets a string representation of the lexicographically least among all shortest access paths
+ * from the root to this node.
+ */
+ string getPath() { result = min(string p | p = getAPath(Impl::distanceFromRoot(this)) | p) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between this node and the other
+ * one, and that edge is labeled with `lbl`.
+ */
+ Node getASuccessor(string lbl) { Impl::edge(this, lbl, result) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between that other node and
+ * this one, and that edge is labeled with `lbl`
+ */
+ Node getAPredecessor(string lbl) { this = result.getASuccessor(lbl) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between this node and the other
+ * one.
+ */
+ Node getAPredecessor() { result = getAPredecessor(_) }
+
+ /**
+ * Gets a node such that there is an edge in the API graph between that other node and
+ * this one.
+ */
+ Node getASuccessor() { result = getASuccessor(_) }
+
+ /**
+ * Gets the data-flow node that gives rise to this node, if any.
+ */
+ DataFlow::Node getInducingNode() { this = Impl::MkUse(result) }
+
+ /**
+ * Holds if this node is located in file `path` between line `startline`, column `startcol`,
+ * and line `endline`, column `endcol`.
+ *
+ * For nodes that do not have a meaningful location, `path` is the empty string and all other
+ * parameters are zero.
+ */
+ predicate hasLocationInfo(string path, int startline, int startcol, int endline, int endcol) {
+ getInducingNode().hasLocationInfo(path, startline, startcol, endline, endcol)
+ or
+ not exists(getInducingNode()) and
+ path = "" and
+ startline = 0 and
+ startcol = 0 and
+ endline = 0 and
+ endcol = 0
+ }
+
+ /**
+ * Gets a textual representation of this node.
+ */
+ string toString() {
+ none() // defined in subclasses
+ }
+
+ /**
+ * Gets a path of the given `length` from the root to this node.
+ */
+ private string getAPath(int length) {
+ this instanceof Impl::MkRoot and
+ length = 0 and
+ result = ""
+ or
+ exists(Node pred, string lbl, string predpath |
+ Impl::edge(pred, lbl, this) and
+ lbl != "" and
+ predpath = pred.getAPath(length - 1) and
+ exists(string dot | if length = 1 then dot = "" else dot = "." |
+ result = predpath + dot + lbl and
+ // avoid producing strings longer than 1MB
+ result.length() < 1000 * 1000
+ )
+ ) and
+ length in [1 .. Impl::distanceFromRoot(this)]
+ }
+
+ /** Gets the shortest distance from the root to this node in the API graph. */
+ int getDepth() { result = Impl::distanceFromRoot(this) }
+ }
+
+ /** The root node of an API graph. */
+ class Root extends Node, Impl::MkRoot {
+ override string toString() { result = "root" }
+ }
+
+ /** A node corresponding to the use of an API component. */
+ class Use extends Node, Impl::TUse {
+ override string toString() {
+ exists(string type |
+ this = Impl::MkUse(_) and type = "Use "
+ or
+ this = Impl::MkModuleImport(_) and type = "ModuleImport "
+ |
+ result = type + getPath()
+ or
+ not exists(this.getPath()) and result = type + "with no path"
+ )
+ }
+ }
+
+ /** Gets the root node. */
+ Root root() { any() }
+
+ /** Gets a node corresponding to an import of module `m`. */
+ Node moduleImport(string m) { result = Impl::MkModuleImport(m) }
+
+ /**
+ * Provides the actual implementation of API graphs, cached for performance.
+ *
+ * Ideally, we'd like nodes to correspond to (global) access paths, with edge labels
+ * corresponding to extending the access path by one element. We also want to be able to map
+ * nodes to their definitions and uses in the data-flow graph, and this should happen modulo
+ * (inter-procedural) data flow.
+ *
+ * This, however, is not easy to implement, since access paths can have unbounded length
+ * and we need some way of recognizing cycles to avoid non-termination. Unfortunately, expressing
+ * a condition like "this node hasn't been involved in constructing any predecessor of
+ * this node in the API graph" without negative recursion is tricky.
+ *
+ * So instead most nodes are directly associated with a data-flow node, representing
+ * either a use or a definition of an API component. This ensures that we only have a finite
+ * number of nodes. However, we can now have multiple nodes with the same access
+ * path, which are essentially indistinguishable for a client of the API.
+ *
+ * On the other hand, a single node can have multiple access paths (which is, of
+ * course, unavoidable). We pick as canonical the alphabetically least access path with
+ * shortest length.
+ */
+ cached
+ private module Impl {
+ cached
+ newtype TApiNode =
+ /** The root of the API graph. */
+ MkRoot() or
+ /** An abstract representative for imports of the module called `name`. */
+ MkModuleImport(string name) { imports(_, name) } or
+ /** A use of an API member at the node `nd`. */
+ MkUse(DataFlow::Node nd) { use(_, _, nd) }
+
+ class TUse = MkModuleImport or MkUse;
+
+ /** Holds if `imp` is an import of a module named `name` */
+ private predicate imports(DataFlow::Node imp, string name) { imp = DataFlow::importNode(name) }
+
+ /**
+ * Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
+ * `lbl` in the API graph.
+ */
+ cached
+ predicate use(TApiNode base, string lbl, DataFlow::Node ref) {
+ exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode pred |
+ use(base, src) and pred = trackUseNode(src)
+ |
+ lbl = Label::memberFromRef(ref) and
+ ref = pred.getAnAttributeRead()
+ or
+ lbl = Label::return() and
+ ref = pred.getAnInvocation()
+ )
+ }
+
+ /**
+ * Holds if `ref` is a use of node `nd`.
+ */
+ cached
+ predicate use(TApiNode nd, DataFlow::Node ref) {
+ exists(string name |
+ nd = MkModuleImport(name) and
+ ref = DataFlow::importNode(name)
+ )
+ or
+ nd = MkUse(ref)
+ }
+
+ /**
+ * Gets a data-flow node to which `nd`, which is a use of an API-graph node, flows.
+ *
+ * The flow from `nd` to that node may be inter-procedural.
+ */
+ private DataFlow::LocalSourceNode trackUseNode(
+ DataFlow::LocalSourceNode src, DataFlow::TypeTracker t
+ ) {
+ t.start() and
+ use(_, src) and
+ result = src
+ or
+ // Due to bad performance when using `trackUseNode(t2, attr_name).track(t2, t)`
+ // we have inlined that code and forced a join
+ exists(DataFlow::StepSummary summary |
+ t = trackUseNode_first_join(src, result, summary).append(summary)
+ )
+ }
+
+ pragma[nomagic]
+ private DataFlow::TypeTracker trackUseNode_first_join(
+ DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode res, DataFlow::StepSummary summary
+ ) {
+ DataFlow::StepSummary::step(trackUseNode(src, result), res, summary)
+ }
+
+ cached
+ DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
+ result = trackUseNode(src, DataFlow::TypeTracker::end())
+ }
+
+ /**
+ * Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
+ */
+ cached
+ predicate edge(Node pred, string lbl, Node succ) {
+ /* There's an edge from the root node for each imported module. */
+ exists(string m |
+ pred = MkRoot() and
+ lbl = Label::mod(m)
+ |
+ succ = MkModuleImport(m)
+ )
+ or
+ /* Every node that is a use of an API component is itself added to the API graph. */
+ exists(DataFlow::LocalSourceNode ref |
+ use(pred, lbl, ref) and
+ succ = MkUse(ref)
+ )
+ }
+
+ /**
+ * Holds if there is an edge from `pred` to `succ` in the API graph.
+ */
+ private predicate edge(TApiNode pred, TApiNode succ) { edge(pred, _, succ) }
+
+ /** Gets the shortest distance from the root to `nd` in the API graph. */
+ cached
+ int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result)
+ }
+}
+
+private module Label {
+ /** Gets the edge label for the module `m`. */
+ bindingset[m]
+ bindingset[result]
+ string mod(string m) { result = "moduleImport(\"" + m + "\")" }
+
+ /** Gets the `member` edge label for member `m`. */
+ bindingset[m]
+ bindingset[result]
+ string member(string m) { result = "getMember(\"" + m + "\")" }
+
+ /** Gets the `member` edge label for the unknown member. */
+ string unknownMember() { result = "getUnknownMember()" }
+
+ /** Gets the `member` edge label for the given attribute reference. */
+ string memberFromRef(DataFlow::AttrRef pr) {
+ result = member(pr.getAttributeName())
+ or
+ not exists(pr.getAttributeName()) and
+ result = unknownMember()
+ }
+
+ /** Gets the `return` edge label. */
+ string return() { result = "getReturn()" }
+}
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
index cb807917419..f2f90723d77 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
@@ -447,7 +447,87 @@ class LocalSourceNode extends Node {
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
cached
- predicate flowsTo(Node nodeTo) { simpleLocalFlowStep*(this, nodeTo) }
+ predicate flowsTo(Node nodeTo) { Cached::hasLocalSource(nodeTo, this) }
+
+ /**
+ * Gets a reference (read or write) of attribute `attrName` on this node.
+ */
+ AttrRef getAnAttributeReference(string attrName) { Cached::namedAttrRef(this, attrName, result) }
+
+ /**
+ * Gets a read of attribute `attrName` on this node.
+ */
+ AttrRead getAnAttributeRead(string attrName) { result = getAnAttributeReference(attrName) }
+
+ /**
+ * Gets a reference (read or write) of any attribute on this node.
+ */
+ AttrRef getAnAttributeReference() {
+ Cached::namedAttrRef(this, _, result)
+ or
+ Cached::dynamicAttrRef(this, result)
+ }
+
+ /**
+ * Gets a read of any attribute on this node.
+ */
+ AttrRead getAnAttributeRead() { result = getAnAttributeReference() }
+
+ /**
+ * Gets an invocation (with our without `new`) of this node.
+ */
+ Node getAnInvocation() { Cached::invocation(this, result) }
+}
+
+cached
+private module Cached {
+ /**
+ * Holds if `source` is a `LocalSourceNode` that can reach `sink` via local flow steps.
+ *
+ * The slightly backwards parametering ordering is to force correct indexing.
+ */
+ cached
+ predicate hasLocalSource(Node sink, Node source) {
+ // Declaring `source` to be a `SourceNode` currently causes a redundant check in the
+ // recursive case, so instead we check it explicitly here.
+ source = sink and
+ source instanceof LocalSourceNode
+ or
+ exists(Node mid |
+ hasLocalSource(mid, source) and
+ simpleLocalFlowStep(mid, sink)
+ )
+ }
+
+ /**
+ * Holds if `base` flows to the base of `ref` and `ref` has attribute name `attr`.
+ */
+ cached
+ predicate namedAttrRef(LocalSourceNode base, string attr, AttrRef ref) {
+ hasLocalSource(ref.getObject(), base) and
+ ref.getAttributeName() = attr
+ }
+
+ /**
+ * Holds if `base` flows to the base of `ref` and `ref` has no known attribute name.
+ */
+ cached
+ predicate dynamicAttrRef(LocalSourceNode base, AttrRef ref) {
+ hasLocalSource(ref.getObject(), base) and
+ not exists(ref.getAttributeName())
+ }
+
+ /**
+ * Holds if `func` flows to the callee of `invoke`.
+ */
+ cached
+ predicate invocation(LocalSourceNode func, Node invoke) {
+ exists(CfgNode n, CallNode call |
+ invoke.asCfgNode() = call and n.asCfgNode() = call.getFunction()
+ |
+ hasLocalSource(n, func)
+ )
+ }
}
/**
From 384d0212b11a49dfb73ffe06e6738c5f8ed8de81 Mon Sep 17 00:00:00 2001
From: yoff
Date: Mon, 1 Feb 2021 16:41:43 +0100
Subject: [PATCH 071/429] Update
python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
Co-authored-by: Taus
---
.../src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index 694243e0e81..2ab91694217 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -1260,7 +1260,7 @@ module IterableUnpacking {
Expr getSource() { result = source }
}
- /** The LHS of an assignemnt, it also records the assigned value. */
+ /** The LHS of an assignment, it also records the assigned value. */
class AssignmentTarget extends ControlFlowNode {
Expr value;
From cd7b013a0c084f624952796659566f4be2e300ed Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Mon, 1 Feb 2021 18:57:25 +0100
Subject: [PATCH 072/429] Python: Add missing documentation
---
python/ql/src/semmle/python/ApiGraphs.qll | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index fbee343b623..2dc62ed30a6 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -1,7 +1,22 @@
+/**
+ * Provides an implementation of _API graphs_, which are an abstract representation of the API
+ * surface used and/or defined by a code base.
+ *
+ * The nodes of the API graph represent definitions and uses of API components. The edges are
+ * directed and labeled; they specify how the components represented by nodes relate to each other.
+ */
+
import python
import semmle.python.dataflow.new.DataFlow
+/**
+ * Provides classes and predicates for working with APIs used in a database.
+ */
module API {
+ /**
+ * An abstract representation of a definition or use of an API component such as a function
+ * exported by a Python package, or its result.
+ */
class Node extends Impl::TApiNode {
/**
* Gets a data-flow node corresponding to a use of the API component represented by this node.
From 74fd2c1c3880ce5a5cdeb46d40a1bb0254791697 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Mon, 1 Feb 2021 14:57:35 +0100
Subject: [PATCH 073/429] C#: Move uncertain-read logic into shared SSA
implementation
---
.../code/csharp/dataflow/internal/SsaImpl.qll | 64 ++-----------
.../dataflow/internal/SsaImplCommon.qll | 92 ++++++++++++++++---
.../dataflow/internal/SsaImplSpecific.qll | 2 +-
3 files changed, 90 insertions(+), 68 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
index d51122ef568..5e25c1ea0a4 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
@@ -954,10 +954,12 @@ private predicate variableReadPseudo(ControlFlow::BasicBlock bb, int i, Ssa::Sou
*
* This includes implicit reads via calls.
*/
-predicate variableRead(ControlFlow::BasicBlock bb, int i, Ssa::SourceVariable v) {
- variableReadActual(bb, i, v)
+predicate variableRead(ControlFlow::BasicBlock bb, int i, Ssa::SourceVariable v, boolean certain) {
+ variableReadActual(bb, i, v) and
+ certain = true
or
- variableReadPseudo(bb, i, v)
+ variableReadPseudo(bb, i, v) and
+ certain = false
}
cached
@@ -1107,26 +1109,6 @@ private module Cached {
ssaDefReachesEndOfBlock(bb, def, _)
}
- private predicate adjacentDefReaches(
- Definition def, ControlFlow::BasicBlock bb1, int i1, ControlFlow::BasicBlock bb2, int i2
- ) {
- adjacentDefRead(def, bb1, i1, bb2, i2)
- or
- exists(ControlFlow::BasicBlock bb3, int i3 |
- adjacentDefReaches(def, bb1, i1, bb3, i3) and
- variableReadPseudo(bb3, i3, _) and
- adjacentDefRead(def, bb3, i3, bb2, i2)
- )
- }
-
- pragma[noinline]
- private predicate adjacentDefActualRead(
- Definition def, ControlFlow::BasicBlock bb1, int i1, ControlFlow::BasicBlock bb2, int i2
- ) {
- adjacentDefReaches(def, bb1, i1, bb2, i2) and
- variableReadActual(bb2, i2, _)
- }
-
cached
AssignableRead getAReadAtNode(Definition def, ControlFlow::Node cfn) {
exists(Ssa::SourceVariable v, ControlFlow::BasicBlock bb, int i |
@@ -1145,7 +1127,7 @@ private module Cached {
predicate firstReadSameVar(Definition def, ControlFlow::Node cfn) {
exists(ControlFlow::BasicBlock bb1, int i1, ControlFlow::BasicBlock bb2, int i2 |
def.definesAt(_, bb1, i1) and
- adjacentDefActualRead(def, bb1, i1, bb2, i2) and
+ adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and
cfn = bb2.getNode(i2)
)
}
@@ -1160,48 +1142,20 @@ private module Cached {
exists(ControlFlow::BasicBlock bb1, int i1, ControlFlow::BasicBlock bb2, int i2 |
cfn1 = bb1.getNode(i1) and
variableReadActual(bb1, i1, _) and
- adjacentDefActualRead(def, bb1, i1, bb2, i2) and
+ adjacentDefNoUncertainReads(def, bb1, i1, bb2, i2) and
cfn2 = bb2.getNode(i2)
)
}
- private predicate adjacentDefPseudoRead(
- Definition def, ControlFlow::BasicBlock bb1, int i1, ControlFlow::BasicBlock bb2, int i2
- ) {
- adjacentDefReaches(def, bb1, i1, bb2, i2) and
- variableReadPseudo(bb2, i2, _)
- }
-
- private predicate reachesLastRefRedef(
- Definition def, ControlFlow::BasicBlock bb, int i, Definition next
- ) {
- lastRefRedef(def, bb, i, next)
- or
- exists(ControlFlow::BasicBlock bb0, int i0 |
- reachesLastRefRedef(def, bb0, i0, next) and
- adjacentDefPseudoRead(def, bb, i, bb0, i0)
- )
- }
-
cached
predicate lastRefBeforeRedef(Definition def, ControlFlow::BasicBlock bb, int i, Definition next) {
- reachesLastRefRedef(def, bb, i, next) and
- not variableReadPseudo(bb, i, def.getSourceVariable())
- }
-
- private predicate reachesLastRef(Definition def, ControlFlow::BasicBlock bb, int i) {
- lastRef(def, bb, i)
- or
- exists(ControlFlow::BasicBlock bb0, int i0 |
- reachesLastRef(def, bb0, i0) and
- adjacentDefPseudoRead(def, bb, i, bb0, i0)
- )
+ lastRefRedefNoUncertainReads(def, bb, i, next)
}
cached
predicate lastReadSameVar(Definition def, ControlFlow::Node cfn) {
exists(ControlFlow::BasicBlock bb, int i |
- reachesLastRef(def, bb, i) and
+ lastRefNoUncertainReads(def, bb, i) and
variableReadActual(bb, i, _) and
cfn = bb.getNode(i)
)
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
index f4cb01f7a97..2257994c2e0 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
@@ -17,18 +17,18 @@ private module Liveness {
* (certain or uncertain) writes.
*/
private newtype TRefKind =
- Read() or
- Write(boolean certain) { certain = true or certain = false }
+ Read(boolean certain) { certain in [false, true] } or
+ Write(boolean certain) { certain in [false, true] }
private class RefKind extends TRefKind {
string toString() {
- this = Read() and result = "read"
+ exists(boolean certain | this = Read(certain) and result = "read (" + certain + ")")
or
exists(boolean certain | this = Write(certain) and result = "write (" + certain + ")")
}
int getOrder() {
- this = Read() and
+ this = Read(_) and
result = 0
or
this = Write(_) and
@@ -40,7 +40,7 @@ private module Liveness {
* Holds if the `i`th node of basic block `bb` is a reference to `v` of kind `k`.
*/
private predicate ref(BasicBlock bb, int i, SourceVariable v, RefKind k) {
- variableRead(bb, i, v) and k = Read()
+ exists(boolean certain | variableRead(bb, i, v, certain) | k = Read(certain))
or
exists(boolean certain | variableWrite(bb, i, v, certain) | k = Write(certain))
}
@@ -95,7 +95,7 @@ private module Liveness {
*/
predicate liveAtEntry(BasicBlock bb, SourceVariable v) {
// The first read or certain write to `v` inside `bb` is a read
- refRank(bb, _, v, Read()) = firstReadOrCertainWrite(bb, v)
+ refRank(bb, _, v, Read(_)) = firstReadOrCertainWrite(bb, v)
or
// There is no certain write to `v` inside `bb`, but `v` is live at entry
// to a successor basic block of `bb`
@@ -120,7 +120,7 @@ private module Liveness {
liveAtExit(bb, v)
or
ref(bb, i, v, kind) and
- kind = Read()
+ kind = Read(_)
or
exists(RefKind nextKind |
liveAtRank(bb, _, v, rnk + 1) and
@@ -237,7 +237,7 @@ private module SsaDefReaches {
* Unlike `Liveness::ref`, this includes `phi` nodes.
*/
predicate ssaRef(BasicBlock bb, int i, SourceVariable v, SsaRefKind k) {
- variableRead(bb, i, v) and
+ variableRead(bb, i, v, _) and
k = SsaRead()
or
exists(Definition def | def.definesAt(v, bb, i)) and
@@ -304,7 +304,7 @@ private module SsaDefReaches {
exists(int rnk |
ssaDefReachesRank(bb, def, rnk, v) and
rnk = ssaRefRank(bb, i, v, SsaRead()) and
- variableRead(bb, i, v)
+ variableRead(bb, i, v, _)
)
}
@@ -368,7 +368,7 @@ private module SsaDefReaches {
predicate defAdjacentRead(Definition def, BasicBlock bb1, BasicBlock bb2, int i2) {
varBlockReaches(def, bb1, bb2) and
ssaRefRank(bb2, i2, def.getSourceVariable(), SsaRead()) = 1 and
- variableRead(bb2, i2, _)
+ variableRead(bb2, i2, _, _)
}
}
@@ -394,6 +394,7 @@ private predicate ssaDefReachesEndOfBlockRec(BasicBlock bb, Definition def, Sour
* block `bb`, at which point it is still live, without crossing another
* SSA definition of `v`.
*/
+pragma[nomagic]
predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable v) {
exists(int last | last = maxSsaRefRank(bb, v) |
ssaDefReachesRank(bb, def, last, v) and
@@ -412,10 +413,11 @@ predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable
* basic block `bb`, without crossing another SSA definition of `v`. The read
* is of kind `rk`.
*/
+pragma[nomagic]
predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int i) {
ssaDefReachesReadWithinBlock(v, def, bb, i)
or
- variableRead(bb, i, v) and
+ variableRead(bb, i, v, _) and
ssaDefReachesEndOfBlock(getABasicBlockPredecessor(bb), def, v) and
not ssaDefReachesReadWithinBlock(v, _, bb, i)
}
@@ -427,11 +429,12 @@ predicate ssaDefReachesRead(SourceVariable v, Definition def, BasicBlock bb, int
* or a write), `def` is read at index `i2` in basic block `bb2`, and there is a
* path between them without any read of `def`.
*/
+pragma[nomagic]
predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
exists(int rnk |
rnk = ssaDefRank(def, _, bb1, i1, _) and
rnk + 1 = ssaDefRank(def, _, bb1, i2, SsaRead()) and
- variableRead(bb1, i2, _) and
+ variableRead(bb1, i2, _, _) and
bb2 = bb1
)
or
@@ -439,6 +442,30 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
defAdjacentRead(def, bb1, bb2, i2)
}
+private predicate adjacentDefReachesRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
+) {
+ adjacentDefRead(def, bb1, i1, bb2, i2) and
+ not variableRead(bb1, i1, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb3, int i3 |
+ adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
+ variableRead(bb3, i3, _, false) and
+ adjacentDefRead(def, bb3, i3, bb2, i2)
+ )
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `adjacentDefRead`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate adjacentDefNoUncertainReads(Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2) {
+ adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
+ variableRead(bb2, i2, _, true)
+}
+
/**
* NB: If this predicate is exposed, it should be cached.
*
@@ -446,6 +473,7 @@ predicate adjacentDefRead(Definition def, BasicBlock bb1, int i1, BasicBlock bb2
* `def`. The reference is last because it can reach another write `next`,
* without passing through another read or write.
*/
+pragma[nomagic]
predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
exists(int rnk, SourceVariable v, int j | rnk = ssaDefRank(def, v, bb, i, _) |
// Next reference to `v` inside `bb` is a write
@@ -462,6 +490,29 @@ predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
)
}
+private predicate adjacentDefReachesUncertainRead(
+ Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
+) {
+ adjacentDefReachesRead(def, bb1, i1, bb2, i2) and
+ variableRead(bb2, i2, _, false)
+}
+
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `lastRefRedef`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate lastRefRedefNoUncertainReads(Definition def, BasicBlock bb, int i, Definition next) {
+ lastRefRedef(def, bb, i, next) and
+ not variableRead(bb, i, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb0, int i0 |
+ lastRefRedef(def, bb0, i0, next) and
+ adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
+ )
+}
+
/**
* NB: If this predicate is exposed, it should be cached.
*
@@ -472,6 +523,7 @@ predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
* SSA definition for the underlying source variable, without passing through
* another read.
*/
+pragma[nomagic]
predicate lastRef(Definition def, BasicBlock bb, int i) {
lastRefRedef(def, bb, i, _)
or
@@ -487,6 +539,22 @@ predicate lastRef(Definition def, BasicBlock bb, int i) {
)
}
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Same as `lastRefRedef`, but ignores uncertain reads.
+ */
+pragma[nomagic]
+predicate lastRefNoUncertainReads(Definition def, BasicBlock bb, int i) {
+ lastRef(def, bb, i) and
+ not variableRead(bb, i, def.getSourceVariable(), false)
+ or
+ exists(BasicBlock bb0, int i0 |
+ lastRef(def, bb0, i0) and
+ adjacentDefReachesUncertainRead(def, bb, i, bb0, i0)
+ )
+}
+
/** A static single assignment (SSA) definition. */
class Definition extends TDefinition {
/** Gets the source variable underlying this SSA definition. */
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll
index c99ad7d8fb3..2091b73c388 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplSpecific.qll
@@ -16,4 +16,4 @@ class SourceVariable = SsaImpl::TSourceVariable;
predicate variableWrite = SsaImpl::variableWrite/4;
-predicate variableRead = SsaImpl::variableRead/3;
+predicate variableRead = SsaImpl::variableRead/4;
From b19fd7bb7278302bfd707947c96ed16c29896746 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Tue, 2 Feb 2021 10:42:32 +0100
Subject: [PATCH 074/429] C#: Only cache `TDefinition` in the shared SSA
implementation
---
.../src/semmle/code/csharp/dataflow/SSA.qll | 33 +++++--
.../code/csharp/dataflow/internal/SsaImpl.qll | 12 ++-
.../dataflow/internal/SsaImplCommon.qll | 97 ++++++++-----------
3 files changed, 77 insertions(+), 65 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll b/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll
index 0bd7e848639..44307d68e1f 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/SSA.qll
@@ -384,9 +384,18 @@ module Ssa {
result.getAControlFlowNode() = cfn
}
+ /**
+ * Gets an SSA definition whose value can flow to this one in one step. This
+ * includes inputs to phi nodes and the prior definitions of uncertain writes.
+ */
+ private Definition getAPhiInputOrPriorDefinition() {
+ result = this.(PhiNode).getAnInput() or
+ result = this.(UncertainDefinition).getPriorDefinition()
+ }
+
/**
* Gets a definition that ultimately defines this SSA definition and is
- * not itself a pseudo node. Example:
+ * not itself a phi node. Example:
*
* ```csharp
* int Field;
@@ -413,8 +422,9 @@ module Ssa {
* definition on line 4, the explicit definition on line 7, and the implicit
* definition on line 9.
*/
- final override Definition getAnUltimateDefinition() {
- result = SsaImpl::Definition.super.getAnUltimateDefinition()
+ final Definition getAnUltimateDefinition() {
+ result = this.getAPhiInputOrPriorDefinition*() and
+ not result instanceof PhiNode
}
/**
@@ -425,7 +435,7 @@ module Ssa {
/**
* Gets the syntax element associated with this SSA definition, if any.
* This is either an expression, for example `x = 0`, a parameter, or a
- * callable. Pseudo nodes have no associated syntax element.
+ * callable. Phi nodes have no associated syntax element.
*/
Element getElement() { result = this.getControlFlowNode().getElement() }
@@ -681,7 +691,12 @@ module Ssa {
* definition on line 4, the explicit definition on line 7, and the implicit
* call definition on line 9 as inputs.
*/
- final override Definition getAnInput() { result = SsaImpl::PhiNode.super.getAnInput() }
+ final Definition getAnInput() { this.hasInputFromBlock(result, _) }
+
+ /** Holds if `inp` is an input to this phi node along the edge originating in `bb`. */
+ predicate hasInputFromBlock(Definition inp, ControlFlow::BasicBlock bb) {
+ inp = SsaImpl::phiHasInputFromBlock(this, bb)
+ }
override string toString() {
result = getToStringPrefix(this) + "SSA phi(" + getSourceVariable() + ")"
@@ -704,5 +719,11 @@ module Ssa {
* need not be certain), an implicit non-local update via a call, or an
* uncertain update of the qualifier.
*/
- class UncertainDefinition extends Definition, SsaImpl::UncertainWriteDefinition { }
+ class UncertainDefinition extends Definition, SsaImpl::UncertainWriteDefinition {
+ /**
+ * Gets the immediately preceding definition. Since this update is uncertain,
+ * the value from the preceding definition might still be valid.
+ */
+ Definition getPriorDefinition() { result = SsaImpl::uncertainWriteDefinitionInput(this) }
+ }
}
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
index 5e25c1ea0a4..eff99d351c4 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImpl.qll
@@ -1075,7 +1075,7 @@ private module Cached {
Ssa::ExplicitDefinition def, Ssa::ImplicitEntryDefinition edef,
ControlFlow::Nodes::ElementNode c, boolean additionalCalls
) {
- exists(Ssa::SourceVariable v, Definition def0, ControlFlow::BasicBlock bb, int i |
+ exists(Ssa::SourceVariable v, Ssa::Definition def0, ControlFlow::BasicBlock bb, int i |
v = def.getSourceVariable() and
capturedReadIn(_, _, v, edef.getSourceVariable(), c, additionalCalls) and
def = def0.getAnUltimateDefinition() and
@@ -1109,6 +1109,11 @@ private module Cached {
ssaDefReachesEndOfBlock(bb, def, _)
}
+ cached
+ Definition phiHasInputFromBlock(PhiNode phi, ControlFlow::BasicBlock bb) {
+ phiHasInputFromBlock(phi, result, bb)
+ }
+
cached
AssignableRead getAReadAtNode(Definition def, ControlFlow::Node cfn) {
exists(Ssa::SourceVariable v, ControlFlow::BasicBlock bb, int i |
@@ -1161,6 +1166,11 @@ private module Cached {
)
}
+ cached
+ Definition uncertainWriteDefinitionInput(UncertainWriteDefinition def) {
+ uncertainWriteDefinitionInput(def, result)
+ }
+
cached
predicate isLiveOutRefParameterDefinition(Ssa::Definition def, Parameter p) {
p.isOutOrRef() and
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
index 2257994c2e0..be01c05b8fa 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/SsaImplCommon.qll
@@ -174,35 +174,16 @@ private predicate inDefDominanceFrontier(BasicBlock bb, SourceVariable v) {
}
cached
-private module Cached {
- cached
- newtype TDefinition =
- TWriteDef(SourceVariable v, BasicBlock bb, int i) {
- variableWrite(bb, i, v, _) and
- liveAfterWrite(bb, i, v)
- } or
- TPhiNode(SourceVariable v, BasicBlock bb) {
- inDefDominanceFrontier(bb, v) and
- liveAtEntry(bb, v)
- }
-
- cached
- predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
- lastRefRedef(inp, _, _, def)
+newtype TDefinition =
+ TWriteDef(SourceVariable v, BasicBlock bb, int i) {
+ variableWrite(bb, i, v, _) and
+ liveAfterWrite(bb, i, v)
+ } or
+ TPhiNode(SourceVariable v, BasicBlock bb) {
+ inDefDominanceFrontier(bb, v) and
+ liveAtEntry(bb, v)
}
- cached
- predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) {
- exists(SourceVariable v, BasicBlock bbDef |
- phi.definesAt(v, bbDef, _) and
- getABasicBlockPredecessor(bbDef) = bb and
- ssaDefReachesEndOfBlock(bb, inp, v)
- )
- }
-}
-
-import Cached
-
private module SsaDefReaches {
newtype TSsaRefKind =
SsaRead() or
@@ -406,6 +387,20 @@ predicate ssaDefReachesEndOfBlock(BasicBlock bb, Definition def, SourceVariable
not ssaRef(bb, _, v, SsaDef())
}
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `inp` is an input to the phi node `phi` along the edge originating in `bb`.
+ */
+pragma[nomagic]
+predicate phiHasInputFromBlock(PhiNode phi, Definition inp, BasicBlock bb) {
+ exists(SourceVariable v, BasicBlock bbDef |
+ phi.definesAt(v, bbDef, _) and
+ getABasicBlockPredecessor(bbDef) = bb and
+ ssaDefReachesEndOfBlock(bb, inp, v)
+ )
+}
+
/**
* NB: If this predicate is exposed, it should be cached.
*
@@ -446,7 +441,11 @@ private predicate adjacentDefReachesRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
) {
adjacentDefRead(def, bb1, i1, bb2, i2) and
- not variableRead(bb1, i1, def.getSourceVariable(), false)
+ exists(SourceVariable v | v = def.getSourceVariable() |
+ ssaRef(bb1, i1, v, SsaDef())
+ or
+ variableRead(bb1, i1, v, true)
+ )
or
exists(BasicBlock bb3, int i3 |
adjacentDefReachesRead(def, bb1, i1, bb3, i3) and
@@ -490,6 +489,18 @@ predicate lastRefRedef(Definition def, BasicBlock bb, int i, Definition next) {
)
}
+/**
+ * NB: If this predicate is exposed, it should be cached.
+ *
+ * Holds if `inp` is an immediately preceding definition of uncertain definition
+ * `def`. Since `def` is uncertain, the value from the preceding definition might
+ * still be valid.
+ */
+pragma[nomagic]
+predicate uncertainWriteDefinitionInput(UncertainWriteDefinition def, Definition inp) {
+ lastRefRedef(inp, _, _, def)
+}
+
private predicate adjacentDefReachesUncertainRead(
Definition def, BasicBlock bb1, int i1, BasicBlock bb2, int i2
) {
@@ -574,24 +585,6 @@ class Definition extends TDefinition {
/** Gets the basic block to which this SSA definition belongs. */
final BasicBlock getBasicBlock() { this.definesAt(_, result, _) }
- /**
- * Gets an SSA definition whose value can flow to this one in one step. This
- * includes inputs to phi nodes and the prior definitions of uncertain writes.
- */
- private Definition getAPseudoInputOrPriorDefinition() {
- result = this.(PhiNode).getAnInput() or
- result = this.(UncertainWriteDefinition).getPriorDefinition()
- }
-
- /**
- * Gets a definition that ultimately defines this SSA definition and is
- * not itself a phi node.
- */
- Definition getAnUltimateDefinition() {
- result = this.getAPseudoInputOrPriorDefinition*() and
- not result instanceof PhiNode
- }
-
/** Gets a textual representation of this SSA definition. */
string toString() { none() }
}
@@ -609,12 +602,6 @@ class WriteDefinition extends Definition, TWriteDef {
/** A phi node. */
class PhiNode extends Definition, TPhiNode {
- /** Gets an input of this phi node. */
- Definition getAnInput() { this.hasInputFromBlock(result, _) }
-
- /** Holds if `inp` is an input to the phi node along the edge originating in `bb`. */
- predicate hasInputFromBlock(Definition inp, BasicBlock bb) { phiHasInputFromBlock(this, inp, bb) }
-
override string toString() { result = "Phi" }
}
@@ -629,10 +616,4 @@ class UncertainWriteDefinition extends WriteDefinition {
variableWrite(bb, i, v, false)
)
}
-
- /**
- * Gets the immediately preceding definition. Since this update is uncertain,
- * the value from the preceding definition might still be valid.
- */
- Definition getPriorDefinition() { uncertainWriteDefinitionInput(this, result) }
}
From d046e39a826b2fcb247a1d615811549d0e7cb2d8 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Tue, 2 Feb 2021 12:04:24 +0100
Subject: [PATCH 075/429] Python: Fix tornado inline expectations in tests
After merge commit
---
.../library-tests/frameworks/tornado/response_test.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/python/ql/test/experimental/library-tests/frameworks/tornado/response_test.py b/python/ql/test/experimental/library-tests/frameworks/tornado/response_test.py
index d8af7f3895a..42cd37c2118 100644
--- a/python/ql/test/experimental/library-tests/frameworks/tornado/response_test.py
+++ b/python/ql/test/experimental/library-tests/frameworks/tornado/response_test.py
@@ -33,7 +33,8 @@ class ExplicitContentType(tornado.web.RequestHandler):
class ExampleRedirect(tornado.web.RequestHandler):
def get(self): # $ requestHandler
- self.redirect("http://example.com") # TODO: Model redirect
+ url = "http://example.com"
+ self.redirect(url) # $ HttpRedirectResponse HttpResponse redirectLocation=url
class ExampleConnectionWrite(tornado.web.RequestHandler):
From 64f0dfb17452a8a0842410ad0fd7c1aa30541b65 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 2 Feb 2021 14:21:26 +0100
Subject: [PATCH 076/429] Fix code review findings
---
.../code/csharp/dataflow/CallContext.qll | 8 +---
.../dataflow/internal/DataFlowDispatch.qll | 10 ++---
.../dataflow/internal/DataFlowPrivate.qll | 2 +-
.../dataflow/internal/DelegateDataFlow.qll | 41 +++++++------------
.../ql/src/semmle/code/csharp/exprs/Call.qll | 27 +++++-------
5 files changed, 32 insertions(+), 56 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll b/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll
index aa501a58e0d..8be2fc0939f 100755
--- a/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/CallContext.qll
@@ -82,12 +82,8 @@ class DelegateLikeCallArgumentCallContext extends ArgumentCallContext {
/** An argument of a delegate call. */
class DelegateCallArgumentCallContext extends DelegateLikeCallArgumentCallContext,
- TArgDelegateCallContext {
- DelegateCallArgumentCallContext() { this = TArgDelegateCallContext(dc, arg) }
-}
+ TArgDelegateCallContext { }
/** An argument of a function pointer call. */
class FunctionPointerCallArgumentCallContext extends DelegateLikeCallArgumentCallContext,
- TArgFunctionPointerCallContext {
- FunctionPointerCallArgumentCallContext() { this = TArgFunctionPointerCallContext(dc, arg) }
-}
+ TArgFunctionPointerCallContext { }
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 2337f2ec349..c68ba38e0b5 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowDispatch.qll
@@ -101,7 +101,7 @@ private module Cached {
TNonDelegateCall(ControlFlow::Nodes::ElementNode cfn, DispatchCall dc) {
cfn.getElement() = dc.getCall()
} or
- TExplicitDelegateCall(ControlFlow::Nodes::ElementNode cfn, DelegateCall dc) {
+ TExplicitDelegateLikeCall(ControlFlow::Nodes::ElementNode cfn, DelegateLikeCall dc) {
cfn.getElement() = dc
} or
TTransitiveCapturedCall(ControlFlow::Nodes::ElementNode cfn, Callable target) {
@@ -308,12 +308,12 @@ abstract class DelegateDataFlowCall extends DataFlowCall {
override DataFlowCallable getARuntimeTarget() { result = this.getARuntimeTarget(_) }
}
-/** An explicit delegate call relevant for data flow. */
-class ExplicitDelegateDataFlowCall extends DelegateDataFlowCall, TExplicitDelegateCall {
+/** An explicit delegate or function pointer call relevant for data flow. */
+class ExplicitDelegateLikeDataFlowCall extends DelegateDataFlowCall, TExplicitDelegateLikeCall {
private ControlFlow::Nodes::ElementNode cfn;
- private DelegateCall dc;
+ private DelegateLikeCall dc;
- ExplicitDelegateDataFlowCall() { this = TExplicitDelegateCall(cfn, dc) }
+ ExplicitDelegateLikeDataFlowCall() { this = TExplicitDelegateLikeCall(cfn, dc) }
override DataFlowCallable getARuntimeTarget(CallContext::CallContext cc) {
result = getCallableForDataFlow(dc.getARuntimeTarget(cc))
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
index 5446323543a..64452768185 100644
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowPrivate.qll
@@ -1394,7 +1394,7 @@ private module OutNodes {
private DataFlowCall csharpCall(Expr e, ControlFlow::Node cfn) {
e = any(DispatchCall dc | result = TNonDelegateCall(cfn, dc)).getCall() or
- result = TExplicitDelegateCall(cfn, e)
+ result = TExplicitDelegateLikeCall(cfn, e)
}
/** A valid return type for a method that uses `yield return`. */
diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll
index af70cb19654..e6b15b95d7f 100755
--- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll
+++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DelegateDataFlow.qll
@@ -15,9 +15,9 @@ private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.frameworks.system.linq.Expressions
/** A source of flow for a delegate or function pointer expression. */
-private class DelegateLikeFlowSource extends DataFlow::ExprNode {
+abstract private class DelegateLikeFlowSource extends DataFlow::ExprNode {
/** Gets the callable that is referenced in this delegate or function pointer flow source. */
- Callable getCallable() { none() }
+ abstract Callable getCallable();
}
/** A source of flow for a delegate expression. */
@@ -41,10 +41,13 @@ private class FunctionPointerFlowSource extends DelegateLikeFlowSource {
Callable c;
FunctionPointerFlowSource() {
- this.getExpr() =
- any(Expr e |
- c = e.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
- )
+ c =
+ this.getExpr()
+ .(AddressOfExpr)
+ .getOperand()
+ .(CallableAccess)
+ .getTarget()
+ .getUnboundDeclaration()
}
/** Gets the callable that is referenced in this function pointer flow source. */
@@ -115,28 +118,12 @@ abstract private class DelegateLikeFlowSink extends DataFlow::Node {
/** A delegate or function pointer call expression. */
class DelegateLikeCallExpr extends DelegateLikeFlowSink, DataFlow::ExprNode {
+ DelegateLikeCall dc;
+
+ DelegateLikeCallExpr() { this.getExpr() = dc.getExpr() }
+
/** Gets the delegate or function pointer call that this expression belongs to. */
- DelegateLikeCall getCall() { none() }
-}
-
-/** A delegate call expression. */
-class DelegateCallExpr extends DelegateLikeCallExpr {
- DelegateCall dc;
-
- DelegateCallExpr() { this.getExpr() = dc.getExpr() }
-
- /** Gets the delegate call that this expression belongs to. */
- override DelegateCall getCall() { result = dc }
-}
-
-/** A function pointer call expression. */
-class FunctionPointerCallExpr extends DelegateLikeCallExpr {
- FunctionPointerCall fptrc;
-
- FunctionPointerCallExpr() { this.getExpr() = fptrc.getExpr() }
-
- /** Gets the function pointer call that this expression belongs to. */
- override FunctionPointerCall getCall() { result = fptrc }
+ DelegateLikeCall getCall() { result = dc }
}
/** A parameter of delegate type belonging to a callable with a flow summary. */
diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
index 941873d9afd..078ecd1a52a 100644
--- a/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
+++ b/csharp/ql/src/semmle/code/csharp/exprs/Call.qll
@@ -527,17 +527,24 @@ class MutatorOperatorCall extends OperatorCall {
predicate isPostfix() { mutator_invocation_mode(this, 2) }
}
+private class DelegateLikeCall_ = @delegate_invocation_expr or @function_pointer_invocation_expr;
+
/**
* A function pointer or delegate call.
*/
-abstract class DelegateLikeCall extends Call {
+class DelegateLikeCall extends Call, DelegateLikeCall_ {
override Callable getTarget() { none() }
/**
* Gets a potential run-time target of this delegate or function pointer call in the given
* call context `cc`.
*/
- Callable getARuntimeTarget(CallContext::CallContext cc) { none() }
+ Callable getARuntimeTarget(CallContext::CallContext cc) {
+ exists(DelegateLikeCallExpr call |
+ this = call.getCall() and
+ result = call.getARuntimeTarget(cc)
+ )
+ }
/**
* Gets the delegate or function pointer expression of this call. For example, the
@@ -579,10 +586,7 @@ class DelegateCall extends DelegateLikeCall, @delegate_invocation_expr {
* call context `cc`.
*/
override Callable getARuntimeTarget(CallContext::CallContext cc) {
- exists(DelegateCallExpr call |
- this = call.getCall() and
- result = call.getARuntimeTarget(cc)
- )
+ result = DelegateLikeCall.super.getARuntimeTarget(cc)
or
exists(AddEventSource aes, CallContext::CallContext cc2 |
aes = this.getAnAddEventSource(_) and
@@ -634,17 +638,6 @@ class DelegateCall extends DelegateLikeCall, @delegate_invocation_expr {
* ```
*/
class FunctionPointerCall extends DelegateLikeCall, @function_pointer_invocation_expr {
- /**
- * Gets a potential run-time target of this function pointer call in the given
- * call context `cc`.
- */
- override Callable getARuntimeTarget(CallContext::CallContext cc) {
- exists(FunctionPointerCallExpr call |
- this = call.getCall() and
- result = call.getARuntimeTarget(cc)
- )
- }
-
override string toString() { result = "function pointer call" }
override string getAPrimaryQlClass() { result = "FunctionPointerCall" }
From 50be54385a6a0ab4d7219c350bd16d1f7e9f1f12 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Tue, 2 Feb 2021 14:49:50 +0000
Subject: [PATCH 077/429] Update qldoc
---
.../Security/CWE/CWE-326/InsufficientKeySize.java | 4 ++--
java/ql/src/semmle/code/java/security/Encryption.qll | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
index 6ccf025c244..ff30196abb5 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.java
@@ -25,12 +25,12 @@ public class InsufficientKeySize {
keyPairGen4.initialize(2048);
KeyPairGenerator keyPairGen5 = KeyPairGenerator.getInstance("EC");
- // BAD: Key size is less than 224
+ // BAD: Key size is less than 256
ECGenParameterSpec ecSpec1 = new ECGenParameterSpec("secp112r1");
keyPairGen5.initialize(ecSpec1);
KeyPairGenerator keyPairGen6 = KeyPairGenerator.getInstance("EC");
- // GOOD: Key size is no less than 224
+ // GOOD: Key size is no less than 256
ECGenParameterSpec ecSpec2 = new ECGenParameterSpec("secp256r1");
keyPairGen6.initialize(ecSpec2);
}
diff --git a/java/ql/src/semmle/code/java/security/Encryption.qll b/java/ql/src/semmle/code/java/security/Encryption.qll
index 9c10569d8c1..ddbaf9cba73 100644
--- a/java/ql/src/semmle/code/java/security/Encryption.qll
+++ b/java/ql/src/semmle/code/java/security/Encryption.qll
@@ -315,7 +315,7 @@ class JavaSecuritySignature extends JavaSecurityAlgoSpec {
override Expr getAlgoSpec() { result = this.(ConstructorCall).getArgument(0) }
}
-/** Method call to the Java class `java.security.KeyPairGenerator`. */
+/** A method call to the Java class `java.security.KeyPairGenerator`. */
class JavaSecurityKeyPairGenerator extends JavaxCryptoAlgoSpec {
JavaSecurityKeyPairGenerator() {
exists(Method m | m.getAReference() = this |
From 5e3b6fa3415d899c6bf2dea25de011244d563cee Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Tue, 2 Feb 2021 16:20:39 +0000
Subject: [PATCH 078/429] Update qldoc
---
.../experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
index 10ec6adc9df..4d4ec76f060 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-326/InsufficientKeySize.qhelp
@@ -6,7 +6,7 @@ are vulnerable to brute force attack when too small a key size is used.
-
The key should be at least 2048 bits long when using RSA and DSA encryption, 224 bits long when using EC encryption, and 128 bits long when using
+
The key should be at least 2048 bits long when using RSA and DSA encryption, 256 bits long when using EC encryption, and 128 bits long when using
symmetric encryption.
From 3151aeff48c41c067e1daea026edbe3d759b702b Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Tue, 2 Feb 2021 18:26:29 +0000
Subject: [PATCH 079/429] Enhance the query
---
.../CWE/CWE-522/InsecureLdapAuth.java | 2 +-
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 72 +++++++++----------
.../code/java/frameworks/Networking.qll | 1 +
3 files changed, 36 insertions(+), 39 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
index 33764506a1b..3c5f6555100 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.java
@@ -15,7 +15,7 @@ public class InsecureLdapAuth {
environment.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
environment.put(Context.PROVIDER_URL, ldapUrl);
environment.put(Context.REFERRAL, "follow");
- env.put(Context.SECURITY_AUTHENTICATION, "simple");
+ environment.put(Context.SECURITY_AUTHENTICATION, "simple");
environment.put(Context.SECURITY_PRINCIPAL, ldapUserName);
environment.put(Context.SECURITY_CREDENTIALS, password);
DirContext dirContext = new InitialDirContext(environment);
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index 9563c755410..b6e7b5ed702 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -76,7 +76,7 @@ class InsecureLdapUrl extends Expr {
*/
predicate isProviderUrlSetter(MethodAccess ma) {
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
- (ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
+ ma.getMethod().hasName(["put", "setProperty"]) and
(
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = "java.naming.provider.url"
or
@@ -89,27 +89,48 @@ predicate isProviderUrlSetter(MethodAccess ma) {
}
/**
- * Holds if `ma` sets `fieldValue` with attribute name `fieldName` to `envValue` in some `Hashtable`.
+ * Holds if `ma` sets `fieldValue` to `envValue` in some `Hashtable`.
*/
bindingset[fieldValue, envValue]
-predicate hasEnvWithValue(MethodAccess ma, string fieldValue, string envValue) {
+predicate hasFieldValueEnv(MethodAccess ma, string fieldValue, string envValue) {
+ // environment.put("java.naming.security.authentication", "simple")
ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
- (ma.getMethod().hasName("put") or ma.getMethod().hasName("setProperty")) and
+ ma.getMethod().hasName(["put", "setProperty"]) and
ma.getArgument(0).(CompileTimeConstantExpr).getStringValue() = fieldValue and
ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = envValue
}
+/**
+ * Holds if `ma` sets attribute name `fieldName` to `envValue` in some `Hashtable`.
+ */
+bindingset[fieldName, envValue]
+predicate hasFieldNameEnv(MethodAccess ma, string fieldName, string envValue) {
+ // environment.put(Context.SECURITY_AUTHENTICATION, "simple")
+ ma.getMethod().getDeclaringType().getAnAncestor() instanceof TypeHashtable and
+ ma.getMethod().hasName(["put", "setProperty"]) and
+ exists(Field f |
+ ma.getArgument(0) = f.getAnAccess() and
+ f.hasName(fieldName) and
+ f.getDeclaringType() instanceof TypeNamingContext
+ ) and
+ ma.getArgument(1).(CompileTimeConstantExpr).getStringValue() = envValue
+}
+
/**
* Holds if `ma` sets `java.naming.security.authentication` (also known as `Context.SECURITY_AUTHENTICATION`) to `simple` in some `Hashtable`.
*/
predicate isBasicAuthEnv(MethodAccess ma) {
- hasEnvWithValue(ma, "java.naming.security.authentication", "simple")
+ hasFieldValueEnv(ma, "java.naming.security.authentication", "simple") or
+ hasFieldNameEnv(ma, "SECURITY_AUTHENTICATION", "simple")
}
/**
* Holds if `ma` sets `java.naming.security.protocol` (also known as `Context.SECURITY_PROTOCOL`) to `ssl` in some `Hashtable`.
*/
-predicate isSSLEnv(MethodAccess ma) { hasEnvWithValue(ma, "java.naming.security.protocol", "ssl") }
+predicate isSSLEnv(MethodAccess ma) {
+ hasFieldValueEnv(ma, "java.naming.security.protocol", "ssl") or
+ hasFieldNameEnv(ma, "SECURITY_PROTOCOL", "ssl")
+}
/**
* A taint-tracking configuration for `ldap://` URL in LDAP authentication.
@@ -141,12 +162,12 @@ class InsecureUrlFlowConfig extends TaintTracking::Configuration {
/**
* A taint-tracking configuration for `simple` basic-authentication in LDAP configuration.
*/
-class BasicAuthFlowConfig extends TaintTracking::Configuration {
+class BasicAuthFlowConfig extends DataFlow::Configuration {
BasicAuthFlowConfig() { this = "InsecureLdapAuth:BasicAuthFlowConfig" }
/** Source of `simple` configuration. */
override predicate isSource(DataFlow::Node src) {
- src.asExpr().(CompileTimeConstantExpr).getStringValue() = "simple"
+ exists(MethodAccess ma | isBasicAuthEnv(ma) and ma.getQualifier() = src.asExpr())
}
/** Sink of directory context creation. */
@@ -156,26 +177,17 @@ class BasicAuthFlowConfig extends TaintTracking::Configuration {
sink.asExpr() = cc.getArgument(0)
)
}
-
- /** Method call of `env.put()`. */
- override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(MethodAccess ma |
- pred.asExpr() = ma.getArgument(1) and
- isBasicAuthEnv(ma) and
- succ.asExpr() = ma.getQualifier()
- )
- }
}
/**
* A taint-tracking configuration for `ssl` configuration in LDAP authentication.
*/
-class SSLFlowConfig extends TaintTracking::Configuration {
+class SSLFlowConfig extends DataFlow::Configuration {
SSLFlowConfig() { this = "InsecureLdapAuth:SSLFlowConfig" }
/** Source of `ssl` configuration. */
override predicate isSource(DataFlow::Node src) {
- src.asExpr().(CompileTimeConstantExpr).getStringValue() = "ssl"
+ exists(MethodAccess ma | isSSLEnv(ma) and ma.getQualifier() = src.asExpr())
}
/** Sink of directory context creation. */
@@ -185,28 +197,12 @@ class SSLFlowConfig extends TaintTracking::Configuration {
sink.asExpr() = cc.getArgument(0)
)
}
-
- /** Method call of `env.put()`. */
- override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
- exists(MethodAccess ma |
- pred.asExpr() = ma.getArgument(1) and
- isSSLEnv(ma) and
- succ.asExpr() = ma.getQualifier()
- )
- }
}
-from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureUrlFlowConfig config, VarAccess va
+from DataFlow::PathNode source, DataFlow::PathNode sink, InsecureUrlFlowConfig config
where
config.hasFlowPath(source, sink) and
- sink.getNode().asExpr() = va and
- exists(BasicAuthFlowConfig bc, DataFlow::PathNode source2, DataFlow::PathNode sink2 |
- bc.hasFlowPath(source2, sink2) and
- sink2.getNode().asExpr() = va
- ) and
- not exists(SSLFlowConfig sc, DataFlow::PathNode source3, DataFlow::PathNode sink3 |
- sc.hasFlowPath(source3, sink3) and
- sink3.getNode().asExpr() = va
- )
+ exists(BasicAuthFlowConfig bc | bc.hasFlowTo(sink.getNode())) and
+ not exists(SSLFlowConfig sc | sc.hasFlowTo(sink.getNode()))
select sink.getNode(), source, sink, "Insecure LDAP authentication from $@.", source.getNode(),
"LDAP connection string"
diff --git a/java/ql/src/semmle/code/java/frameworks/Networking.qll b/java/ql/src/semmle/code/java/frameworks/Networking.qll
index 997b8075403..cad948ed2f4 100644
--- a/java/ql/src/semmle/code/java/frameworks/Networking.qll
+++ b/java/ql/src/semmle/code/java/frameworks/Networking.qll
@@ -132,6 +132,7 @@ class UrlOpenConnectionMethod extends Method {
/**
* A string matching private host names of IPv4 and IPv6, which only matches the host portion therefore checking for port is not necessary.
+ * Several examples are localhost, reserved IPv4 IP addresses including 127.0.0.1, 10.x.x.x, 172.16.x,x, 192.168.x,x, and reserved IPv6 addresses including [0:0:0:0:0:0:0:1] and [::1]
*/
class PrivateHostName extends string {
bindingset[this]
From e4c3544a3f2706625f4c961a38c5d9c8700486e1 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Tue, 2 Feb 2021 21:59:33 +0100
Subject: [PATCH 080/429] Python: Add support for `from foo.bar import baz`
This turned out to be fairly simple. Given an import such as
```python
from foo.bar.baz import quux
```
we create an API-graph node for each valid dotted prefix of
`foo.bar.baz`, i.e. `foo`, `foo.bar`, and `foo.bar.baz`. For these, we
then insert nodes in the API graph, such that `foo` steps to `foo.bar`
along an edge labeled `bar`, etc.
Finally, we only allow undotted names to hang off of the API-graph
root. Thus, `foo` will have a `moduleImport` edge off of the root, and
a `getMember` edge for `bar` (which in turn has a `getMember` edge for
`baz`).
Relative imports are explicitly ignored.
Finally, this commit also adds inline tests for a variety of ways of
importing modules, including a copy of the "import-helper" tests (with
a few modifications to allow a single annotation per line, as these
get rather long quickly!).
---
python/ql/src/semmle/python/ApiGraphs.qll | 56 +++++++++++++++++--
.../dataflow/ApiGraphs/mypkg/__init__.py | 1 +
.../dataflow/ApiGraphs/mypkg/bar.py | 1 +
.../dataflow/ApiGraphs/mypkg/foo.py | 1 +
.../experimental/dataflow/ApiGraphs/options | 1 +
.../experimental/dataflow/ApiGraphs/test.py | 34 +++++++++++
.../experimental/dataflow/ApiGraphs/test1.py | 6 ++
.../experimental/dataflow/ApiGraphs/test2.py | 4 ++
.../experimental/dataflow/ApiGraphs/test3.py | 4 ++
.../experimental/dataflow/ApiGraphs/test4.py | 4 ++
.../experimental/dataflow/ApiGraphs/test5.py | 10 ++++
.../experimental/dataflow/ApiGraphs/test6.py | 6 ++
.../experimental/dataflow/ApiGraphs/test7.py | 10 ++++
.../dataflow/ApiGraphs/test_deep.py | 4 ++
.../dataflow/ApiGraphs/use.expected | 0
.../experimental/dataflow/ApiGraphs/use.ql | 30 ++++++++++
16 files changed, 168 insertions(+), 4 deletions(-)
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/mypkg/__init__.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/mypkg/bar.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/mypkg/foo.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/options
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test1.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test2.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test3.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test4.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test5.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test6.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test7.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/test_deep.py
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/use.expected
create mode 100644 python/ql/test/experimental/dataflow/ApiGraphs/use.ql
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 2dc62ed30a6..2c1ff3b6cf7 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -230,14 +230,52 @@ module API {
/** The root of the API graph. */
MkRoot() or
/** An abstract representative for imports of the module called `name`. */
- MkModuleImport(string name) { imports(_, name) } or
+ MkModuleImport(string name) {
+ imports(_, name) or name = any(ImportExpr e | not e.isRelative()).getAnImportedModuleName()
+ } or
/** A use of an API member at the node `nd`. */
MkUse(DataFlow::Node nd) { use(_, _, nd) }
class TUse = MkModuleImport or MkUse;
+ /**
+ * Holds if the dotted module name `sub` refers to the `member` member of `base`.
+ *
+ * For instance, `prefix_member("foo.bar", "baz", "foo.bar.baz")` would hold.
+ */
+ private predicate prefix_member(TApiNode base, string member, TApiNode sub) {
+ exists(string base_str, string sub_str |
+ base = MkModuleImport(base_str) and
+ sub = MkModuleImport(sub_str)
+ |
+ base_str + "." + member = sub_str and
+ not member.matches("%.%")
+ )
+ }
+
/** Holds if `imp` is an import of a module named `name` */
- private predicate imports(DataFlow::Node imp, string name) { imp = DataFlow::importNode(name) }
+ private predicate imports(DataFlow::Node import_node, string name) {
+ exists(Variable var, Import imp, Alias alias |
+ alias = imp.getAName() and
+ alias.getAsname() = var.getAStore() and
+ (
+ name = alias.getValue().(ImportMember).getImportedModuleName()
+ or
+ name = alias.getValue().(ImportExpr).getImportedModuleName() and
+ not alias.getValue().(ImportExpr).isRelative()
+ ) and
+ import_node.asExpr() = alias.getValue()
+ )
+ or
+ exists(ImportExpr imp_expr |
+ not imp_expr.isRelative() and
+ imp_expr.getName() = name and
+ import_node.asCfgNode().getNode() = imp_expr and
+ // in `import foo.bar` we DON'T want to give a result for `importNode("foo.bar")`,
+ // only for `importNode("foo")`. We exclude those cases with the following clause.
+ not exists(Import imp | imp.getAName().getValue() = imp_expr)
+ )
+ }
/**
* Holds if `ref` is a use of a node that should have an incoming edge from `base` labeled
@@ -248,9 +286,11 @@ module API {
exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode pred |
use(base, src) and pred = trackUseNode(src)
|
+ // Reading an attribute on a node that is a use of `base`:
lbl = Label::memberFromRef(ref) and
ref = pred.getAnAttributeRead()
or
+ // Calling a node that is a use of `base`
lbl = Label::return() and
ref = pred.getAnInvocation()
)
@@ -263,7 +303,7 @@ module API {
predicate use(TApiNode nd, DataFlow::Node ref) {
exists(string name |
nd = MkModuleImport(name) and
- ref = DataFlow::importNode(name)
+ imports(ref, name)
)
or
nd = MkUse(ref)
@@ -310,7 +350,15 @@ module API {
pred = MkRoot() and
lbl = Label::mod(m)
|
- succ = MkModuleImport(m)
+ succ = MkModuleImport(m) and
+ // Only allow undotted names to count as base modules.
+ not m.matches("%.%")
+ )
+ or
+ /* Step from the dotted module name `foo.bar` to `foo.bar.baz` along an edge labeled `baz` */
+ exists(string member |
+ prefix_member(pred, member, succ) and
+ lbl = Label::member(member)
)
or
/* Every node that is a use of an API component is itself added to the API graph. */
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/__init__.py b/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/__init__.py
new file mode 100644
index 00000000000..c84a9b135a3
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/__init__.py
@@ -0,0 +1 @@
+foo = 42
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/bar.py b/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/bar.py
new file mode 100644
index 00000000000..2ae28399f5f
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/bar.py
@@ -0,0 +1 @@
+pass
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/foo.py b/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/foo.py
new file mode 100644
index 00000000000..2ae28399f5f
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/mypkg/foo.py
@@ -0,0 +1 @@
+pass
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/options b/python/ql/test/experimental/dataflow/ApiGraphs/options
new file mode 100644
index 00000000000..1099600818b
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/options
@@ -0,0 +1 @@
+semmle-extractor-options: --lang=3
\ No newline at end of file
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test.py b/python/ql/test/experimental/dataflow/ApiGraphs/test.py
new file mode 100644
index 00000000000..c361e3a169f
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test.py
@@ -0,0 +1,34 @@
+import a1 #$ use=moduleImport("a1")
+
+x = a1.blah1 #$ use=moduleImport("a1").getMember("blah1")
+
+import a2 as m2 #$ use=moduleImport("a2")
+
+x2 = m2.blah2 #$ use=moduleImport("a2").getMember("blah2")
+
+import a3.b3 as m3 #$ use=moduleImport("a3").getMember("b3")
+
+x3 = m3.blah3 #$ use=moduleImport("a3").getMember("b3").getMember("blah3")
+
+from a4.b4 import c4 as m4 #$ use=moduleImport("a4").getMember("b4").getMember("c4")
+
+x4 = m4.blah4 #$ use=moduleImport("a4").getMember("b4").getMember("c4").getMember("blah4")
+
+import a.b.c.d #$ use=moduleImport("a")
+
+ab = a.b #$ use=moduleImport("a").getMember("b")
+
+abc = ab.c #$ use=moduleImport("a").getMember("b").getMember("c")
+
+abcd = abc.d #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d")
+
+x5 = abcd() #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d").getReturn()
+
+y5 = x5.method() #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d").getReturn().getMember("method").getReturn()
+
+
+# Relative imports. These are ignored
+
+from .foo import bar
+
+from ..foobar import baz
\ No newline at end of file
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test1.py b/python/ql/test/experimental/dataflow/ApiGraphs/test1.py
new file mode 100644
index 00000000000..847abda7749
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test1.py
@@ -0,0 +1,6 @@
+import mypkg #$ use=moduleImport("mypkg")
+print(mypkg.foo) #$ use=moduleImport("mypkg").getMember("foo") // 42
+try:
+ print(mypkg.bar) #$ use=moduleImport("mypkg").getMember("bar")
+except AttributeError as e:
+ print(e) # module 'mypkg' has no attribute 'bar'
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test2.py b/python/ql/test/experimental/dataflow/ApiGraphs/test2.py
new file mode 100644
index 00000000000..dce67f0b034
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test2.py
@@ -0,0 +1,4 @@
+from mypkg import foo #$ use=moduleImport("mypkg").getMember("foo")
+from mypkg import bar #$ use=moduleImport("mypkg").getMember("bar")
+print(foo) #$ use=moduleImport("mypkg").getMember("foo")
+print(bar) #$ use=moduleImport("mypkg").getMember("bar")
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test3.py b/python/ql/test/experimental/dataflow/ApiGraphs/test3.py
new file mode 100644
index 00000000000..83ff25cc402
--- /dev/null
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test3.py
@@ -0,0 +1,4 @@
+import mypkg.foo #$ use=moduleImport("mypkg")
+import mypkg.bar #$ use=moduleImport("mypkg")
+print(mypkg.foo) #$ use=moduleImport("mypkg").getMember("foo") //
Date: Wed, 3 Feb 2021 08:49:37 +0100
Subject: [PATCH 081/429] Revert "Merge pull request #4784 from
MathiasVP/mathiasvp/reverse-read-take-3"
This reverts commit 1b3d69d617a46b9c966b886e9941bd6cce9fcc9b, reversing
changes made to 527c41520e2d7ee8ca941d254545d0cc5300b608.
---
.../ir/dataflow/internal/DataFlowPrivate.qll | 342 ++++++-------
.../cpp/ir/dataflow/internal/DataFlowUtil.qll | 391 ++++-----------
.../dataflow-ir-consistency.expected | 65 ++-
.../dataflow/dataflow-tests/test.cpp | 2 +-
.../dataflow/fields/aliasing.cpp | 28 --
.../dataflow/fields/by_reference.cpp | 12 +-
.../fields/dataflow-consistency.expected | 8 -
.../fields/dataflow-ir-consistency.expected | 200 +++++---
.../dataflow/fields/ir-path-flow.expected | 468 +++++-------------
.../fields/partial-definition-diff.expected | 263 ++++++----
.../fields/partial-definition-ir.expected | 266 ----------
.../fields/partial-definition.expected | 17 -
.../dataflow/fields/path-flow.expected | 55 --
.../library-tests/dataflow/fields/simple.cpp | 12 -
.../dataflow-ir-consistency.expected | 194 ++++----
.../TaintedAllocationSize.expected | 24 +-
.../ArithmeticUncontrolled.expected | 30 +-
17 files changed, 885 insertions(+), 1492 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
index 68b31a732f4..762ce8d47b4 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowPrivate.qll
@@ -2,7 +2,6 @@ private import cpp
private import DataFlowUtil
private import semmle.code.cpp.ir.IR
private import DataFlowDispatch
-private import semmle.code.cpp.models.interfaces.DataFlow
/**
* A data flow node that occurs as the argument of a call and is passed as-is
@@ -210,13 +209,6 @@ private class FieldContent extends Content, TFieldContent {
predicate hasOffset(Class cl, int start, int end) { cl = c and start = startBit and end = endBit }
Field getAField() { result = getAField(c, startBit, endBit) }
-
- pragma[noinline]
- Field getADirectField() {
- c = result.getDeclaringType() and
- this.getAField() = result and
- this.hasOffset(c, _, _)
- }
}
private class CollectionContent extends Content, TCollectionContent {
@@ -229,101 +221,69 @@ private class ArrayContent extends Content, TArrayContent {
override string toString() { result = "array content" }
}
-/**
- * A store step from the value of a `StoreInstruction` to the "innermost" field of the destination.
- * This predicate only holds when there is no `ChiInsturction` that merges the result of the
- * `StoreInstruction` into a larger memory.
- */
-private predicate instrToFieldNodeStoreStepNoChi(
- Node node1, FieldContent f, PartialDefinitionNode node2
-) {
- exists(StoreInstruction store, PartialFieldDefinition pd |
- pd = node2.getPartialDefinition() and
- not exists(ChiInstruction chi | chi.getPartial() = store) and
- pd.getPreUpdateNode() = GetFieldNode::fromInstruction(store.getDestinationAddress()) and
+private predicate fieldStoreStepNoChi(Node node1, FieldContent f, PostUpdateNode node2) {
+ exists(StoreInstruction store, Class c |
+ store = node2.asInstruction() and
store.getSourceValueOperand() = node1.asOperand() and
- f.getADirectField() = pd.getPreUpdateNode().getField()
+ getWrittenField(store, f.(FieldContent).getAField(), c) and
+ f.hasOffset(c, _, _)
)
}
-/**
- * A store step from a `StoreInstruction` to the "innermost" field
- * of the destination. This predicate only holds when there exists a `ChiInstruction` that merges the
- * result of the `StoreInstruction` into a larger memory.
- */
-private predicate instrToFieldNodeStoreStepChi(
- Node node1, FieldContent f, PartialDefinitionNode node2
-) {
- exists(
- ChiPartialOperand operand, StoreInstruction store, ChiInstruction chi, PartialFieldDefinition pd
- |
- pd = node2.getPartialDefinition() and
- not chi.isResultConflated() and
- node1.asOperand() = operand and
+private FieldAddressInstruction getFieldInstruction(Instruction instr) {
+ result = instr or
+ result = instr.(CopyValueInstruction).getUnary()
+}
+
+pragma[noinline]
+private predicate getWrittenField(Instruction instr, Field f, Class c) {
+ exists(FieldAddressInstruction fa |
+ fa =
+ getFieldInstruction([
+ instr.(StoreInstruction).getDestinationAddress(),
+ instr.(WriteSideEffectInstruction).getDestinationAddress()
+ ]) and
+ f = fa.getField() and
+ c = f.getDeclaringType()
+ )
+}
+
+private predicate fieldStoreStepChi(Node node1, FieldContent f, PostUpdateNode node2) {
+ exists(ChiPartialOperand operand, ChiInstruction chi |
chi.getPartialOperand() = operand and
- store = operand.getDef() and
- pd.getPreUpdateNode() = GetFieldNode::fromInstruction(store.getDestinationAddress()) and
- f.getADirectField() = pd.getPreUpdateNode().getField()
- )
-}
-
-private predicate callableWithoutDefinitionStoreStep(
- Node node1, FieldContent f, PartialDefinitionNode node2
-) {
- exists(
- WriteSideEffectInstruction write, ChiInstruction chi, PartialFieldDefinition pd,
- Function callable, CallInstruction call
- |
- chi.getPartial() = write and
- not chi.isResultConflated() and
- pd = node2.getPartialDefinition() and
- pd.getPreUpdateNode() = GetFieldNode::fromInstruction(write.getDestinationAddress()) and
- f.getADirectField() = pd.getPreUpdateNode().getField() and
- call = write.getPrimaryInstruction() and
- callable = call.getStaticCallTarget() and
- not callable.hasDefinition()
- |
- exists(OutParameterDeref out | out.getIndex() = write.getIndex() |
- callable.(DataFlowFunction).hasDataFlow(_, out) and
- node1.asInstruction() = write
- )
- or
- // Ideally we shouldn't need to do a store step from a read side effect, but if we don't have a
- // model for the callee there might not be flow to the write side effect (since the callee has no
- // definition). This case ensures that we propagate dataflow when a field is passed into a
- // function that has a write side effect, even though the write side effect doesn't have incoming
- // flow.
- not callable instanceof DataFlowFunction and
- exists(ReadSideEffectInstruction read | call = read.getPrimaryInstruction() |
- node1.asInstruction() = read.getSideEffectOperand().getAnyDef()
+ node1.asOperand() = operand and
+ node2.asInstruction() = chi and
+ exists(Class c |
+ c = chi.getResultType() and
+ exists(int startBit, int endBit |
+ chi.getUpdatedInterval(startBit, endBit) and
+ f.hasOffset(c, startBit, endBit)
+ )
+ or
+ getWrittenField(operand.getDef(), f.getAField(), c) and
+ f.hasOffset(c, _, _)
)
)
}
-/**
- * A store step from a `StoreInstruction` to the `ChiInstruction` generated from assigning
- * to a pointer or array indirection
- */
-private predicate arrayStoreStepChi(Node node1, ArrayContent a, PartialDefinitionNode node2) {
+private predicate arrayStoreStepChi(Node node1, ArrayContent a, PostUpdateNode node2) {
a = TArrayContent() and
- exists(
- ChiPartialOperand operand, ChiInstruction chi, StoreInstruction store, PartialDefinition pd
- |
- pd = node2.getPartialDefinition() and
+ exists(ChiPartialOperand operand, ChiInstruction chi, StoreInstruction store |
chi.getPartialOperand() = operand and
store = operand.getDef() and
node1.asOperand() = operand and
// This `ChiInstruction` will always have a non-conflated result because both `ArrayStoreNode`
// and `PointerStoreNode` require it in their characteristic predicates.
- pd.getPreUpdateNode().asOperand() = chi.getTotalOperand()
- |
- // `x[i] = taint()`
- // This matches the characteristic predicate in `ArrayStoreNode`.
- store.getDestinationAddress() instanceof PointerAddInstruction
- or
- // `*p = taint()`
- // This matches the characteristic predicate in `PointerStoreNode`.
- store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
+ node2.asInstruction() = chi and
+ (
+ // `x[i] = taint()`
+ // This matches the characteristic predicate in `ArrayStoreNode`.
+ store.getDestinationAddress() instanceof PointerAddInstruction
+ or
+ // `*p = taint()`
+ // This matches the characteristic predicate in `PointerStoreNode`.
+ store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
+ )
)
}
@@ -333,10 +293,82 @@ private predicate arrayStoreStepChi(Node node1, ArrayContent a, PartialDefinitio
* value of `node1`.
*/
predicate storeStep(Node node1, Content f, PostUpdateNode node2) {
- instrToFieldNodeStoreStepNoChi(node1, f, node2) or
- instrToFieldNodeStoreStepChi(node1, f, node2) or
+ fieldStoreStepNoChi(node1, f, node2) or
+ fieldStoreStepChi(node1, f, node2) or
arrayStoreStepChi(node1, f, node2) or
- callableWithoutDefinitionStoreStep(node1, f, node2)
+ fieldStoreStepAfterArraySuppression(node1, f, node2)
+}
+
+// This predicate pushes the correct `FieldContent` onto the access path when the
+// `suppressArrayRead` predicate has popped off an `ArrayContent`.
+private predicate fieldStoreStepAfterArraySuppression(
+ Node node1, FieldContent f, PostUpdateNode node2
+) {
+ exists(WriteSideEffectInstruction write, ChiInstruction chi, Class c |
+ not chi.isResultConflated() and
+ node1.asInstruction() = chi and
+ node2.asInstruction() = chi and
+ chi.getPartial() = write and
+ getWrittenField(write, f.getAField(), c) and
+ f.hasOffset(c, _, _)
+ )
+}
+
+bindingset[result, i]
+private int unbindInt(int i) { i <= result and i >= result }
+
+pragma[noinline]
+private predicate getLoadedField(LoadInstruction load, Field f, Class c) {
+ exists(FieldAddressInstruction fa |
+ fa = load.getSourceAddress() and
+ f = fa.getField() and
+ c = f.getDeclaringType()
+ )
+}
+
+/**
+ * Holds if data can flow from `node1` to `node2` via a read of `f`.
+ * Thus, `node1` references an object with a field `f` whose value ends up in
+ * `node2`.
+ */
+private predicate fieldReadStep(Node node1, FieldContent f, Node node2) {
+ exists(LoadOperand operand |
+ node2.asOperand() = operand and
+ node1.asInstruction() = operand.getAnyDef() and
+ exists(Class c |
+ c = operand.getAnyDef().getResultType() and
+ exists(int startBit, int endBit |
+ operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit)) and
+ f.hasOffset(c, startBit, endBit)
+ )
+ or
+ getLoadedField(operand.getUse(), f.getAField(), c) and
+ f.hasOffset(c, _, _)
+ )
+ )
+}
+
+/**
+ * When a store step happens in a function that looks like an array write such as:
+ * ```cpp
+ * void f(int* pa) {
+ * pa = source();
+ * }
+ * ```
+ * it can be a write to an array, but it can also happen that `f` is called as `f(&a.x)`. If that is
+ * the case, the `ArrayContent` that was written by the call to `f` should be popped off the access
+ * path, and a `FieldContent` containing `x` should be pushed instead.
+ * So this case pops `ArrayContent` off the access path, and the `fieldStoreStepAfterArraySuppression`
+ * predicate in `storeStep` ensures that we push the right `FieldContent` onto the access path.
+ */
+predicate suppressArrayRead(Node node1, ArrayContent a, Node node2) {
+ a = TArrayContent() and
+ exists(WriteSideEffectInstruction write, ChiInstruction chi |
+ node1.asInstruction() = write and
+ node2.asInstruction() = chi and
+ chi.getPartial() = write and
+ getWrittenField(write, _, _)
+ )
}
private class ArrayToPointerConvertInstruction extends ConvertInstruction {
@@ -346,96 +378,34 @@ private class ArrayToPointerConvertInstruction extends ConvertInstruction {
}
}
-private class InexactLoadOperand extends LoadOperand {
- InexactLoadOperand() { this.isDefinitionInexact() }
-}
-
-/** Get the result type of `i`, if it is a `PointerType`. */
-private PointerType getPointerType(Instruction i) {
- // We are done if the type is a pointer type that is not a glvalue
- i.getResultLanguageType().hasType(result, false)
+private Instruction skipOneCopyValueInstructionRec(CopyValueInstruction copy) {
+ copy.getUnary() = result and not result instanceof CopyValueInstruction
or
- // Some instructions produce a glvalue. Recurse past those to get the actual `PointerType`.
- result = getPointerType(i.(PointerOffsetInstruction).getLeft())
+ result = skipOneCopyValueInstructionRec(copy.getUnary())
}
-pragma[noinline]
-private predicate deconstructLoad(
- LoadInstruction load, InexactLoadOperand loadOperand, Instruction addressInstr
-) {
- load.getSourceAddress() = addressInstr and
- load.getSourceValueOperand() = loadOperand
+private Instruction skipCopyValueInstructions(Operand op) {
+ not result instanceof CopyValueInstruction and result = op.getDef()
+ or
+ result = skipOneCopyValueInstructionRec(op.getDef())
}
private predicate arrayReadStep(Node node1, ArrayContent a, Node node2) {
a = TArrayContent() and
// Explicit dereferences such as `*p` or `p[i]` where `p` is a pointer or array.
- exists(InexactLoadOperand loadOperand, LoadInstruction load, Instruction address |
- deconstructLoad(load, loadOperand, address) and
- node1.asInstruction() = loadOperand.getAnyDef() and
- not node1.asInstruction().isResultConflated() and
- loadOperand = node2.asOperand() and
- // Ensure that the load is actually loading from an array or a pointer.
- getPointerType(address).getBaseType() = load.getResultType()
- )
-}
-
-/** Step from the value loaded by a `LoadInstruction` to the "outermost" loaded field. */
-private predicate instrToFieldNodeReadStep(FieldNode node1, FieldContent f, Node node2) {
- (
- node1.getNextNode() = node2
- or
- not exists(node1.getNextNode()) and
- (
- exists(LoadInstruction load |
- node2.asInstruction() = load and
- node1 = GetFieldNode::fromInstruction(load.getSourceAddress())
- )
- or
- exists(ReadSideEffectInstruction read |
- node2.asOperand() = read.getSideEffectOperand() and
- node1 = GetFieldNode::fromInstruction(read.getArgumentDef())
- )
- )
- ) and
- f.getADirectField() = node1.getField()
-}
-
-bindingset[result, i]
-private int unbindInt(int i) { i <= result and i >= result }
-
-pragma[noinline]
-private FieldNode getFieldNodeFromLoadOperand(LoadOperand loadOperand) {
- result = GetFieldNode::fromOperand(loadOperand.getAddressOperand())
-}
-
-/**
- * Sometimes there's no explicit field dereference. In such cases we use the IR alias analysis to
- * determine the offset being, and deduce the field from this information.
- */
-private predicate aliasedReadStep(Node node1, FieldContent f, Node node2) {
- exists(LoadOperand operand, Class c, int startBit, int endBit |
- // Ensure that we don't already catch this store step using a `FieldNode`.
- not instrToFieldNodeReadStep(getFieldNodeFromLoadOperand(operand), f, _) and
+ exists(LoadOperand operand, Instruction address |
+ operand.isDefinitionInexact() and
node1.asInstruction() = operand.getAnyDef() and
- node2.asOperand() = operand and
- not node1.asInstruction().isResultConflated() and
- c = operand.getAnyDef().getResultType() and
- f.hasOffset(c, startBit, endBit) and
- operand.getUsedInterval(unbindInt(startBit), unbindInt(endBit))
+ operand = node2.asOperand() and
+ address = skipCopyValueInstructions(operand.getAddressOperand()) and
+ (
+ address instanceof LoadInstruction or
+ address instanceof ArrayToPointerConvertInstruction or
+ address instanceof PointerOffsetInstruction
+ )
)
}
-/** Get the result type of an `Instruction` i, if it is a `ReferenceType`. */
-private ReferenceType getReferenceType(Instruction i) {
- i.getResultLanguageType().hasType(result, false)
-}
-
-pragma[noinline]
-Type getResultTypeOfSourceValue(CopyValueInstruction copy) {
- result = copy.getSourceValue().getResultType()
-}
-
/**
* In cases such as:
* ```cpp
@@ -447,25 +417,21 @@ Type getResultTypeOfSourceValue(CopyValueInstruction copy) {
* f(&x);
* use(x);
* ```
- * the store to `*pa` in `f` will push `ArrayContent` onto the access path. The `innerRead` predicate
- * pops the `ArrayContent` off the access path when a value-to-pointer or value-to-reference conversion
- * happens on the argument that is ends up as the target of such a store.
+ * the load on `x` in `use(x)` will exactly overlap with its definition (in this case the definition
+ * is a `WriteSideEffect`). This predicate pops the `ArrayContent` (pushed by the store in `f`)
+ * from the access path.
*/
-private predicate innerReadStep(Node node1, Content a, Node node2) {
+private predicate exactReadStep(Node node1, ArrayContent a, Node node2) {
a = TArrayContent() and
- exists(WriteSideEffectInstruction write, CallInstruction call, CopyValueInstruction copyValue |
- write.getPrimaryInstruction() = call and
+ exists(WriteSideEffectInstruction write, ChiInstruction chi |
+ not chi.isResultConflated() and
+ chi.getPartial() = write and
node1.asInstruction() = write and
- (
- not exists(ChiInstruction chi | chi.getPartial() = write)
- or
- exists(ChiInstruction chi | chi.getPartial() = write and not chi.isResultConflated())
- ) and
- node2.asInstruction() = write and
- copyValue = call.getArgument(write.getIndex()) and
- // Check that `copyValue` is actually doing a T to a T* conversion.
- [getPointerType(copyValue).getBaseType(), getReferenceType(copyValue).getBaseType()].stripType() =
- getResultTypeOfSourceValue(copyValue).stripType()
+ node2.asInstruction() = chi and
+ // To distinquish this case from the `arrayReadStep` case we require that the entire variable was
+ // overwritten by the `WriteSideEffectInstruction` (i.e., there is a load that reads the
+ // entire variable).
+ exists(LoadInstruction load | load.getSourceValue() = chi)
)
}
@@ -475,10 +441,10 @@ private predicate innerReadStep(Node node1, Content a, Node node2) {
* `node2`.
*/
predicate readStep(Node node1, Content f, Node node2) {
- aliasedReadStep(node1, f, node2) or
+ fieldReadStep(node1, f, node2) or
arrayReadStep(node1, f, node2) or
- instrToFieldNodeReadStep(node1, f, node2) or
- innerReadStep(node1, f, node2)
+ exactReadStep(node1, f, node2) or
+ suppressArrayRead(node1, f, node2)
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index aba8e3bceec..b21a0abc20b 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -15,11 +15,7 @@ cached
private newtype TIRDataFlowNode =
TInstructionNode(Instruction i) or
TOperandNode(Operand op) or
- TVariableNode(Variable var) or
- // `FieldNodes` are used as targets of certain `storeStep`s to implement handling of stores to
- // nested structs.
- TFieldNode(FieldAddressInstruction field) or
- TPartialDefinitionNode(PartialDefinition pd)
+ TVariableNode(Variable var)
/**
* A node in a data flow graph.
@@ -32,22 +28,7 @@ class Node extends TIRDataFlowNode {
/**
* INTERNAL: Do not use.
*/
- final Declaration getEnclosingCallable() {
- result = unique(Declaration d | d = this.getEnclosingCallableImpl() | d)
- }
-
- final private Declaration getEnclosingCallableImpl() {
- result = this.asInstruction().getEnclosingFunction() or
- result = this.asOperand().getUse().getEnclosingFunction() or
- // When flow crosses from one _enclosing callable_ to another, the
- // interprocedural data-flow library discards call contexts and inserts a
- // node in the big-step relation used for human-readable path explanations.
- // Therefore we want a distinct enclosing callable for each `VariableNode`,
- // and that can be the `Variable` itself.
- result = this.asVariable() or
- result = this.(FieldNode).getFieldInstruction().getEnclosingFunction() or
- result = this.(PartialDefinitionNode).getPreUpdateNode().getFunction()
- }
+ Declaration getEnclosingCallable() { none() } // overridden in subclasses
/** Gets the function to which this node belongs, if any. */
Function getFunction() { none() } // overridden in subclasses
@@ -152,6 +133,8 @@ class InstructionNode extends Node, TInstructionNode {
/** Gets the instruction corresponding to this node. */
Instruction getInstruction() { result = instr }
+ override Declaration getEnclosingCallable() { result = this.getFunction() }
+
override Function getFunction() { result = instr.getEnclosingFunction() }
override IRType getType() { result = instr.getResultIRType() }
@@ -176,6 +159,8 @@ class OperandNode extends Node, TOperandNode {
/** Gets the operand corresponding to this node. */
Operand getOperand() { result = op }
+ override Declaration getEnclosingCallable() { result = this.getFunction() }
+
override Function getFunction() { result = op.getUse().getEnclosingFunction() }
override IRType getType() { result = op.getIRType() }
@@ -185,139 +170,6 @@ class OperandNode extends Node, TOperandNode {
override string toString() { result = this.getOperand().toString() }
}
-/**
- * INTERNAL: do not use. Encapsulates the details of getting a `FieldNode` from
- * an `Instruction` or an `Operand`.
- */
-module GetFieldNode {
- /** An abstract class that defines conversion-like instructions. */
- abstract private class SkippableInstruction extends Instruction {
- abstract Instruction getSourceInstruction();
- }
-
- /**
- * Gets the instruction that is propaged through a non-empty sequence of conversion-like instructions.
- */
- private Instruction skipSkippableInstructionsRec(SkippableInstruction skip) {
- result = skip.getSourceInstruction() and not result instanceof SkippableInstruction
- or
- result = skipSkippableInstructionsRec(skip.getSourceInstruction())
- }
-
- /**
- * Gets the instruction that is propagated through a (possibly empty) sequence of conversion-like
- * instructions.
- */
- private Instruction skipSkippableInstructions(Instruction instr) {
- result = instr and not result instanceof SkippableInstruction
- or
- result = skipSkippableInstructionsRec(instr)
- }
-
- private class SkippableCopyValueInstruction extends SkippableInstruction, CopyValueInstruction {
- override Instruction getSourceInstruction() { result = this.getSourceValue() }
- }
-
- private class SkippableConvertInstruction extends SkippableInstruction, ConvertInstruction {
- override Instruction getSourceInstruction() { result = this.getUnary() }
- }
-
- private class SkippableCheckedConvertInstruction extends SkippableInstruction,
- CheckedConvertOrNullInstruction {
- override Instruction getSourceInstruction() { result = this.getUnary() }
- }
-
- private class SkippableInheritanceConversionInstruction extends SkippableInstruction,
- InheritanceConversionInstruction {
- override Instruction getSourceInstruction() { result = this.getUnary() }
- }
-
- /**
- * INTERNAL: do not use. Gets the `FieldNode` corresponding to `instr`, if
- * `instr` is an instruction that propagates an address of a `FieldAddressInstruction`.
- */
- FieldNode fromInstruction(Instruction instr) {
- result.getFieldInstruction() = skipSkippableInstructions(instr)
- }
-
- /**
- * INTERNAL: do not use. Gets the `FieldNode` corresponding to `op`, if the definition
- * of `op` is an instruction that propagates an address of a `FieldAddressInstruction`.
- */
- FieldNode fromOperand(Operand op) { result = fromInstruction(op.getDef()) }
-}
-
-/**
- * INTERNAL: do not use. A `FieldNode` represents the state of a field before any partial definitions
- * of the field. For instance, in the snippet:
- * ```cpp
- * struct A { struct B { int c; } b; };
- * // ...
- * A a;
- * f(a.b.c);
- * ```
- * there are two `FieldNode`s: one corresponding to `c`, and one corresponding to `b`. Similarly,
- * in `a.b.c = x` there are two `FieldNode`s: one for `c` and one for `b`.
- */
-class FieldNode extends Node, TFieldNode {
- FieldAddressInstruction field;
-
- FieldNode() { this = TFieldNode(field) }
-
- /** Gets the `Field` of this `FieldNode`. */
- Field getField() { result = getFieldInstruction().getField() }
-
- /** Gets the `FieldAddressInstruction` of this `FieldNode`. */
- FieldAddressInstruction getFieldInstruction() { result = field }
-
- /**
- * Gets the `FieldNode` corresponding to the parent field of this `FieldNode`, if any.
- *
- * For example, if `f` is the `FieldNode` for `c` in the expression `a.b.c`, then `f.getObjectNode()`
- * gives the `FieldNode` of `b`, and `f.getObjectNode().getObjectNode()` has no result as `a` is
- * not a field.
- */
- FieldNode getObjectNode() { result = GetFieldNode::fromInstruction(field.getObjectAddress()) }
-
- /**
- * Gets the `FieldNode` that has this `FieldNode` as parent, if any.
- *
- * For example, if `f` is the `FieldNode` corresponding to `b` in `a.b.c`, then `f.getNextNode()`
- * gives the `FieldNode` corresponding to `c`, and `f.getNextNode().getNextNode()`.
- */
- FieldNode getNextNode() { result.getObjectNode() = this }
-
- /** Gets the class where the field of this node is declared. */
- Class getDeclaringType() { result = getField().getDeclaringType() }
-
- override Function getFunction() { result = field.getEnclosingFunction() }
-
- override IRType getType() { result = field.getResultIRType() }
-
- override Location getLocation() { result = field.getLocation() }
-
- override string toString() { result = this.getField().toString() }
-}
-
-/**
- * INTERNAL: do not use. A partial definition of a `FieldNode`.
- */
-class PartialFieldDefinition extends FieldNode, PartialDefinition {
- /**
- * The pre-update node of a partial definition of a `FieldNode` is the `FieldNode` itself. This ensures
- * that the data flow library's reverse read mechanism builds up the correct access path for nested
- * fields.
- * For instance, in `a.b.c = x` there is a partial definition for `c` (let's call it `post[c]`) and a
- * partial definition for `b` (let's call it `post[b]`), and there is a read step from `b` to `c`
- * (using `instrToFieldNodeReadStep`), so there is a store step from `post[c]` to `post[b]`.
- */
- override FieldNode getPreUpdateNode() { result = this }
-
- override Expr getDefinedExpr() {
- result = this.getFieldInstruction().getObjectAddress().getUnconvertedResultExpression()
- }
-}
-
/**
* An expression, viewed as a node in a data flow graph.
*/
@@ -455,26 +307,11 @@ deprecated class UninitializedNode extends Node {
* This class exists to match the interface used by Java. There are currently no non-abstract
* classes that extend it. When we implement field flow, we can revisit this.
*/
-abstract class PostUpdateNode extends Node {
+abstract class PostUpdateNode extends InstructionNode {
/**
* Gets the node before the state update.
*/
abstract Node getPreUpdateNode();
-
- override Function getFunction() { result = getPreUpdateNode().getFunction() }
-
- override IRType getType() { result = getPreUpdateNode().getType() }
-
- override Location getLocation() { result = getPreUpdateNode().getLocation() }
-}
-
-/** INTERNAL: do not use. A partial definition of a node. */
-abstract class PartialDefinition extends Node {
- /** Gets the node before the state update. */
- abstract Node getPreUpdateNode();
-
- /** Gets the expression that is partially defined by this node. */
- abstract Expr getDefinedExpr();
}
/**
@@ -490,40 +327,112 @@ abstract class PartialDefinition extends Node {
* setY(&x); // a partial definition of the object `x`.
* ```
*/
-class PartialDefinitionNode extends PostUpdateNode, TPartialDefinitionNode {
- PartialDefinition pd;
+abstract private class PartialDefinitionNode extends PostUpdateNode {
+ abstract Expr getDefinedExpr();
+}
- PartialDefinitionNode() { this = TPartialDefinitionNode(pd) }
+private class ExplicitFieldStoreQualifierNode extends PartialDefinitionNode {
+ override ChiInstruction instr;
+ StoreInstruction store;
- /** Gets the expression that is partially defined by this node, if any. */
- Expr getDefinedExpr() { result = pd.getDefinedExpr() }
+ ExplicitFieldStoreQualifierNode() {
+ not instr.isResultConflated() and
+ instr.getPartial() = store and
+ (
+ instr.getUpdatedInterval(_, _) or
+ store.getDestinationAddress() instanceof FieldAddressInstruction
+ )
+ }
- override Node getPreUpdateNode() { result = pd.getPreUpdateNode() }
+ // By using an operand as the result of this predicate we avoid the dataflow inconsistency errors
+ // caused by having multiple nodes sharing the same pre update node. This inconsistency error can cause
+ // a tuple explosion in the big step dataflow relation since it can make many nodes be the entry node
+ // into a big step.
+ override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
- /** Gets the `PartialDefinition` associated with this node. */
- PartialDefinition getPartialDefinition() { result = pd }
+ override Expr getDefinedExpr() {
+ result =
+ store
+ .getDestinationAddress()
+ .(FieldAddressInstruction)
+ .getObjectAddress()
+ .getUnconvertedResultExpression()
+ }
+}
- override string toString() { result = getPreUpdateNode().toString() + " [post update]" }
+/**
+ * Not every store instruction generates a chi instruction that we can attach a PostUpdateNode to.
+ * For instance, an update to a field of a struct containing only one field. For these cases we
+ * attach the PostUpdateNode to the store instruction. There's no obvious pre update node for this case
+ * (as the entire memory is updated), so `getPreUpdateNode` is implemented as `none()`.
+ */
+private class ExplicitSingleFieldStoreQualifierNode extends PartialDefinitionNode {
+ override StoreInstruction instr;
+
+ ExplicitSingleFieldStoreQualifierNode() {
+ not exists(ChiInstruction chi | chi.getPartial() = instr) and
+ // Without this condition any store would create a `PostUpdateNode`.
+ instr.getDestinationAddress() instanceof FieldAddressInstruction
+ }
+
+ override Node getPreUpdateNode() { none() }
+
+ override Expr getDefinedExpr() {
+ result =
+ instr
+ .getDestinationAddress()
+ .(FieldAddressInstruction)
+ .getObjectAddress()
+ .getUnconvertedResultExpression()
+ }
+}
+
+private FieldAddressInstruction getFieldInstruction(Instruction instr) {
+ result = instr or
+ result = instr.(CopyValueInstruction).getUnary()
+}
+
+/**
+ * The target of a `fieldStoreStepAfterArraySuppression` store step, which is used to convert
+ * an `ArrayContent` to a `FieldContent` when the `WriteSideEffect` instruction stores
+ * into a field. See the QLDoc for `suppressArrayRead` for an example of where such a conversion
+ * is inserted.
+ */
+private class WriteSideEffectFieldStoreQualifierNode extends PartialDefinitionNode {
+ override ChiInstruction instr;
+ WriteSideEffectInstruction write;
+ FieldAddressInstruction field;
+
+ WriteSideEffectFieldStoreQualifierNode() {
+ not instr.isResultConflated() and
+ instr.getPartial() = write and
+ field = getFieldInstruction(write.getDestinationAddress())
+ }
+
+ override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
+
+ override Expr getDefinedExpr() {
+ result = field.getObjectAddress().getUnconvertedResultExpression()
+ }
}
/**
* The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
* `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
*/
-private class ArrayStoreNode extends InstructionNode, PartialDefinition {
- ChiInstruction chi;
+private class ArrayStoreNode extends PartialDefinitionNode {
+ override ChiInstruction instr;
PointerAddInstruction add;
ArrayStoreNode() {
- chi = this.getInstruction() and
- not chi.isResultConflated() and
+ not instr.isResultConflated() and
exists(StoreInstruction store |
- chi.getPartial() = store and
+ instr.getPartial() = store and
add = store.getDestinationAddress()
)
}
- override Node getPreUpdateNode() { result.asOperand() = chi.getTotalOperand() }
+ override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
override Expr getDefinedExpr() { result = add.getLeft().getUnconvertedResultExpression() }
}
@@ -532,24 +441,18 @@ private class ArrayStoreNode extends InstructionNode, PartialDefinition {
* The `PostUpdateNode` that is the target of a `arrayStoreStepChi` store step. The overriden
* `ChiInstruction` corresponds to the instruction represented by `node2` in `arrayStoreStepChi`.
*/
-private class PointerStoreNode extends InstructionNode, PartialDefinition {
- ChiInstruction chi;
- LoadInstruction load;
+private class PointerStoreNode extends PostUpdateNode {
+ override ChiInstruction instr;
PointerStoreNode() {
- chi = this.getInstruction() and
- not chi.isResultConflated() and
+ not instr.isResultConflated() and
exists(StoreInstruction store |
- chi.getPartial() = store and
- load = store.getDestinationAddress().(CopyValueInstruction).getUnary()
+ instr.getPartial() = store and
+ store.getDestinationAddress().(CopyValueInstruction).getUnary() instanceof LoadInstruction
)
}
- override Node getPreUpdateNode() { result.asOperand() = chi.getTotalOperand() }
-
- override Expr getDefinedExpr() {
- result = load.getSourceAddress().getUnconvertedResultExpression()
- }
+ override Node getPreUpdateNode() { result.asOperand() = instr.getTotalOperand() }
}
/**
@@ -562,7 +465,7 @@ private class PointerStoreNode extends InstructionNode, PartialDefinition {
* returned. This node will have its `getArgument()` equal to `&x` and its
* `getVariableAccess()` equal to `x`.
*/
-class DefinitionByReferenceNode extends InstructionNode, PostUpdateNode {
+class DefinitionByReferenceNode extends InstructionNode {
override WriteSideEffectInstruction instr;
/** Gets the unconverted argument corresponding to this node. */
@@ -590,22 +493,6 @@ class DefinitionByReferenceNode extends InstructionNode, PostUpdateNode {
not exists(instr.getPrimaryInstruction().(CallInstruction).getStaticCallTarget()) and
result = "output argument"
}
-
- override Function getFunction() { result = instr.getEnclosingFunction() }
-
- override IRType getType() { result = instr.getResultIRType() }
-
- override Location getLocation() { result = instr.getLocation() }
-
- // Make the read side effect's side effect operand the pre update node of this write side effect.
- // This ensures that we match up the parameter index of the parameter indirection's modification.
- override Node getPreUpdateNode() {
- exists(ReadSideEffectInstruction read |
- read.getPrimaryInstruction() = instr.getPrimaryInstruction() and
- read.getArgumentDef() = instr.getDestinationAddress() and
- result.asOperand() = read.getSideEffectOperand()
- )
- }
}
/**
@@ -623,6 +510,15 @@ class VariableNode extends Node, TVariableNode {
override Function getFunction() { none() }
+ override Declaration getEnclosingCallable() {
+ // When flow crosses from one _enclosing callable_ to another, the
+ // interprocedural data-flow library discards call contexts and inserts a
+ // node in the big-step relation used for human-readable path explanations.
+ // Therefore we want a distinct enclosing callable for each `VariableNode`,
+ // and that can be the `Variable` itself.
+ result = v
+ }
+
override IRType getType() { result.getCanonicalLanguageType().hasUnspecifiedType(v.getType(), _) }
override Location getLocation() { result = v.getLocation() }
@@ -689,69 +585,6 @@ Node uninitializedNode(LocalVariable v) { none() }
*/
predicate localFlowStep(Node nodeFrom, Node nodeTo) { simpleLocalFlowStep(nodeFrom, nodeTo) }
-private predicate flowOutOfPostUpdate(PartialDefinitionNode nodeFrom, Node nodeTo) {
- // flow from the "outermost" field to the `ChiInstruction`, or `StoreInstruction`
- // if no `ChiInstruction` exists.
- exists(AddressOperand addressOperand, PartialFieldDefinition pd |
- pd = nodeFrom.getPartialDefinition() and
- not exists(pd.getPreUpdateNode().getObjectNode()) and
- pd.getPreUpdateNode().getNextNode*() = GetFieldNode::fromOperand(addressOperand) and
- (
- exists(ChiInstruction chi |
- nodeTo.asInstruction() = chi and
- chi.getPartial().getAnOperand() = addressOperand
- )
- or
- exists(StoreInstruction store |
- not exists(ChiInstruction chi | chi.getPartial() = store) and
- nodeTo.asInstruction() = store and
- store.getDestinationAddressOperand() = addressOperand
- )
- )
- )
- or
- // Note: This partial definition cannot be a `PostUpdateFieldNode` since these nodes do not have an
- // operand node as their pre update node.
- exists(PartialDefinition pd |
- pd = nodeFrom.getPartialDefinition() and
- nodeTo.asInstruction().(ChiInstruction).getTotalOperand() = pd.getPreUpdateNode().asOperand()
- )
-}
-
-/**
- * Gets the `FieldNode` corresponding to the outermost field that is used to compute `address`.
- */
-private FieldNode getOutermostFieldNode(Instruction address) {
- not exists(result.getObjectNode()) and
- result.getNextNode*() = GetFieldNode::fromInstruction(address)
-}
-
-private predicate flowIntoReadNode(Node nodeFrom, FieldNode nodeTo) {
- // flow from the memory of a load to the "outermost" field of that load.
- exists(LoadInstruction load |
- nodeTo = getOutermostFieldNode(load.getSourceAddress()) and
- not nodeFrom.asInstruction().isResultConflated() and
- nodeFrom.asInstruction() = load.getSourceValueOperand().getAnyDef()
- )
- or
- // We need this to make stores look like loads for the dataflow library. So when there's a store
- // of the form x->y = z we need to make the field node corresponding to y look like it's reading
- // from the memory of x.
- exists(StoreInstruction store, ChiInstruction chi |
- chi.getPartial() = store and
- nodeTo = getOutermostFieldNode(store.getDestinationAddress()) and
- not nodeFrom.asInstruction().isResultConflated() and
- nodeFrom.asInstruction() = chi.getTotal()
- )
- or
- exists(ReadSideEffectInstruction read, SideEffectOperand sideEffect |
- sideEffect = read.getSideEffectOperand() and
- not sideEffect.getAnyDef().isResultConflated() and
- nodeTo = getOutermostFieldNode(read.getArgumentDef()) and
- nodeFrom.asOperand() = sideEffect
- )
-}
-
/**
* INTERNAL: do not use.
*
@@ -765,10 +598,6 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
or
// Instruction -> Operand flow
simpleOperandLocalFlowStep(nodeFrom.asInstruction(), nodeTo.asOperand())
- or
- flowIntoReadNode(nodeFrom, nodeTo)
- or
- flowOutOfPostUpdate(nodeFrom, nodeTo)
}
pragma[noinline]
diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected
index b9f6f717c80..fc6c97aa2a6 100644
--- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected
+++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/dataflow-ir-consistency.expected
@@ -26,21 +26,60 @@ unreachableNodeCCtx
localCallNodes
postIsNotPre
postHasUniquePre
-| dispatch.cpp:15:8:15:8 | Top output argument | PostUpdateNode should have one pre-update node but has 0. |
-| dispatch.cpp:21:8:21:8 | Middle output argument | PostUpdateNode should have one pre-update node but has 0. |
-| dispatch.cpp:60:18:60:29 | Bottom output argument | PostUpdateNode should have one pre-update node but has 0. |
-| dispatch.cpp:61:18:61:29 | Middle output argument | PostUpdateNode should have one pre-update node but has 0. |
-| dispatch.cpp:65:10:65:21 | Bottom output argument | PostUpdateNode should have one pre-update node but has 0. |
-| test.cpp:384:10:384:13 | memcpy output argument | PostUpdateNode should have one pre-update node but has 0. |
-| test.cpp:391:10:391:13 | memcpy output argument | PostUpdateNode should have one pre-update node but has 0. |
-| test.cpp:400:10:400:13 | memcpy output argument | PostUpdateNode should have one pre-update node but has 0. |
-| test.cpp:407:10:407:13 | memcpy output argument | PostUpdateNode should have one pre-update node but has 0. |
uniquePostUpdate
postIsInSameCallable
reverseRead
argHasPostUpdate
postWithInFlow
-| test.cpp:384:10:384:13 | memcpy output argument | PostUpdateNode should not be the target of local flow. |
-| test.cpp:391:10:391:13 | memcpy output argument | PostUpdateNode should not be the target of local flow. |
-| test.cpp:400:10:400:13 | memcpy output argument | PostUpdateNode should not be the target of local flow. |
-| test.cpp:407:10:407:13 | memcpy output argument | PostUpdateNode should not be the target of local flow. |
+| BarrierGuard.cpp:49:3:49:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| BarrierGuard.cpp:60:3:60:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:28:3:28:34 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:34:22:34:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:34:32:34:37 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:39:32:39:37 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:39:42:39:47 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:43:35:43:40 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:43:51:43:51 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:49:25:49:30 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:49:35:49:40 | Chi | PostUpdateNode should not be the target of local flow. |
+| clang.cpp:50:3:50:26 | Chi | PostUpdateNode should not be the target of local flow. |
+| example.c:17:19:17:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| example.c:17:21:17:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| example.c:24:2:24:30 | Chi | PostUpdateNode should not be the target of local flow. |
+| example.c:24:13:24:30 | Chi | PostUpdateNode should not be the target of local flow. |
+| example.c:26:2:26:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. |
+| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. |
+| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. |
+| lambdas.cpp:13:12:13:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| lambdas.cpp:13:15:13:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| lambdas.cpp:28:10:31:2 | Chi | PostUpdateNode should not be the target of local flow. |
+| lambdas.cpp:28:10:31:2 | Chi | PostUpdateNode should not be the target of local flow. |
+| lambdas.cpp:43:3:43:14 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:11:5:11:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:20:5:20:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:22:7:22:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:24:7:24:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:29:5:29:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:31:7:31:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:39:7:39:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:44:5:44:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:46:7:46:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:48:7:48:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:75:5:75:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:83:5:83:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:87:7:87:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:89:7:89:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:94:5:94:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:96:7:96:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:104:7:104:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:109:5:109:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:113:7:113:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| ref.cpp:115:7:115:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:91:3:91:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:115:3:115:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:120:3:120:10 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:125:3:125:11 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:359:5:359:20 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:373:5:373:20 | Chi | PostUpdateNode should not be the target of local flow. |
+| test.cpp:465:3:465:15 | Chi | PostUpdateNode should not be the target of local flow. |
diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp
index 774ecddeab2..f59552aa2dd 100644
--- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp
+++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.cpp
@@ -362,7 +362,7 @@ class FlowThroughFields {
int f() {
sink(field); // tainted or clean? Not sure.
taintField();
- sink(field); // $ ast,ir
+ sink(field); // $ ast MISSING: ir
}
int calledAfterTaint() {
diff --git a/cpp/ql/test/library-tests/dataflow/fields/aliasing.cpp b/cpp/ql/test/library-tests/dataflow/fields/aliasing.cpp
index 833e85600a6..500bbed53a9 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/aliasing.cpp
+++ b/cpp/ql/test/library-tests/dataflow/fields/aliasing.cpp
@@ -204,32 +204,4 @@ void deep_member_field_arrow(S2 *ps2) {
void deep_member_field_arrow_different_fields(S2 *ps2) {
taint_a_ptr(&ps2->s.m1);
sink(ps2->s.m2);
-}
-
-void test_deep_struct_fields() {
- S2 s2;
- s2.s.m1 = user_input();
- S s = s2.s;
- sink(s.m1); // $ ast,ir
-}
-
-void test_deep_struct_fields_no_flow() {
- S2 s2;
- s2.s.m1 = user_input();
- S s = s2.s;
- sink(s.m2);
-}
-
-void test_deep_struct_fields_taint_through_call() {
- S2 s2;
- taint_a_ptr(&s2.s.m1);
- S s = s2.s;
- sink(s.m1); // $ ast,ir
-}
-
-void test_deep_struct_fields_taint_through_call_no_flow() {
- S2 s2;
- taint_a_ptr(&s2.s.m1);
- S s = s2.s;
- sink(s.m2);
}
\ No newline at end of file
diff --git a/cpp/ql/test/library-tests/dataflow/fields/by_reference.cpp b/cpp/ql/test/library-tests/dataflow/fields/by_reference.cpp
index 46abcd62c07..8e84d39be3b 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/by_reference.cpp
+++ b/cpp/ql/test/library-tests/dataflow/fields/by_reference.cpp
@@ -1,4 +1,4 @@
-void sink(void *o); void sink(const char *o);
+void sink(void *o);
void *user_input(void);
struct S {
@@ -135,13 +135,3 @@ void test_outer_with_ref(Outer *pouter) {
sink(pouter->inner_ptr->a); // $ ast MISSING: ir
sink(pouter->a); // $ ast,ir
}
-
-void taint_a_ptr(const char **pa) {
- *pa = (char*)user_input();
-}
-
-void test_const_char_ref() {
- const char* s;
- taint_a_ptr(&s);
- sink(s); // $ ast ir=140:9 ir=140:16
-}
\ No newline at end of file
diff --git a/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected b/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected
index 500faa38eb1..c6528723d22 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/dataflow-consistency.expected
@@ -89,10 +89,6 @@ postWithInFlow
| aliasing.cpp:194:21:194:22 | m1 [inner post update] | PostUpdateNode should not be the target of local flow. |
| aliasing.cpp:200:23:200:24 | m1 [inner post update] | PostUpdateNode should not be the target of local flow. |
| aliasing.cpp:205:23:205:24 | m1 [inner post update] | PostUpdateNode should not be the target of local flow. |
-| aliasing.cpp:211:8:211:9 | m1 [post update] | PostUpdateNode should not be the target of local flow. |
-| aliasing.cpp:218:8:218:9 | m1 [post update] | PostUpdateNode should not be the target of local flow. |
-| aliasing.cpp:225:21:225:22 | m1 [inner post update] | PostUpdateNode should not be the target of local flow. |
-| aliasing.cpp:232:21:232:22 | m1 [inner post update] | PostUpdateNode should not be the target of local flow. |
| arrays.cpp:6:3:6:5 | arr [inner post update] | PostUpdateNode should not be the target of local flow. |
| arrays.cpp:6:3:6:8 | access to array [post update] | PostUpdateNode should not be the target of local flow. |
| arrays.cpp:15:3:15:10 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
@@ -123,9 +119,6 @@ postWithInFlow
| by_reference.cpp:108:24:108:24 | a [inner post update] | PostUpdateNode should not be the target of local flow. |
| by_reference.cpp:123:28:123:36 | inner_ptr [inner post update] | PostUpdateNode should not be the target of local flow. |
| by_reference.cpp:127:30:127:38 | inner_ptr [inner post update] | PostUpdateNode should not be the target of local flow. |
-| by_reference.cpp:140:3:140:5 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
-| by_reference.cpp:140:4:140:5 | pa [inner post update] | PostUpdateNode should not be the target of local flow. |
-| by_reference.cpp:145:16:145:16 | s [inner post update] | PostUpdateNode should not be the target of local flow. |
| complex.cpp:11:22:11:23 | a_ [post update] | PostUpdateNode should not be the target of local flow. |
| complex.cpp:12:22:12:23 | b_ [post update] | PostUpdateNode should not be the target of local flow. |
| conflated.cpp:10:3:10:7 | * ... [post update] | PostUpdateNode should not be the target of local flow. |
@@ -159,6 +152,5 @@ postWithInFlow
| simple.cpp:65:7:65:7 | i [post update] | PostUpdateNode should not be the target of local flow. |
| simple.cpp:83:12:83:13 | f1 [post update] | PostUpdateNode should not be the target of local flow. |
| simple.cpp:92:7:92:7 | i [post update] | PostUpdateNode should not be the target of local flow. |
-| simple.cpp:104:9:104:9 | i [post update] | PostUpdateNode should not be the target of local flow. |
| struct_init.c:24:11:24:12 | ab [inner post update] | PostUpdateNode should not be the target of local flow. |
| struct_init.c:36:17:36:24 | nestedAB [inner post update] | PostUpdateNode should not be the target of local flow. |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected
index 0a03ccef569..63d3b2c0f48 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/dataflow-ir-consistency.expected
@@ -20,71 +20,145 @@ unreachableNodeCCtx
localCallNodes
postIsNotPre
postHasUniquePre
-| A.cpp:9:9:9:9 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:14:9:14:9 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:31:14:31:21 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:38:7:38:8 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:39:7:39:8 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:41:15:41:21 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:47:12:47:18 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:54:12:54:18 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:55:12:55:19 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:57:11:57:24 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:57:17:57:23 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:62:13:62:19 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:64:21:64:28 | C2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:71:13:71:19 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:73:25:73:32 | C2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:89:15:89:21 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:99:14:99:21 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:116:12:116:19 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:126:12:126:18 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:130:12:130:18 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:142:14:142:20 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:143:25:143:31 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:150:12:150:18 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:151:12:151:24 | D output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:159:12:159:18 | B output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:160:18:160:60 | MyList output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:160:32:160:59 | MyList output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:161:18:161:40 | MyList output argument | PostUpdateNode should have one pre-update node but has 0. |
-| A.cpp:162:18:162:40 | MyList output argument | PostUpdateNode should have one pre-update node but has 0. |
-| B.cpp:7:16:7:35 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| B.cpp:8:16:8:27 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| B.cpp:16:16:16:38 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| B.cpp:17:16:17:27 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| C.cpp:18:12:18:18 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:29:15:29:41 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:29:24:29:40 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:36:15:36:41 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:36:24:36:40 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:43:15:43:41 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:43:24:43:40 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:50:15:50:41 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:50:24:50:40 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:57:16:57:42 | Box2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| D.cpp:57:25:57:41 | Box1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| complex.cpp:22:11:22:17 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| complex.cpp:25:7:25:7 | Bar output argument | PostUpdateNode should have one pre-update node but has 0. |
-| complex.cpp:48:9:48:10 | Outer output argument | PostUpdateNode should have one pre-update node but has 0. |
-| complex.cpp:49:9:49:10 | Outer output argument | PostUpdateNode should have one pre-update node but has 0. |
-| complex.cpp:50:9:50:10 | Outer output argument | PostUpdateNode should have one pre-update node but has 0. |
-| complex.cpp:51:9:51:10 | Outer output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conflated.cpp:59:20:59:39 | LinkedList output argument | PostUpdateNode should have one pre-update node but has 0. |
-| constructors.cpp:34:11:34:26 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| constructors.cpp:35:11:35:26 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| constructors.cpp:36:11:36:37 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| constructors.cpp:37:11:37:15 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| realistic.cpp:54:16:54:47 | memcpy output argument | PostUpdateNode should have one pre-update node but has 0. |
-| realistic.cpp:60:16:60:18 | memcpy output argument | PostUpdateNode should have one pre-update node but has 0. |
-| simple.cpp:34:11:34:15 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| simple.cpp:35:11:35:15 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| simple.cpp:36:11:36:15 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| simple.cpp:37:11:37:15 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
+| simple.cpp:65:5:65:22 | Store | PostUpdateNode should have one pre-update node but has 0. |
+| simple.cpp:92:5:92:22 | Store | PostUpdateNode should have one pre-update node but has 0. |
uniquePostUpdate
postIsInSameCallable
reverseRead
argHasPostUpdate
postWithInFlow
-| realistic.cpp:54:16:54:47 | memcpy output argument | PostUpdateNode should not be the target of local flow. |
-| realistic.cpp:60:16:60:18 | memcpy output argument | PostUpdateNode should not be the target of local flow. |
+| A.cpp:25:7:25:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:27:22:27:32 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:98:12:98:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:100:5:100:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:142:7:142:20 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:143:7:143:31 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:183:7:183:20 | Chi | PostUpdateNode should not be the target of local flow. |
+| A.cpp:184:7:184:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| B.cpp:6:15:6:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| B.cpp:15:15:15:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| B.cpp:35:7:35:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| B.cpp:36:7:36:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| B.cpp:46:7:46:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| C.cpp:22:12:22:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| C.cpp:22:12:22:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| C.cpp:24:5:24:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| C.cpp:24:16:24:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:9:21:9:28 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:11:29:11:36 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:16:21:16:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:18:29:18:35 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:28:15:28:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:35:15:35:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:42:15:42:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:49:15:49:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:56:15:56:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| D.cpp:57:5:57:42 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:9:3:9:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:13:3:13:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:17:3:17:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:21:12:21:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:21:15:21:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:22:12:22:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:22:15:22:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:23:12:23:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:23:15:23:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:35:12:35:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:35:15:35:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:37:3:37:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:40:12:40:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:40:15:40:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:42:3:42:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:47:12:47:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:47:15:47:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:49:3:49:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:52:12:52:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:52:15:52:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:54:3:54:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:59:12:59:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:59:15:59:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:60:3:60:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:70:19:70:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:70:22:70:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:72:3:72:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:77:19:77:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:77:22:77:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:79:3:79:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:84:19:84:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:84:22:84:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:86:3:86:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:91:19:91:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:91:22:91:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:92:3:92:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:98:3:98:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:106:3:106:20 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:111:15:111:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:147:15:147:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:175:15:175:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:181:15:181:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:187:15:187:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:194:15:194:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:200:15:200:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| aliasing.cpp:205:15:205:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:5:18:5:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:5:21:5:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:6:3:6:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:14:18:14:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:14:21:14:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:15:3:15:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| arrays.cpp:36:3:36:37 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:12:5:12:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:16:5:16:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:84:3:84:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:88:3:88:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:92:3:92:20 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:96:3:96:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:102:21:102:39 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:104:15:104:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:106:21:106:41 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:108:15:108:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:122:21:122:38 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:124:15:124:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:126:21:126:40 | Chi | PostUpdateNode should not be the target of local flow. |
+| by_reference.cpp:128:15:128:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:11:22:11:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:12:22:12:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:14:26:14:26 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:14:33:14:33 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:22:11:22:17 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:25:7:25:7 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:42:16:42:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:43:16:43:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:53:12:53:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:54:12:54:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:55:12:55:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| complex.cpp:56:12:56:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:45:39:45:42 | Chi | PostUpdateNode should not be the target of local flow. |
+| conflated.cpp:53:3:53:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| constructors.cpp:20:24:20:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| constructors.cpp:21:24:21:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| constructors.cpp:23:28:23:28 | Chi | PostUpdateNode should not be the target of local flow. |
+| constructors.cpp:23:35:23:35 | Chi | PostUpdateNode should not be the target of local flow. |
+| qualifiers.cpp:9:30:9:44 | Chi | PostUpdateNode should not be the target of local flow. |
+| qualifiers.cpp:12:49:12:64 | Chi | PostUpdateNode should not be the target of local flow. |
+| qualifiers.cpp:13:51:13:65 | Chi | PostUpdateNode should not be the target of local flow. |
+| realistic.cpp:39:12:39:95 | Chi | PostUpdateNode should not be the target of local flow. |
+| realistic.cpp:49:9:49:64 | Chi | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:20:24:20:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:21:24:21:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:23:28:23:28 | Chi | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:23:35:23:35 | Chi | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:65:5:65:22 | Store | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:83:9:83:28 | Chi | PostUpdateNode should not be the target of local flow. |
+| simple.cpp:92:5:92:22 | Store | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:20:20:20:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:20:34:20:34 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:27:7:27:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:27:21:27:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:28:5:28:7 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:36:10:36:24 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:40:20:40:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:40:34:40:34 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:42:7:42:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:42:21:42:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| struct_init.c:43:5:43:7 | Chi | PostUpdateNode should not be the target of local flow. |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected
index 0f25daa307d..c8b70a74b3a 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/ir-path-flow.expected
@@ -4,62 +4,50 @@ edges
| A.cpp:55:12:55:19 | new | A.cpp:55:5:55:5 | set output argument [c] |
| A.cpp:57:11:57:24 | B output argument [c] | A.cpp:57:28:57:30 | call to get |
| A.cpp:57:17:57:23 | new | A.cpp:57:11:57:24 | B output argument [c] |
-| A.cpp:98:12:98:18 | new | A.cpp:100:9:100:9 | a [post update] [a] |
-| A.cpp:100:9:100:9 | a [post update] [a] | A.cpp:103:14:103:14 | *c [a] |
-| A.cpp:103:14:103:14 | *c [a] | A.cpp:107:16:107:16 | a [a] |
-| A.cpp:107:16:107:16 | a [a] | A.cpp:107:16:107:16 | a |
+| A.cpp:98:12:98:18 | new | A.cpp:100:5:100:13 | Chi [a] |
+| A.cpp:100:5:100:13 | Chi [a] | A.cpp:103:14:103:14 | *c [a] |
+| A.cpp:103:14:103:14 | *c [a] | A.cpp:107:16:107:16 | a |
| A.cpp:126:5:126:5 | Chi [c] | A.cpp:131:8:131:8 | f7 output argument [c] |
| A.cpp:126:5:126:5 | set output argument [c] | A.cpp:126:5:126:5 | Chi [c] |
-| A.cpp:126:5:126:5 | set output argument [c] | A.cpp:131:8:131:8 | f7 output argument [c] |
| A.cpp:126:12:126:18 | new | A.cpp:126:5:126:5 | set output argument [c] |
-| A.cpp:131:8:131:8 | f7 output argument [c] | A.cpp:132:13:132:13 | c [c] |
-| A.cpp:132:13:132:13 | c [c] | A.cpp:132:13:132:13 | c |
+| A.cpp:131:8:131:8 | Chi [c] | A.cpp:132:13:132:13 | c |
+| A.cpp:131:8:131:8 | f7 output argument [c] | A.cpp:131:8:131:8 | Chi [c] |
| A.cpp:142:7:142:20 | Chi [c] | A.cpp:151:18:151:18 | D output argument [c] |
-| A.cpp:142:10:142:10 | c [post update] [c] | A.cpp:142:7:142:20 | Chi [c] |
-| A.cpp:142:10:142:10 | c [post update] [c] | A.cpp:151:18:151:18 | D output argument [c] |
-| A.cpp:142:14:142:20 | new | A.cpp:142:10:142:10 | c [post update] [c] |
+| A.cpp:142:14:142:20 | new | A.cpp:142:7:142:20 | Chi [c] |
| A.cpp:143:7:143:31 | Chi [b] | A.cpp:151:12:151:24 | D output argument [b] |
-| A.cpp:143:13:143:13 | b [post update] [b] | A.cpp:143:7:143:31 | Chi [b] |
-| A.cpp:143:25:143:31 | new | A.cpp:143:13:143:13 | b [post update] [b] |
+| A.cpp:143:25:143:31 | new | A.cpp:143:7:143:31 | Chi [b] |
| A.cpp:150:12:150:18 | new | A.cpp:151:12:151:24 | D output argument [b] |
-| A.cpp:151:12:151:24 | D output argument [b] | A.cpp:152:13:152:13 | b [b] |
-| A.cpp:151:18:151:18 | D output argument [c] | A.cpp:154:13:154:13 | c [c] |
-| A.cpp:152:13:152:13 | b [b] | A.cpp:152:13:152:13 | b |
-| A.cpp:154:13:154:13 | c [c] | A.cpp:154:13:154:13 | c |
+| A.cpp:151:12:151:24 | Chi [b] | A.cpp:152:13:152:13 | b |
+| A.cpp:151:12:151:24 | D output argument [b] | A.cpp:151:12:151:24 | Chi [b] |
+| A.cpp:151:18:151:18 | Chi [c] | A.cpp:154:13:154:13 | c |
+| A.cpp:151:18:151:18 | D output argument [c] | A.cpp:151:18:151:18 | Chi [c] |
| C.cpp:18:12:18:18 | C output argument [s1] | C.cpp:27:8:27:11 | *#this [s1] |
| C.cpp:18:12:18:18 | C output argument [s3] | C.cpp:27:8:27:11 | *#this [s3] |
-| C.cpp:22:9:22:22 | s1 [post update] [s1] | C.cpp:24:5:24:25 | Chi [s1] |
-| C.cpp:22:12:22:21 | new | C.cpp:22:9:22:22 | s1 [post update] [s1] |
+| C.cpp:22:12:22:21 | Chi [s1] | C.cpp:24:5:24:25 | Chi [s1] |
+| C.cpp:22:12:22:21 | new | C.cpp:22:12:22:21 | Chi [s1] |
| C.cpp:24:5:24:25 | Chi [s1] | C.cpp:18:12:18:18 | C output argument [s1] |
| C.cpp:24:5:24:25 | Chi [s3] | C.cpp:18:12:18:18 | C output argument [s3] |
-| C.cpp:24:11:24:12 | s3 [post update] [s3] | C.cpp:24:5:24:25 | Chi [s3] |
-| C.cpp:24:16:24:25 | new | C.cpp:24:11:24:12 | s3 [post update] [s3] |
-| C.cpp:27:8:27:11 | *#this [s1] | C.cpp:29:10:29:11 | s1 [s1] |
-| C.cpp:27:8:27:11 | *#this [s3] | C.cpp:31:10:31:11 | s3 [s3] |
-| C.cpp:29:10:29:11 | s1 [s1] | C.cpp:29:10:29:11 | s1 |
-| C.cpp:31:10:31:11 | s3 [s3] | C.cpp:31:10:31:11 | s3 |
+| C.cpp:24:16:24:25 | new | C.cpp:24:5:24:25 | Chi [s3] |
+| C.cpp:27:8:27:11 | *#this [s1] | C.cpp:29:10:29:11 | s1 |
+| C.cpp:27:8:27:11 | *#this [s3] | C.cpp:31:10:31:11 | s3 |
| aliasing.cpp:9:3:9:22 | Chi [m1] | aliasing.cpp:25:17:25:19 | pointerSetter output argument [m1] |
-| aliasing.cpp:9:6:9:7 | m1 [post update] [m1] | aliasing.cpp:9:3:9:22 | Chi [m1] |
-| aliasing.cpp:9:6:9:7 | m1 [post update] [m1] | aliasing.cpp:25:17:25:19 | pointerSetter output argument [m1] |
-| aliasing.cpp:9:11:9:20 | call to user_input | aliasing.cpp:9:6:9:7 | m1 [post update] [m1] |
+| aliasing.cpp:9:11:9:20 | call to user_input | aliasing.cpp:9:3:9:22 | Chi [m1] |
| aliasing.cpp:13:3:13:21 | Chi [m1] | aliasing.cpp:26:19:26:20 | referenceSetter output argument [m1] |
-| aliasing.cpp:13:5:13:6 | m1 [post update] [m1] | aliasing.cpp:13:3:13:21 | Chi [m1] |
-| aliasing.cpp:13:5:13:6 | m1 [post update] [m1] | aliasing.cpp:26:19:26:20 | referenceSetter output argument [m1] |
-| aliasing.cpp:13:10:13:19 | call to user_input | aliasing.cpp:13:5:13:6 | m1 [post update] [m1] |
-| aliasing.cpp:25:17:25:19 | pointerSetter output argument [m1] | aliasing.cpp:29:11:29:12 | m1 [m1] |
-| aliasing.cpp:26:19:26:20 | referenceSetter output argument [m1] | aliasing.cpp:30:11:30:12 | m1 [m1] |
-| aliasing.cpp:29:11:29:12 | m1 [m1] | aliasing.cpp:29:11:29:12 | m1 |
-| aliasing.cpp:30:11:30:12 | m1 [m1] | aliasing.cpp:30:11:30:12 | m1 |
+| aliasing.cpp:13:10:13:19 | call to user_input | aliasing.cpp:13:3:13:21 | Chi [m1] |
+| aliasing.cpp:25:17:25:19 | Chi [m1] | aliasing.cpp:29:11:29:12 | m1 |
+| aliasing.cpp:25:17:25:19 | pointerSetter output argument [m1] | aliasing.cpp:25:17:25:19 | Chi [m1] |
+| aliasing.cpp:26:19:26:20 | Chi [m1] | aliasing.cpp:30:11:30:12 | m1 |
+| aliasing.cpp:26:19:26:20 | referenceSetter output argument [m1] | aliasing.cpp:26:19:26:20 | Chi [m1] |
| aliasing.cpp:37:13:37:22 | call to user_input | aliasing.cpp:38:11:38:12 | m1 |
| aliasing.cpp:42:11:42:20 | call to user_input | aliasing.cpp:43:13:43:14 | m1 |
-| aliasing.cpp:60:6:60:7 | m1 [post update] [m1] | aliasing.cpp:62:14:62:15 | m1 [m1] |
-| aliasing.cpp:60:11:60:20 | call to user_input | aliasing.cpp:60:6:60:7 | m1 [post update] [m1] |
-| aliasing.cpp:62:14:62:15 | m1 [m1] | aliasing.cpp:62:14:62:15 | m1 |
+| aliasing.cpp:60:3:60:22 | Chi [m1] | aliasing.cpp:61:13:61:14 | Store [m1] |
+| aliasing.cpp:60:11:60:20 | call to user_input | aliasing.cpp:60:3:60:22 | Chi [m1] |
+| aliasing.cpp:61:13:61:14 | Store [m1] | aliasing.cpp:62:14:62:15 | m1 |
| aliasing.cpp:79:11:79:20 | call to user_input | aliasing.cpp:80:12:80:13 | m1 |
| aliasing.cpp:86:10:86:19 | call to user_input | aliasing.cpp:87:12:87:13 | m1 |
| aliasing.cpp:92:12:92:21 | call to user_input | aliasing.cpp:93:12:93:13 | m1 |
-| aliasing.cpp:98:5:98:6 | m1 [post update] [m1] | aliasing.cpp:100:14:100:14 | Store [m1] |
-| aliasing.cpp:98:10:98:19 | call to user_input | aliasing.cpp:98:5:98:6 | m1 [post update] [m1] |
+| aliasing.cpp:98:3:98:21 | Chi [m1] | aliasing.cpp:100:14:100:14 | Store [m1] |
+| aliasing.cpp:98:10:98:19 | call to user_input | aliasing.cpp:98:3:98:21 | Chi [m1] |
| aliasing.cpp:100:14:100:14 | Store [m1] | aliasing.cpp:102:8:102:10 | * ... |
| aliasing.cpp:106:3:106:20 | Chi [array content] | aliasing.cpp:121:15:121:16 | taint_a_ptr output argument [array content] |
| aliasing.cpp:106:3:106:20 | Chi [array content] | aliasing.cpp:126:15:126:20 | taint_a_ptr output argument [array content] |
@@ -70,19 +58,7 @@ edges
| aliasing.cpp:106:3:106:20 | Chi [array content] | aliasing.cpp:175:15:175:22 | taint_a_ptr output argument [array content] |
| aliasing.cpp:106:3:106:20 | Chi [array content] | aliasing.cpp:187:15:187:22 | taint_a_ptr output argument [array content] |
| aliasing.cpp:106:3:106:20 | Chi [array content] | aliasing.cpp:200:15:200:24 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | Chi [array content] | aliasing.cpp:225:15:225:22 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:106:3:106:20 | Chi [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:121:15:121:16 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:126:15:126:20 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:131:15:131:16 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:136:15:136:17 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:158:15:158:20 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:164:15:164:20 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:175:15:175:22 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:187:15:187:22 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:200:15:200:24 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | aliasing.cpp:225:15:225:22 | taint_a_ptr output argument [array content] |
-| aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] |
+| aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:106:3:106:20 | Chi [array content] |
| aliasing.cpp:121:15:121:16 | Chi [array content] | aliasing.cpp:122:8:122:12 | access to array |
| aliasing.cpp:121:15:121:16 | taint_a_ptr output argument [array content] | aliasing.cpp:121:15:121:16 | Chi [array content] |
| aliasing.cpp:126:15:126:20 | Chi [array content] | aliasing.cpp:127:8:127:16 | * ... |
@@ -95,37 +71,16 @@ edges
| aliasing.cpp:158:15:158:20 | taint_a_ptr output argument [array content] | aliasing.cpp:158:15:158:20 | Chi [array content] |
| aliasing.cpp:164:15:164:20 | Chi [array content] | aliasing.cpp:165:8:165:16 | access to array |
| aliasing.cpp:164:15:164:20 | taint_a_ptr output argument [array content] | aliasing.cpp:164:15:164:20 | Chi [array content] |
-| aliasing.cpp:175:15:175:22 | taint_a_ptr output argument | aliasing.cpp:175:21:175:22 | m1 [post update] [m1] |
-| aliasing.cpp:175:15:175:22 | taint_a_ptr output argument [array content] | aliasing.cpp:175:15:175:22 | taint_a_ptr output argument |
-| aliasing.cpp:175:19:175:19 | s [post update] [s, m1] | aliasing.cpp:176:11:176:11 | s [s, m1] |
-| aliasing.cpp:175:21:175:22 | m1 [post update] [m1] | aliasing.cpp:175:19:175:19 | s [post update] [s, m1] |
-| aliasing.cpp:176:11:176:11 | s [s, m1] | aliasing.cpp:176:13:176:14 | m1 [m1] |
-| aliasing.cpp:176:13:176:14 | m1 [m1] | aliasing.cpp:176:13:176:14 | m1 |
-| aliasing.cpp:187:15:187:22 | taint_a_ptr output argument | aliasing.cpp:187:21:187:22 | m1 [post update] [m1] |
-| aliasing.cpp:187:15:187:22 | taint_a_ptr output argument [array content] | aliasing.cpp:187:15:187:22 | taint_a_ptr output argument |
-| aliasing.cpp:187:19:187:19 | s [post update] [s, m1] | aliasing.cpp:189:13:189:13 | s [s, m1] |
-| aliasing.cpp:187:21:187:22 | m1 [post update] [m1] | aliasing.cpp:187:19:187:19 | s [post update] [s, m1] |
-| aliasing.cpp:189:13:189:13 | s [s, m1] | aliasing.cpp:189:15:189:16 | m1 [m1] |
-| aliasing.cpp:189:15:189:16 | m1 [m1] | aliasing.cpp:189:15:189:16 | m1 |
-| aliasing.cpp:200:15:200:24 | taint_a_ptr output argument | aliasing.cpp:200:23:200:24 | m1 [post update] [m1] |
-| aliasing.cpp:200:15:200:24 | taint_a_ptr output argument [array content] | aliasing.cpp:200:15:200:24 | taint_a_ptr output argument |
-| aliasing.cpp:200:21:200:21 | s [post update] [s, m1] | aliasing.cpp:201:13:201:13 | s [s, m1] |
-| aliasing.cpp:200:23:200:24 | m1 [post update] [m1] | aliasing.cpp:200:21:200:21 | s [post update] [s, m1] |
-| aliasing.cpp:201:13:201:13 | s [s, m1] | aliasing.cpp:201:15:201:16 | m1 [m1] |
-| aliasing.cpp:201:15:201:16 | m1 [m1] | aliasing.cpp:201:15:201:16 | m1 |
-| aliasing.cpp:211:6:211:6 | s [post update] [s, m1] | aliasing.cpp:212:12:212:12 | s [s, m1] |
-| aliasing.cpp:211:8:211:9 | m1 [post update] [m1] | aliasing.cpp:211:6:211:6 | s [post update] [s, m1] |
-| aliasing.cpp:211:13:211:22 | call to user_input | aliasing.cpp:211:8:211:9 | m1 [post update] [m1] |
-| aliasing.cpp:212:12:212:12 | s [m1] | aliasing.cpp:213:10:213:11 | m1 [m1] |
-| aliasing.cpp:212:12:212:12 | s [s, m1] | aliasing.cpp:212:12:212:12 | s [m1] |
-| aliasing.cpp:213:10:213:11 | m1 [m1] | aliasing.cpp:213:10:213:11 | m1 |
-| aliasing.cpp:225:15:225:22 | taint_a_ptr output argument | aliasing.cpp:225:21:225:22 | m1 [post update] [m1] |
-| aliasing.cpp:225:15:225:22 | taint_a_ptr output argument [array content] | aliasing.cpp:225:15:225:22 | taint_a_ptr output argument |
-| aliasing.cpp:225:19:225:19 | s [post update] [s, m1] | aliasing.cpp:226:12:226:12 | s [s, m1] |
-| aliasing.cpp:225:21:225:22 | m1 [post update] [m1] | aliasing.cpp:225:19:225:19 | s [post update] [s, m1] |
-| aliasing.cpp:226:12:226:12 | s [m1] | aliasing.cpp:227:10:227:11 | m1 [m1] |
-| aliasing.cpp:226:12:226:12 | s [s, m1] | aliasing.cpp:226:12:226:12 | s [m1] |
-| aliasing.cpp:227:10:227:11 | m1 [m1] | aliasing.cpp:227:10:227:11 | m1 |
+| aliasing.cpp:175:15:175:22 | Chi | aliasing.cpp:175:15:175:22 | Chi [m1] |
+| aliasing.cpp:175:15:175:22 | Chi [m1] | aliasing.cpp:176:13:176:14 | m1 |
+| aliasing.cpp:175:15:175:22 | taint_a_ptr output argument [array content] | aliasing.cpp:175:15:175:22 | Chi |
+| aliasing.cpp:187:15:187:22 | Chi | aliasing.cpp:187:15:187:22 | Chi [m1] |
+| aliasing.cpp:187:15:187:22 | Chi [m1] | aliasing.cpp:188:13:188:14 | Store [m1] |
+| aliasing.cpp:187:15:187:22 | taint_a_ptr output argument [array content] | aliasing.cpp:187:15:187:22 | Chi |
+| aliasing.cpp:188:13:188:14 | Store [m1] | aliasing.cpp:189:15:189:16 | m1 |
+| aliasing.cpp:200:15:200:24 | Chi | aliasing.cpp:200:15:200:24 | Chi [m1] |
+| aliasing.cpp:200:15:200:24 | Chi [m1] | aliasing.cpp:201:15:201:16 | m1 |
+| aliasing.cpp:200:15:200:24 | taint_a_ptr output argument [array content] | aliasing.cpp:200:15:200:24 | Chi |
| arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:7:8:7:13 | access to array |
| arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:9:8:9:11 | * ... |
| arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:10:8:10:15 | * ... |
@@ -141,126 +96,65 @@ edges
| by_reference.cpp:68:21:68:30 | call to user_input | by_reference.cpp:68:17:68:18 | nonMemberSetA output argument [a] |
| by_reference.cpp:84:3:84:25 | Chi [a] | by_reference.cpp:102:21:102:39 | taint_inner_a_ptr output argument [a] |
| by_reference.cpp:84:3:84:25 | Chi [a] | by_reference.cpp:106:21:106:41 | taint_inner_a_ptr output argument [a] |
-| by_reference.cpp:84:10:84:10 | a [post update] [a] | by_reference.cpp:84:3:84:25 | Chi [a] |
-| by_reference.cpp:84:10:84:10 | a [post update] [a] | by_reference.cpp:102:21:102:39 | taint_inner_a_ptr output argument [a] |
-| by_reference.cpp:84:10:84:10 | a [post update] [a] | by_reference.cpp:106:21:106:41 | taint_inner_a_ptr output argument [a] |
-| by_reference.cpp:84:14:84:23 | call to user_input | by_reference.cpp:84:10:84:10 | a [post update] [a] |
+| by_reference.cpp:84:14:84:23 | call to user_input | by_reference.cpp:84:3:84:25 | Chi [a] |
| by_reference.cpp:88:3:88:24 | Chi [a] | by_reference.cpp:122:21:122:38 | taint_inner_a_ref output argument [a] |
| by_reference.cpp:88:3:88:24 | Chi [a] | by_reference.cpp:126:21:126:40 | taint_inner_a_ref output argument [a] |
-| by_reference.cpp:88:9:88:9 | a [post update] [a] | by_reference.cpp:88:3:88:24 | Chi [a] |
-| by_reference.cpp:88:9:88:9 | a [post update] [a] | by_reference.cpp:122:21:122:38 | taint_inner_a_ref output argument [a] |
-| by_reference.cpp:88:9:88:9 | a [post update] [a] | by_reference.cpp:126:21:126:40 | taint_inner_a_ref output argument [a] |
-| by_reference.cpp:88:13:88:22 | call to user_input | by_reference.cpp:88:9:88:9 | a [post update] [a] |
+| by_reference.cpp:88:13:88:22 | call to user_input | by_reference.cpp:88:3:88:24 | Chi [a] |
| by_reference.cpp:92:3:92:20 | Chi [array content] | by_reference.cpp:104:15:104:22 | taint_a_ptr output argument [array content] |
| by_reference.cpp:92:3:92:20 | Chi [array content] | by_reference.cpp:108:15:108:24 | taint_a_ptr output argument [array content] |
-| by_reference.cpp:92:3:92:20 | ChiTotal [post update] [array content] | by_reference.cpp:92:3:92:20 | Chi [array content] |
-| by_reference.cpp:92:3:92:20 | ChiTotal [post update] [array content] | by_reference.cpp:104:15:104:22 | taint_a_ptr output argument [array content] |
-| by_reference.cpp:92:3:92:20 | ChiTotal [post update] [array content] | by_reference.cpp:108:15:108:24 | taint_a_ptr output argument [array content] |
-| by_reference.cpp:92:9:92:18 | call to user_input | by_reference.cpp:92:3:92:20 | ChiTotal [post update] [array content] |
+| by_reference.cpp:92:9:92:18 | call to user_input | by_reference.cpp:92:3:92:20 | Chi [array content] |
| by_reference.cpp:96:3:96:19 | Chi [array content] | by_reference.cpp:124:15:124:21 | taint_a_ref output argument [array content] |
| by_reference.cpp:96:3:96:19 | Chi [array content] | by_reference.cpp:128:15:128:23 | taint_a_ref output argument [array content] |
-| by_reference.cpp:96:3:96:19 | ChiTotal [post update] [array content] | by_reference.cpp:96:3:96:19 | Chi [array content] |
-| by_reference.cpp:96:3:96:19 | ChiTotal [post update] [array content] | by_reference.cpp:124:15:124:21 | taint_a_ref output argument [array content] |
-| by_reference.cpp:96:3:96:19 | ChiTotal [post update] [array content] | by_reference.cpp:128:15:128:23 | taint_a_ref output argument [array content] |
-| by_reference.cpp:96:8:96:17 | call to user_input | by_reference.cpp:96:3:96:19 | ChiTotal [post update] [array content] |
-| by_reference.cpp:102:21:102:39 | taint_inner_a_ptr output argument [a] | by_reference.cpp:102:28:102:39 | inner_nested [post update] [a, a] |
-| by_reference.cpp:102:28:102:39 | inner_nested [post update] [a, a] | by_reference.cpp:110:14:110:25 | inner_nested [a, a] |
-| by_reference.cpp:104:15:104:22 | taint_a_ptr output argument | by_reference.cpp:104:22:104:22 | a [post update] [a] |
-| by_reference.cpp:104:15:104:22 | taint_a_ptr output argument [array content] | by_reference.cpp:104:15:104:22 | taint_a_ptr output argument |
-| by_reference.cpp:104:22:104:22 | a [post update] [a] | by_reference.cpp:112:14:112:14 | a [a] |
-| by_reference.cpp:106:21:106:41 | taint_inner_a_ptr output argument [a] | by_reference.cpp:106:30:106:41 | inner_nested [post update] [a, a] |
-| by_reference.cpp:106:30:106:41 | inner_nested [post update] [a, a] | by_reference.cpp:114:16:114:27 | inner_nested [a, a] |
-| by_reference.cpp:108:15:108:24 | taint_a_ptr output argument | by_reference.cpp:108:24:108:24 | a [post update] [a] |
-| by_reference.cpp:108:15:108:24 | taint_a_ptr output argument [array content] | by_reference.cpp:108:15:108:24 | taint_a_ptr output argument |
-| by_reference.cpp:108:24:108:24 | a [post update] [a] | by_reference.cpp:116:16:116:16 | a [a] |
-| by_reference.cpp:110:14:110:25 | inner_nested [a, a] | by_reference.cpp:110:27:110:27 | a [a] |
-| by_reference.cpp:110:27:110:27 | a [a] | by_reference.cpp:110:27:110:27 | a |
-| by_reference.cpp:112:14:112:14 | a [a] | by_reference.cpp:112:14:112:14 | a |
-| by_reference.cpp:114:16:114:27 | inner_nested [a, a] | by_reference.cpp:114:29:114:29 | a [a] |
-| by_reference.cpp:114:29:114:29 | a [a] | by_reference.cpp:114:29:114:29 | a |
-| by_reference.cpp:116:16:116:16 | a [a] | by_reference.cpp:116:16:116:16 | a |
-| by_reference.cpp:122:21:122:38 | taint_inner_a_ref output argument [a] | by_reference.cpp:122:27:122:38 | inner_nested [post update] [a, a] |
-| by_reference.cpp:122:27:122:38 | inner_nested [post update] [a, a] | by_reference.cpp:130:14:130:25 | inner_nested [a, a] |
-| by_reference.cpp:124:15:124:21 | taint_a_ref output argument | by_reference.cpp:124:21:124:21 | a [post update] [a] |
-| by_reference.cpp:124:15:124:21 | taint_a_ref output argument [array content] | by_reference.cpp:124:15:124:21 | taint_a_ref output argument |
-| by_reference.cpp:124:21:124:21 | a [post update] [a] | by_reference.cpp:132:14:132:14 | a [a] |
-| by_reference.cpp:126:21:126:40 | taint_inner_a_ref output argument [a] | by_reference.cpp:126:29:126:40 | inner_nested [post update] [a, a] |
-| by_reference.cpp:126:29:126:40 | inner_nested [post update] [a, a] | by_reference.cpp:134:16:134:27 | inner_nested [a, a] |
-| by_reference.cpp:128:15:128:23 | taint_a_ref output argument | by_reference.cpp:128:23:128:23 | a [post update] [a] |
-| by_reference.cpp:128:15:128:23 | taint_a_ref output argument [array content] | by_reference.cpp:128:15:128:23 | taint_a_ref output argument |
-| by_reference.cpp:128:23:128:23 | a [post update] [a] | by_reference.cpp:136:16:136:16 | a [a] |
-| by_reference.cpp:130:14:130:25 | inner_nested [a, a] | by_reference.cpp:130:27:130:27 | a [a] |
-| by_reference.cpp:130:27:130:27 | a [a] | by_reference.cpp:130:27:130:27 | a |
-| by_reference.cpp:132:14:132:14 | a [a] | by_reference.cpp:132:14:132:14 | a |
-| by_reference.cpp:134:16:134:27 | inner_nested [a, a] | by_reference.cpp:134:29:134:29 | a [a] |
-| by_reference.cpp:134:29:134:29 | a [a] | by_reference.cpp:134:29:134:29 | a |
-| by_reference.cpp:136:16:136:16 | a [a] | by_reference.cpp:136:16:136:16 | a |
-| by_reference.cpp:140:3:140:27 | Chi [array content] | by_reference.cpp:145:15:145:16 | taint_a_ptr output argument [array content] |
-| by_reference.cpp:140:3:140:27 | ChiTotal [post update] [array content] | by_reference.cpp:140:3:140:27 | Chi [array content] |
-| by_reference.cpp:140:3:140:27 | ChiTotal [post update] [array content] | by_reference.cpp:145:15:145:16 | taint_a_ptr output argument [array content] |
-| by_reference.cpp:140:9:140:27 | (char *)... | by_reference.cpp:140:3:140:27 | ChiTotal [post update] [array content] |
-| by_reference.cpp:140:9:140:27 | (const char *)... | by_reference.cpp:140:3:140:27 | ChiTotal [post update] [array content] |
-| by_reference.cpp:140:16:140:25 | call to user_input | by_reference.cpp:140:3:140:27 | ChiTotal [post update] [array content] |
-| by_reference.cpp:145:15:145:16 | taint_a_ptr output argument | by_reference.cpp:146:8:146:8 | s |
-| by_reference.cpp:145:15:145:16 | taint_a_ptr output argument [array content] | by_reference.cpp:145:15:145:16 | taint_a_ptr output argument |
+| by_reference.cpp:96:8:96:17 | call to user_input | by_reference.cpp:96:3:96:19 | Chi [array content] |
+| by_reference.cpp:102:21:102:39 | Chi [a] | by_reference.cpp:110:27:110:27 | a |
+| by_reference.cpp:102:21:102:39 | taint_inner_a_ptr output argument [a] | by_reference.cpp:102:21:102:39 | Chi [a] |
+| by_reference.cpp:104:15:104:22 | Chi | by_reference.cpp:104:15:104:22 | Chi [a] |
+| by_reference.cpp:104:15:104:22 | Chi [a] | by_reference.cpp:112:14:112:14 | a |
+| by_reference.cpp:104:15:104:22 | taint_a_ptr output argument [array content] | by_reference.cpp:104:15:104:22 | Chi |
+| by_reference.cpp:106:21:106:41 | Chi [a] | by_reference.cpp:114:29:114:29 | a |
+| by_reference.cpp:106:21:106:41 | taint_inner_a_ptr output argument [a] | by_reference.cpp:106:21:106:41 | Chi [a] |
+| by_reference.cpp:108:15:108:24 | Chi | by_reference.cpp:108:15:108:24 | Chi [a] |
+| by_reference.cpp:108:15:108:24 | Chi [a] | by_reference.cpp:116:16:116:16 | a |
+| by_reference.cpp:108:15:108:24 | taint_a_ptr output argument [array content] | by_reference.cpp:108:15:108:24 | Chi |
+| by_reference.cpp:122:21:122:38 | Chi [a] | by_reference.cpp:130:27:130:27 | a |
+| by_reference.cpp:122:21:122:38 | taint_inner_a_ref output argument [a] | by_reference.cpp:122:21:122:38 | Chi [a] |
+| by_reference.cpp:124:15:124:21 | Chi | by_reference.cpp:124:15:124:21 | Chi [a] |
+| by_reference.cpp:124:15:124:21 | Chi [a] | by_reference.cpp:132:14:132:14 | a |
+| by_reference.cpp:124:15:124:21 | taint_a_ref output argument [array content] | by_reference.cpp:124:15:124:21 | Chi |
+| by_reference.cpp:126:21:126:40 | Chi [a] | by_reference.cpp:134:29:134:29 | a |
+| by_reference.cpp:126:21:126:40 | taint_inner_a_ref output argument [a] | by_reference.cpp:126:21:126:40 | Chi [a] |
+| by_reference.cpp:128:15:128:23 | Chi | by_reference.cpp:128:15:128:23 | Chi [a] |
+| by_reference.cpp:128:15:128:23 | Chi [a] | by_reference.cpp:136:16:136:16 | a |
+| by_reference.cpp:128:15:128:23 | taint_a_ref output argument [array content] | by_reference.cpp:128:15:128:23 | Chi |
| complex.cpp:40:17:40:17 | *b [a_] | complex.cpp:42:18:42:18 | call to a |
+| complex.cpp:40:17:40:17 | *b [b_] | complex.cpp:42:16:42:16 | Chi [b_] |
| complex.cpp:40:17:40:17 | *b [b_] | complex.cpp:42:16:42:16 | a output argument [b_] |
| complex.cpp:40:17:40:17 | *b [b_] | complex.cpp:43:18:43:18 | call to b |
-| complex.cpp:40:17:40:17 | *b [f, f, a_] | complex.cpp:42:10:42:14 | inner [f, f, a_] |
-| complex.cpp:40:17:40:17 | *b [f, f, b_] | complex.cpp:42:10:42:14 | inner [f, f, b_] |
-| complex.cpp:40:17:40:17 | *b [f, f, b_] | complex.cpp:42:16:42:16 | a output argument [f, f, b_] |
-| complex.cpp:40:17:40:17 | *b [f, f, b_] | complex.cpp:43:10:43:14 | inner [f, f, b_] |
-| complex.cpp:40:17:40:17 | *b [f, f, f, f, a_] | complex.cpp:42:10:42:14 | inner [f, f, f, f, a_] |
-| complex.cpp:42:10:42:14 | inner [f, f, a_] | complex.cpp:42:16:42:16 | f [f, a_] |
-| complex.cpp:42:10:42:14 | inner [f, f, b_] | complex.cpp:42:16:42:16 | f [f, b_] |
-| complex.cpp:42:10:42:14 | inner [f, f, f, f, a_] | complex.cpp:42:16:42:16 | f [f, f, f, a_] |
-| complex.cpp:42:10:42:14 | inner [post update] [f, f, b_] | complex.cpp:43:10:43:14 | inner [f, f, b_] |
-| complex.cpp:42:10:42:14 | inner [post update] [f, f, f, f, b_] | complex.cpp:43:10:43:14 | inner [f, f, f, f, b_] |
-| complex.cpp:42:16:42:16 | a output argument [b_] | complex.cpp:42:16:42:16 | f [post update] [f, b_] |
+| complex.cpp:42:16:42:16 | Chi [b_] | complex.cpp:43:18:43:18 | call to b |
+| complex.cpp:42:16:42:16 | a output argument [b_] | complex.cpp:42:16:42:16 | Chi [b_] |
| complex.cpp:42:16:42:16 | a output argument [b_] | complex.cpp:43:18:43:18 | call to b |
-| complex.cpp:42:16:42:16 | a output argument [f, f, b_] | complex.cpp:42:16:42:16 | f [post update] [f, f, f, b_] |
-| complex.cpp:42:16:42:16 | a output argument [f, f, b_] | complex.cpp:43:10:43:14 | inner [f, f, b_] |
-| complex.cpp:42:16:42:16 | f [f, a_] | complex.cpp:42:18:42:18 | call to a |
-| complex.cpp:42:16:42:16 | f [f, b_] | complex.cpp:42:16:42:16 | a output argument [b_] |
-| complex.cpp:42:16:42:16 | f [f, f, f, a_] | complex.cpp:42:10:42:14 | inner [f, f, a_] |
-| complex.cpp:42:16:42:16 | f [post update] [f, b_] | complex.cpp:42:10:42:14 | inner [post update] [f, f, b_] |
-| complex.cpp:42:16:42:16 | f [post update] [f, f, f, b_] | complex.cpp:42:10:42:14 | inner [post update] [f, f, f, f, b_] |
-| complex.cpp:43:10:43:14 | inner [f, f, b_] | complex.cpp:43:16:43:16 | f [f, b_] |
-| complex.cpp:43:10:43:14 | inner [f, f, f, f, b_] | complex.cpp:43:16:43:16 | f [f, f, f, b_] |
-| complex.cpp:43:16:43:16 | f [f, b_] | complex.cpp:43:18:43:18 | call to b |
-| complex.cpp:43:16:43:16 | f [f, f, f, b_] | complex.cpp:43:10:43:14 | inner [f, f, b_] |
-| complex.cpp:53:6:53:10 | inner [post update] [f, f, a_] | complex.cpp:40:17:40:17 | *b [f, f, a_] |
-| complex.cpp:53:12:53:12 | f [post update] [f, a_] | complex.cpp:53:6:53:10 | inner [post update] [f, f, a_] |
+| complex.cpp:53:12:53:12 | Chi [a_] | complex.cpp:40:17:40:17 | *b [a_] |
| complex.cpp:53:12:53:12 | setA output argument [a_] | complex.cpp:40:17:40:17 | *b [a_] |
-| complex.cpp:53:12:53:12 | setA output argument [a_] | complex.cpp:53:12:53:12 | f [post update] [f, a_] |
+| complex.cpp:53:12:53:12 | setA output argument [a_] | complex.cpp:53:12:53:12 | Chi [a_] |
| complex.cpp:53:19:53:28 | call to user_input | complex.cpp:53:12:53:12 | setA output argument [a_] |
-| complex.cpp:54:6:54:10 | inner [post update] [f, f, b_] | complex.cpp:40:17:40:17 | *b [f, f, b_] |
-| complex.cpp:54:12:54:12 | f [post update] [f, b_] | complex.cpp:54:6:54:10 | inner [post update] [f, f, b_] |
+| complex.cpp:54:12:54:12 | Chi [b_] | complex.cpp:40:17:40:17 | *b [b_] |
| complex.cpp:54:12:54:12 | setB output argument [b_] | complex.cpp:40:17:40:17 | *b [b_] |
-| complex.cpp:54:12:54:12 | setB output argument [b_] | complex.cpp:54:12:54:12 | f [post update] [f, b_] |
+| complex.cpp:54:12:54:12 | setB output argument [b_] | complex.cpp:54:12:54:12 | Chi [b_] |
| complex.cpp:54:19:54:28 | call to user_input | complex.cpp:54:12:54:12 | setB output argument [b_] |
-| complex.cpp:55:6:55:10 | inner [post update] [f, f, a_] | complex.cpp:40:17:40:17 | *b [f, f, a_] |
-| complex.cpp:55:6:55:10 | inner [post update] [f, f, a_] | complex.cpp:56:6:56:10 | inner [f, f, a_] |
-| complex.cpp:55:6:55:10 | inner [post update] [f, f, a_] | complex.cpp:56:12:56:12 | setB output argument [f, f, a_] |
-| complex.cpp:55:12:55:12 | f [post update] [f, a_] | complex.cpp:55:6:55:10 | inner [post update] [f, f, a_] |
+| complex.cpp:55:12:55:12 | Chi [a_] | complex.cpp:40:17:40:17 | *b [a_] |
+| complex.cpp:55:12:55:12 | Chi [a_] | complex.cpp:56:12:56:12 | Chi [a_] |
+| complex.cpp:55:12:55:12 | Chi [a_] | complex.cpp:56:12:56:12 | setB output argument [a_] |
| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:40:17:40:17 | *b [a_] |
-| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:55:12:55:12 | f [post update] [f, a_] |
+| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:55:12:55:12 | Chi [a_] |
+| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:56:12:56:12 | Chi [a_] |
| complex.cpp:55:12:55:12 | setA output argument [a_] | complex.cpp:56:12:56:12 | setB output argument [a_] |
| complex.cpp:55:19:55:28 | call to user_input | complex.cpp:55:12:55:12 | setA output argument [a_] |
-| complex.cpp:56:6:56:10 | inner [f, f, a_] | complex.cpp:56:12:56:12 | f [f, a_] |
-| complex.cpp:56:6:56:10 | inner [post update] [f, f, a_] | complex.cpp:40:17:40:17 | *b [f, f, a_] |
-| complex.cpp:56:6:56:10 | inner [post update] [f, f, b_] | complex.cpp:40:17:40:17 | *b [f, f, b_] |
-| complex.cpp:56:6:56:10 | inner [post update] [f, f, f, f, a_] | complex.cpp:40:17:40:17 | *b [f, f, f, f, a_] |
-| complex.cpp:56:12:56:12 | f [f, a_] | complex.cpp:56:12:56:12 | setB output argument [a_] |
-| complex.cpp:56:12:56:12 | f [post update] [f, a_] | complex.cpp:56:6:56:10 | inner [post update] [f, f, a_] |
-| complex.cpp:56:12:56:12 | f [post update] [f, b_] | complex.cpp:56:6:56:10 | inner [post update] [f, f, b_] |
-| complex.cpp:56:12:56:12 | f [post update] [f, f, f, a_] | complex.cpp:56:6:56:10 | inner [post update] [f, f, f, f, a_] |
+| complex.cpp:56:12:56:12 | Chi [a_] | complex.cpp:40:17:40:17 | *b [a_] |
+| complex.cpp:56:12:56:12 | Chi [b_] | complex.cpp:40:17:40:17 | *b [b_] |
| complex.cpp:56:12:56:12 | setB output argument [a_] | complex.cpp:40:17:40:17 | *b [a_] |
-| complex.cpp:56:12:56:12 | setB output argument [a_] | complex.cpp:56:12:56:12 | f [post update] [f, a_] |
+| complex.cpp:56:12:56:12 | setB output argument [a_] | complex.cpp:56:12:56:12 | Chi [a_] |
| complex.cpp:56:12:56:12 | setB output argument [b_] | complex.cpp:40:17:40:17 | *b [b_] |
-| complex.cpp:56:12:56:12 | setB output argument [b_] | complex.cpp:56:12:56:12 | f [post update] [f, b_] |
-| complex.cpp:56:12:56:12 | setB output argument [f, f, a_] | complex.cpp:40:17:40:17 | *b [f, f, a_] |
-| complex.cpp:56:12:56:12 | setB output argument [f, f, a_] | complex.cpp:56:12:56:12 | f [post update] [f, f, f, a_] |
+| complex.cpp:56:12:56:12 | setB output argument [b_] | complex.cpp:56:12:56:12 | Chi [b_] |
| complex.cpp:56:19:56:28 | call to user_input | complex.cpp:56:12:56:12 | setB output argument [b_] |
| constructors.cpp:26:15:26:15 | *f [a_] | constructors.cpp:28:12:28:12 | call to a |
| constructors.cpp:26:15:26:15 | *f [b_] | constructors.cpp:28:10:28:10 | a output argument [b_] |
@@ -288,25 +182,21 @@ edges
| simple.cpp:42:5:42:5 | setB output argument [a_] | simple.cpp:26:15:26:15 | *f [a_] |
| simple.cpp:42:5:42:5 | setB output argument [b_] | simple.cpp:26:15:26:15 | *f [b_] |
| simple.cpp:42:12:42:21 | call to user_input | simple.cpp:42:5:42:5 | setB output argument [b_] |
-| simple.cpp:65:7:65:7 | i [post update] [i] | simple.cpp:67:13:67:13 | i [i] |
-| simple.cpp:65:11:65:20 | call to user_input | simple.cpp:65:7:65:7 | i [post update] [i] |
-| simple.cpp:67:13:67:13 | i [i] | simple.cpp:67:13:67:13 | i |
-| simple.cpp:83:9:83:10 | f2 [post update] [f1, f1] | simple.cpp:84:14:84:20 | call to getf2f1 |
-| simple.cpp:83:12:83:13 | f1 [post update] [f1] | simple.cpp:83:9:83:10 | f2 [post update] [f1, f1] |
-| simple.cpp:83:17:83:26 | call to user_input | simple.cpp:83:12:83:13 | f1 [post update] [f1] |
-| simple.cpp:92:7:92:7 | i [post update] [i] | simple.cpp:94:13:94:13 | i [i] |
-| simple.cpp:92:11:92:20 | call to user_input | simple.cpp:92:7:92:7 | i [post update] [i] |
-| simple.cpp:94:13:94:13 | i [i] | simple.cpp:94:13:94:13 | i |
-| struct_init.c:14:24:14:25 | *ab [a] | struct_init.c:15:12:15:12 | a [a] |
-| struct_init.c:15:12:15:12 | a [a] | struct_init.c:15:12:15:12 | a |
-| struct_init.c:20:17:20:36 | a [post update] [a] | struct_init.c:14:24:14:25 | *ab [a] |
-| struct_init.c:20:20:20:29 | call to user_input | struct_init.c:20:17:20:36 | a [post update] [a] |
+| simple.cpp:65:5:65:22 | Store [i] | simple.cpp:66:12:66:12 | Store [i] |
+| simple.cpp:65:11:65:20 | call to user_input | simple.cpp:65:5:65:22 | Store [i] |
+| simple.cpp:66:12:66:12 | Store [i] | simple.cpp:67:13:67:13 | i |
+| simple.cpp:83:9:83:28 | Chi [f1] | simple.cpp:84:14:84:20 | call to getf2f1 |
+| simple.cpp:83:17:83:26 | call to user_input | simple.cpp:83:9:83:28 | Chi [f1] |
+| simple.cpp:92:5:92:22 | Store [i] | simple.cpp:93:20:93:20 | Store [i] |
+| simple.cpp:92:11:92:20 | call to user_input | simple.cpp:92:5:92:22 | Store [i] |
+| simple.cpp:93:20:93:20 | Store [i] | simple.cpp:94:13:94:13 | i |
+| struct_init.c:14:24:14:25 | *ab [a] | struct_init.c:15:12:15:12 | a |
+| struct_init.c:20:20:20:29 | Chi [a] | struct_init.c:14:24:14:25 | *ab [a] |
+| struct_init.c:20:20:20:29 | call to user_input | struct_init.c:20:20:20:29 | Chi [a] |
| struct_init.c:20:20:20:29 | call to user_input | struct_init.c:22:11:22:11 | a |
-| struct_init.c:26:23:29:3 | nestedAB [post update] [nestedAB, a] | struct_init.c:36:17:36:24 | nestedAB [nestedAB, a] |
-| struct_init.c:27:5:27:23 | a [post update] [a] | struct_init.c:26:23:29:3 | nestedAB [post update] [nestedAB, a] |
-| struct_init.c:27:7:27:16 | call to user_input | struct_init.c:27:5:27:23 | a [post update] [a] |
+| struct_init.c:27:7:27:16 | Chi [a] | struct_init.c:14:24:14:25 | *ab [a] |
+| struct_init.c:27:7:27:16 | call to user_input | struct_init.c:27:7:27:16 | Chi [a] |
| struct_init.c:27:7:27:16 | call to user_input | struct_init.c:31:23:31:23 | a |
-| struct_init.c:36:17:36:24 | nestedAB [nestedAB, a] | struct_init.c:14:24:14:25 | *ab [a] |
nodes
| A.cpp:55:5:55:5 | set output argument [c] | semmle.label | set output argument [c] |
| A.cpp:55:12:55:19 | (C *)... | semmle.label | (C *)... |
@@ -316,75 +206,66 @@ nodes
| A.cpp:57:17:57:23 | new | semmle.label | new |
| A.cpp:57:28:57:30 | call to get | semmle.label | call to get |
| A.cpp:98:12:98:18 | new | semmle.label | new |
-| A.cpp:100:9:100:9 | a [post update] [a] | semmle.label | a [post update] [a] |
+| A.cpp:100:5:100:13 | Chi [a] | semmle.label | Chi [a] |
| A.cpp:103:14:103:14 | *c [a] | semmle.label | *c [a] |
| A.cpp:107:16:107:16 | a | semmle.label | a |
-| A.cpp:107:16:107:16 | a [a] | semmle.label | a [a] |
| A.cpp:126:5:126:5 | Chi [c] | semmle.label | Chi [c] |
| A.cpp:126:5:126:5 | set output argument [c] | semmle.label | set output argument [c] |
| A.cpp:126:12:126:18 | new | semmle.label | new |
+| A.cpp:131:8:131:8 | Chi [c] | semmle.label | Chi [c] |
| A.cpp:131:8:131:8 | f7 output argument [c] | semmle.label | f7 output argument [c] |
| A.cpp:132:13:132:13 | c | semmle.label | c |
-| A.cpp:132:13:132:13 | c [c] | semmle.label | c [c] |
| A.cpp:142:7:142:20 | Chi [c] | semmle.label | Chi [c] |
-| A.cpp:142:10:142:10 | c [post update] [c] | semmle.label | c [post update] [c] |
| A.cpp:142:14:142:20 | new | semmle.label | new |
| A.cpp:143:7:143:31 | Chi [b] | semmle.label | Chi [b] |
-| A.cpp:143:13:143:13 | b [post update] [b] | semmle.label | b [post update] [b] |
| A.cpp:143:25:143:31 | new | semmle.label | new |
| A.cpp:150:12:150:18 | new | semmle.label | new |
+| A.cpp:151:12:151:24 | Chi [b] | semmle.label | Chi [b] |
| A.cpp:151:12:151:24 | D output argument [b] | semmle.label | D output argument [b] |
+| A.cpp:151:18:151:18 | Chi [c] | semmle.label | Chi [c] |
| A.cpp:151:18:151:18 | D output argument [c] | semmle.label | D output argument [c] |
| A.cpp:152:13:152:13 | b | semmle.label | b |
-| A.cpp:152:13:152:13 | b [b] | semmle.label | b [b] |
| A.cpp:154:13:154:13 | c | semmle.label | c |
-| A.cpp:154:13:154:13 | c [c] | semmle.label | c [c] |
| C.cpp:18:12:18:18 | C output argument [s1] | semmle.label | C output argument [s1] |
| C.cpp:18:12:18:18 | C output argument [s3] | semmle.label | C output argument [s3] |
-| C.cpp:22:9:22:22 | s1 [post update] [s1] | semmle.label | s1 [post update] [s1] |
+| C.cpp:22:12:22:21 | Chi [s1] | semmle.label | Chi [s1] |
| C.cpp:22:12:22:21 | new | semmle.label | new |
| C.cpp:24:5:24:25 | Chi [s1] | semmle.label | Chi [s1] |
| C.cpp:24:5:24:25 | Chi [s3] | semmle.label | Chi [s3] |
-| C.cpp:24:11:24:12 | s3 [post update] [s3] | semmle.label | s3 [post update] [s3] |
| C.cpp:24:16:24:25 | new | semmle.label | new |
| C.cpp:27:8:27:11 | *#this [s1] | semmle.label | *#this [s1] |
| C.cpp:27:8:27:11 | *#this [s3] | semmle.label | *#this [s3] |
| C.cpp:29:10:29:11 | s1 | semmle.label | s1 |
-| C.cpp:29:10:29:11 | s1 [s1] | semmle.label | s1 [s1] |
| C.cpp:31:10:31:11 | s3 | semmle.label | s3 |
-| C.cpp:31:10:31:11 | s3 [s3] | semmle.label | s3 [s3] |
| aliasing.cpp:9:3:9:22 | Chi [m1] | semmle.label | Chi [m1] |
-| aliasing.cpp:9:6:9:7 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
| aliasing.cpp:9:11:9:20 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:13:3:13:21 | Chi [m1] | semmle.label | Chi [m1] |
-| aliasing.cpp:13:5:13:6 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
| aliasing.cpp:13:10:13:19 | call to user_input | semmle.label | call to user_input |
+| aliasing.cpp:25:17:25:19 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:25:17:25:19 | pointerSetter output argument [m1] | semmle.label | pointerSetter output argument [m1] |
+| aliasing.cpp:26:19:26:20 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:26:19:26:20 | referenceSetter output argument [m1] | semmle.label | referenceSetter output argument [m1] |
| aliasing.cpp:29:11:29:12 | m1 | semmle.label | m1 |
-| aliasing.cpp:29:11:29:12 | m1 [m1] | semmle.label | m1 [m1] |
| aliasing.cpp:30:11:30:12 | m1 | semmle.label | m1 |
-| aliasing.cpp:30:11:30:12 | m1 [m1] | semmle.label | m1 [m1] |
| aliasing.cpp:37:13:37:22 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:38:11:38:12 | m1 | semmle.label | m1 |
| aliasing.cpp:42:11:42:20 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:43:13:43:14 | m1 | semmle.label | m1 |
-| aliasing.cpp:60:6:60:7 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
+| aliasing.cpp:60:3:60:22 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:60:11:60:20 | call to user_input | semmle.label | call to user_input |
+| aliasing.cpp:61:13:61:14 | Store [m1] | semmle.label | Store [m1] |
| aliasing.cpp:62:14:62:15 | m1 | semmle.label | m1 |
-| aliasing.cpp:62:14:62:15 | m1 [m1] | semmle.label | m1 [m1] |
| aliasing.cpp:79:11:79:20 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:80:12:80:13 | m1 | semmle.label | m1 |
| aliasing.cpp:86:10:86:19 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:87:12:87:13 | m1 | semmle.label | m1 |
| aliasing.cpp:92:12:92:21 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:93:12:93:13 | m1 | semmle.label | m1 |
-| aliasing.cpp:98:5:98:6 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
+| aliasing.cpp:98:3:98:21 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:98:10:98:19 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:100:14:100:14 | Store [m1] | semmle.label | Store [m1] |
| aliasing.cpp:102:8:102:10 | * ... | semmle.label | * ... |
| aliasing.cpp:106:3:106:20 | Chi [array content] | semmle.label | Chi [array content] |
-| aliasing.cpp:106:3:106:20 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
| aliasing.cpp:106:9:106:18 | call to user_input | semmle.label | call to user_input |
| aliasing.cpp:121:15:121:16 | Chi [array content] | semmle.label | Chi [array content] |
| aliasing.cpp:121:15:121:16 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
@@ -404,42 +285,19 @@ nodes
| aliasing.cpp:164:15:164:20 | Chi [array content] | semmle.label | Chi [array content] |
| aliasing.cpp:164:15:164:20 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
| aliasing.cpp:165:8:165:16 | access to array | semmle.label | access to array |
-| aliasing.cpp:175:15:175:22 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
+| aliasing.cpp:175:15:175:22 | Chi | semmle.label | Chi |
+| aliasing.cpp:175:15:175:22 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:175:15:175:22 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| aliasing.cpp:175:19:175:19 | s [post update] [s, m1] | semmle.label | s [post update] [s, m1] |
-| aliasing.cpp:175:21:175:22 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
-| aliasing.cpp:176:11:176:11 | s [s, m1] | semmle.label | s [s, m1] |
| aliasing.cpp:176:13:176:14 | m1 | semmle.label | m1 |
-| aliasing.cpp:176:13:176:14 | m1 [m1] | semmle.label | m1 [m1] |
-| aliasing.cpp:187:15:187:22 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
+| aliasing.cpp:187:15:187:22 | Chi | semmle.label | Chi |
+| aliasing.cpp:187:15:187:22 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:187:15:187:22 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| aliasing.cpp:187:19:187:19 | s [post update] [s, m1] | semmle.label | s [post update] [s, m1] |
-| aliasing.cpp:187:21:187:22 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
-| aliasing.cpp:189:13:189:13 | s [s, m1] | semmle.label | s [s, m1] |
+| aliasing.cpp:188:13:188:14 | Store [m1] | semmle.label | Store [m1] |
| aliasing.cpp:189:15:189:16 | m1 | semmle.label | m1 |
-| aliasing.cpp:189:15:189:16 | m1 [m1] | semmle.label | m1 [m1] |
-| aliasing.cpp:200:15:200:24 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
+| aliasing.cpp:200:15:200:24 | Chi | semmle.label | Chi |
+| aliasing.cpp:200:15:200:24 | Chi [m1] | semmle.label | Chi [m1] |
| aliasing.cpp:200:15:200:24 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| aliasing.cpp:200:21:200:21 | s [post update] [s, m1] | semmle.label | s [post update] [s, m1] |
-| aliasing.cpp:200:23:200:24 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
-| aliasing.cpp:201:13:201:13 | s [s, m1] | semmle.label | s [s, m1] |
| aliasing.cpp:201:15:201:16 | m1 | semmle.label | m1 |
-| aliasing.cpp:201:15:201:16 | m1 [m1] | semmle.label | m1 [m1] |
-| aliasing.cpp:211:6:211:6 | s [post update] [s, m1] | semmle.label | s [post update] [s, m1] |
-| aliasing.cpp:211:8:211:9 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
-| aliasing.cpp:211:13:211:22 | call to user_input | semmle.label | call to user_input |
-| aliasing.cpp:212:12:212:12 | s [m1] | semmle.label | s [m1] |
-| aliasing.cpp:212:12:212:12 | s [s, m1] | semmle.label | s [s, m1] |
-| aliasing.cpp:213:10:213:11 | m1 | semmle.label | m1 |
-| aliasing.cpp:213:10:213:11 | m1 [m1] | semmle.label | m1 [m1] |
-| aliasing.cpp:225:15:225:22 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
-| aliasing.cpp:225:15:225:22 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| aliasing.cpp:225:19:225:19 | s [post update] [s, m1] | semmle.label | s [post update] [s, m1] |
-| aliasing.cpp:225:21:225:22 | m1 [post update] [m1] | semmle.label | m1 [post update] [m1] |
-| aliasing.cpp:226:12:226:12 | s [m1] | semmle.label | s [m1] |
-| aliasing.cpp:226:12:226:12 | s [s, m1] | semmle.label | s [s, m1] |
-| aliasing.cpp:227:10:227:11 | m1 | semmle.label | m1 |
-| aliasing.cpp:227:10:227:11 | m1 [m1] | semmle.label | m1 [m1] |
| arrays.cpp:6:12:6:21 | call to user_input | semmle.label | call to user_input |
| arrays.cpp:7:8:7:13 | access to array | semmle.label | access to array |
| arrays.cpp:9:8:9:11 | * ... | semmle.label | * ... |
@@ -461,111 +319,60 @@ nodes
| by_reference.cpp:68:21:68:30 | call to user_input | semmle.label | call to user_input |
| by_reference.cpp:69:8:69:20 | call to nonMemberGetA | semmle.label | call to nonMemberGetA |
| by_reference.cpp:84:3:84:25 | Chi [a] | semmle.label | Chi [a] |
-| by_reference.cpp:84:10:84:10 | a [post update] [a] | semmle.label | a [post update] [a] |
| by_reference.cpp:84:14:84:23 | call to user_input | semmle.label | call to user_input |
| by_reference.cpp:88:3:88:24 | Chi [a] | semmle.label | Chi [a] |
-| by_reference.cpp:88:9:88:9 | a [post update] [a] | semmle.label | a [post update] [a] |
| by_reference.cpp:88:13:88:22 | call to user_input | semmle.label | call to user_input |
| by_reference.cpp:92:3:92:20 | Chi [array content] | semmle.label | Chi [array content] |
-| by_reference.cpp:92:3:92:20 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
| by_reference.cpp:92:9:92:18 | call to user_input | semmle.label | call to user_input |
| by_reference.cpp:96:3:96:19 | Chi [array content] | semmle.label | Chi [array content] |
-| by_reference.cpp:96:3:96:19 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
| by_reference.cpp:96:8:96:17 | call to user_input | semmle.label | call to user_input |
+| by_reference.cpp:102:21:102:39 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:102:21:102:39 | taint_inner_a_ptr output argument [a] | semmle.label | taint_inner_a_ptr output argument [a] |
-| by_reference.cpp:102:28:102:39 | inner_nested [post update] [a, a] | semmle.label | inner_nested [post update] [a, a] |
-| by_reference.cpp:104:15:104:22 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
+| by_reference.cpp:104:15:104:22 | Chi | semmle.label | Chi |
+| by_reference.cpp:104:15:104:22 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:104:15:104:22 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| by_reference.cpp:104:22:104:22 | a [post update] [a] | semmle.label | a [post update] [a] |
+| by_reference.cpp:106:21:106:41 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:106:21:106:41 | taint_inner_a_ptr output argument [a] | semmle.label | taint_inner_a_ptr output argument [a] |
-| by_reference.cpp:106:30:106:41 | inner_nested [post update] [a, a] | semmle.label | inner_nested [post update] [a, a] |
-| by_reference.cpp:108:15:108:24 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
+| by_reference.cpp:108:15:108:24 | Chi | semmle.label | Chi |
+| by_reference.cpp:108:15:108:24 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:108:15:108:24 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| by_reference.cpp:108:24:108:24 | a [post update] [a] | semmle.label | a [post update] [a] |
-| by_reference.cpp:110:14:110:25 | inner_nested [a, a] | semmle.label | inner_nested [a, a] |
| by_reference.cpp:110:27:110:27 | a | semmle.label | a |
-| by_reference.cpp:110:27:110:27 | a [a] | semmle.label | a [a] |
| by_reference.cpp:112:14:112:14 | a | semmle.label | a |
-| by_reference.cpp:112:14:112:14 | a [a] | semmle.label | a [a] |
-| by_reference.cpp:114:16:114:27 | inner_nested [a, a] | semmle.label | inner_nested [a, a] |
| by_reference.cpp:114:29:114:29 | a | semmle.label | a |
-| by_reference.cpp:114:29:114:29 | a [a] | semmle.label | a [a] |
| by_reference.cpp:116:16:116:16 | a | semmle.label | a |
-| by_reference.cpp:116:16:116:16 | a [a] | semmle.label | a [a] |
+| by_reference.cpp:122:21:122:38 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:122:21:122:38 | taint_inner_a_ref output argument [a] | semmle.label | taint_inner_a_ref output argument [a] |
-| by_reference.cpp:122:27:122:38 | inner_nested [post update] [a, a] | semmle.label | inner_nested [post update] [a, a] |
-| by_reference.cpp:124:15:124:21 | taint_a_ref output argument | semmle.label | taint_a_ref output argument |
+| by_reference.cpp:124:15:124:21 | Chi | semmle.label | Chi |
+| by_reference.cpp:124:15:124:21 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:124:15:124:21 | taint_a_ref output argument [array content] | semmle.label | taint_a_ref output argument [array content] |
-| by_reference.cpp:124:21:124:21 | a [post update] [a] | semmle.label | a [post update] [a] |
+| by_reference.cpp:126:21:126:40 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:126:21:126:40 | taint_inner_a_ref output argument [a] | semmle.label | taint_inner_a_ref output argument [a] |
-| by_reference.cpp:126:29:126:40 | inner_nested [post update] [a, a] | semmle.label | inner_nested [post update] [a, a] |
-| by_reference.cpp:128:15:128:23 | taint_a_ref output argument | semmle.label | taint_a_ref output argument |
+| by_reference.cpp:128:15:128:23 | Chi | semmle.label | Chi |
+| by_reference.cpp:128:15:128:23 | Chi [a] | semmle.label | Chi [a] |
| by_reference.cpp:128:15:128:23 | taint_a_ref output argument [array content] | semmle.label | taint_a_ref output argument [array content] |
-| by_reference.cpp:128:23:128:23 | a [post update] [a] | semmle.label | a [post update] [a] |
-| by_reference.cpp:130:14:130:25 | inner_nested [a, a] | semmle.label | inner_nested [a, a] |
| by_reference.cpp:130:27:130:27 | a | semmle.label | a |
-| by_reference.cpp:130:27:130:27 | a [a] | semmle.label | a [a] |
| by_reference.cpp:132:14:132:14 | a | semmle.label | a |
-| by_reference.cpp:132:14:132:14 | a [a] | semmle.label | a [a] |
-| by_reference.cpp:134:16:134:27 | inner_nested [a, a] | semmle.label | inner_nested [a, a] |
| by_reference.cpp:134:29:134:29 | a | semmle.label | a |
-| by_reference.cpp:134:29:134:29 | a [a] | semmle.label | a [a] |
| by_reference.cpp:136:16:136:16 | a | semmle.label | a |
-| by_reference.cpp:136:16:136:16 | a [a] | semmle.label | a [a] |
-| by_reference.cpp:140:3:140:27 | Chi [array content] | semmle.label | Chi [array content] |
-| by_reference.cpp:140:3:140:27 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
-| by_reference.cpp:140:9:140:27 | (char *)... | semmle.label | (char *)... |
-| by_reference.cpp:140:9:140:27 | (const char *)... | semmle.label | (const char *)... |
-| by_reference.cpp:140:16:140:25 | call to user_input | semmle.label | call to user_input |
-| by_reference.cpp:145:15:145:16 | taint_a_ptr output argument | semmle.label | taint_a_ptr output argument |
-| by_reference.cpp:145:15:145:16 | taint_a_ptr output argument [array content] | semmle.label | taint_a_ptr output argument [array content] |
-| by_reference.cpp:146:8:146:8 | s | semmle.label | s |
| complex.cpp:40:17:40:17 | *b [a_] | semmle.label | *b [a_] |
| complex.cpp:40:17:40:17 | *b [b_] | semmle.label | *b [b_] |
-| complex.cpp:40:17:40:17 | *b [f, f, a_] | semmle.label | *b [f, f, a_] |
-| complex.cpp:40:17:40:17 | *b [f, f, b_] | semmle.label | *b [f, f, b_] |
-| complex.cpp:40:17:40:17 | *b [f, f, f, f, a_] | semmle.label | *b [f, f, f, f, a_] |
-| complex.cpp:42:10:42:14 | inner [f, f, a_] | semmle.label | inner [f, f, a_] |
-| complex.cpp:42:10:42:14 | inner [f, f, b_] | semmle.label | inner [f, f, b_] |
-| complex.cpp:42:10:42:14 | inner [f, f, f, f, a_] | semmle.label | inner [f, f, f, f, a_] |
-| complex.cpp:42:10:42:14 | inner [post update] [f, f, b_] | semmle.label | inner [post update] [f, f, b_] |
-| complex.cpp:42:10:42:14 | inner [post update] [f, f, f, f, b_] | semmle.label | inner [post update] [f, f, f, f, b_] |
+| complex.cpp:42:16:42:16 | Chi [b_] | semmle.label | Chi [b_] |
| complex.cpp:42:16:42:16 | a output argument [b_] | semmle.label | a output argument [b_] |
-| complex.cpp:42:16:42:16 | a output argument [f, f, b_] | semmle.label | a output argument [f, f, b_] |
-| complex.cpp:42:16:42:16 | f [f, a_] | semmle.label | f [f, a_] |
-| complex.cpp:42:16:42:16 | f [f, b_] | semmle.label | f [f, b_] |
-| complex.cpp:42:16:42:16 | f [f, f, f, a_] | semmle.label | f [f, f, f, a_] |
-| complex.cpp:42:16:42:16 | f [post update] [f, b_] | semmle.label | f [post update] [f, b_] |
-| complex.cpp:42:16:42:16 | f [post update] [f, f, f, b_] | semmle.label | f [post update] [f, f, f, b_] |
| complex.cpp:42:18:42:18 | call to a | semmle.label | call to a |
-| complex.cpp:43:10:43:14 | inner [f, f, b_] | semmle.label | inner [f, f, b_] |
-| complex.cpp:43:10:43:14 | inner [f, f, f, f, b_] | semmle.label | inner [f, f, f, f, b_] |
-| complex.cpp:43:16:43:16 | f [f, b_] | semmle.label | f [f, b_] |
-| complex.cpp:43:16:43:16 | f [f, f, f, b_] | semmle.label | f [f, f, f, b_] |
| complex.cpp:43:18:43:18 | call to b | semmle.label | call to b |
-| complex.cpp:53:6:53:10 | inner [post update] [f, f, a_] | semmle.label | inner [post update] [f, f, a_] |
-| complex.cpp:53:12:53:12 | f [post update] [f, a_] | semmle.label | f [post update] [f, a_] |
+| complex.cpp:53:12:53:12 | Chi [a_] | semmle.label | Chi [a_] |
| complex.cpp:53:12:53:12 | setA output argument [a_] | semmle.label | setA output argument [a_] |
| complex.cpp:53:19:53:28 | call to user_input | semmle.label | call to user_input |
-| complex.cpp:54:6:54:10 | inner [post update] [f, f, b_] | semmle.label | inner [post update] [f, f, b_] |
-| complex.cpp:54:12:54:12 | f [post update] [f, b_] | semmle.label | f [post update] [f, b_] |
+| complex.cpp:54:12:54:12 | Chi [b_] | semmle.label | Chi [b_] |
| complex.cpp:54:12:54:12 | setB output argument [b_] | semmle.label | setB output argument [b_] |
| complex.cpp:54:19:54:28 | call to user_input | semmle.label | call to user_input |
-| complex.cpp:55:6:55:10 | inner [post update] [f, f, a_] | semmle.label | inner [post update] [f, f, a_] |
-| complex.cpp:55:12:55:12 | f [post update] [f, a_] | semmle.label | f [post update] [f, a_] |
+| complex.cpp:55:12:55:12 | Chi [a_] | semmle.label | Chi [a_] |
| complex.cpp:55:12:55:12 | setA output argument [a_] | semmle.label | setA output argument [a_] |
| complex.cpp:55:19:55:28 | call to user_input | semmle.label | call to user_input |
-| complex.cpp:56:6:56:10 | inner [f, f, a_] | semmle.label | inner [f, f, a_] |
-| complex.cpp:56:6:56:10 | inner [post update] [f, f, a_] | semmle.label | inner [post update] [f, f, a_] |
-| complex.cpp:56:6:56:10 | inner [post update] [f, f, b_] | semmle.label | inner [post update] [f, f, b_] |
-| complex.cpp:56:6:56:10 | inner [post update] [f, f, f, f, a_] | semmle.label | inner [post update] [f, f, f, f, a_] |
-| complex.cpp:56:12:56:12 | f [f, a_] | semmle.label | f [f, a_] |
-| complex.cpp:56:12:56:12 | f [post update] [f, a_] | semmle.label | f [post update] [f, a_] |
-| complex.cpp:56:12:56:12 | f [post update] [f, b_] | semmle.label | f [post update] [f, b_] |
-| complex.cpp:56:12:56:12 | f [post update] [f, f, f, a_] | semmle.label | f [post update] [f, f, f, a_] |
+| complex.cpp:56:12:56:12 | Chi [a_] | semmle.label | Chi [a_] |
+| complex.cpp:56:12:56:12 | Chi [b_] | semmle.label | Chi [b_] |
| complex.cpp:56:12:56:12 | setB output argument [a_] | semmle.label | setB output argument [a_] |
| complex.cpp:56:12:56:12 | setB output argument [b_] | semmle.label | setB output argument [b_] |
-| complex.cpp:56:12:56:12 | setB output argument [f, f, a_] | semmle.label | setB output argument [f, f, a_] |
| complex.cpp:56:19:56:28 | call to user_input | semmle.label | call to user_input |
| constructors.cpp:26:15:26:15 | *f [a_] | semmle.label | *f [a_] |
| constructors.cpp:26:15:26:15 | *f [b_] | semmle.label | *f [b_] |
@@ -594,29 +401,25 @@ nodes
| simple.cpp:42:5:42:5 | setB output argument [a_] | semmle.label | setB output argument [a_] |
| simple.cpp:42:5:42:5 | setB output argument [b_] | semmle.label | setB output argument [b_] |
| simple.cpp:42:12:42:21 | call to user_input | semmle.label | call to user_input |
-| simple.cpp:65:7:65:7 | i [post update] [i] | semmle.label | i [post update] [i] |
+| simple.cpp:65:5:65:22 | Store [i] | semmle.label | Store [i] |
| simple.cpp:65:11:65:20 | call to user_input | semmle.label | call to user_input |
+| simple.cpp:66:12:66:12 | Store [i] | semmle.label | Store [i] |
| simple.cpp:67:13:67:13 | i | semmle.label | i |
-| simple.cpp:67:13:67:13 | i [i] | semmle.label | i [i] |
-| simple.cpp:83:9:83:10 | f2 [post update] [f1, f1] | semmle.label | f2 [post update] [f1, f1] |
-| simple.cpp:83:12:83:13 | f1 [post update] [f1] | semmle.label | f1 [post update] [f1] |
+| simple.cpp:83:9:83:28 | Chi [f1] | semmle.label | Chi [f1] |
| simple.cpp:83:17:83:26 | call to user_input | semmle.label | call to user_input |
| simple.cpp:84:14:84:20 | call to getf2f1 | semmle.label | call to getf2f1 |
-| simple.cpp:92:7:92:7 | i [post update] [i] | semmle.label | i [post update] [i] |
+| simple.cpp:92:5:92:22 | Store [i] | semmle.label | Store [i] |
| simple.cpp:92:11:92:20 | call to user_input | semmle.label | call to user_input |
+| simple.cpp:93:20:93:20 | Store [i] | semmle.label | Store [i] |
| simple.cpp:94:13:94:13 | i | semmle.label | i |
-| simple.cpp:94:13:94:13 | i [i] | semmle.label | i [i] |
| struct_init.c:14:24:14:25 | *ab [a] | semmle.label | *ab [a] |
| struct_init.c:15:12:15:12 | a | semmle.label | a |
-| struct_init.c:15:12:15:12 | a [a] | semmle.label | a [a] |
-| struct_init.c:20:17:20:36 | a [post update] [a] | semmle.label | a [post update] [a] |
+| struct_init.c:20:20:20:29 | Chi [a] | semmle.label | Chi [a] |
| struct_init.c:20:20:20:29 | call to user_input | semmle.label | call to user_input |
| struct_init.c:22:11:22:11 | a | semmle.label | a |
-| struct_init.c:26:23:29:3 | nestedAB [post update] [nestedAB, a] | semmle.label | nestedAB [post update] [nestedAB, a] |
-| struct_init.c:27:5:27:23 | a [post update] [a] | semmle.label | a [post update] [a] |
+| struct_init.c:27:7:27:16 | Chi [a] | semmle.label | Chi [a] |
| struct_init.c:27:7:27:16 | call to user_input | semmle.label | call to user_input |
| struct_init.c:31:23:31:23 | a | semmle.label | a |
-| struct_init.c:36:17:36:24 | nestedAB [nestedAB, a] | semmle.label | nestedAB [nestedAB, a] |
#select
| A.cpp:56:13:56:15 | call to get | A.cpp:55:12:55:19 | (C *)... | A.cpp:56:13:56:15 | call to get | call to get flows from $@ | A.cpp:55:12:55:19 | (C *)... | (C *)... |
| A.cpp:56:13:56:15 | call to get | A.cpp:55:12:55:19 | new | A.cpp:56:13:56:15 | call to get | call to get flows from $@ | A.cpp:55:12:55:19 | new | new |
@@ -646,8 +449,6 @@ nodes
| aliasing.cpp:176:13:176:14 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:176:13:176:14 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
| aliasing.cpp:189:15:189:16 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:189:15:189:16 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
| aliasing.cpp:201:15:201:16 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:201:15:201:16 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
-| aliasing.cpp:213:10:213:11 | m1 | aliasing.cpp:211:13:211:22 | call to user_input | aliasing.cpp:213:10:213:11 | m1 | m1 flows from $@ | aliasing.cpp:211:13:211:22 | call to user_input | call to user_input |
-| aliasing.cpp:227:10:227:11 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:227:10:227:11 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
| arrays.cpp:7:8:7:13 | access to array | arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:7:8:7:13 | access to array | access to array flows from $@ | arrays.cpp:6:12:6:21 | call to user_input | call to user_input |
| arrays.cpp:9:8:9:11 | * ... | arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:9:8:9:11 | * ... | * ... flows from $@ | arrays.cpp:6:12:6:21 | call to user_input | call to user_input |
| arrays.cpp:10:8:10:15 | * ... | arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:10:8:10:15 | * ... | * ... flows from $@ | arrays.cpp:6:12:6:21 | call to user_input | call to user_input |
@@ -665,9 +466,6 @@ nodes
| by_reference.cpp:132:14:132:14 | a | by_reference.cpp:96:8:96:17 | call to user_input | by_reference.cpp:132:14:132:14 | a | a flows from $@ | by_reference.cpp:96:8:96:17 | call to user_input | call to user_input |
| by_reference.cpp:134:29:134:29 | a | by_reference.cpp:88:13:88:22 | call to user_input | by_reference.cpp:134:29:134:29 | a | a flows from $@ | by_reference.cpp:88:13:88:22 | call to user_input | call to user_input |
| by_reference.cpp:136:16:136:16 | a | by_reference.cpp:96:8:96:17 | call to user_input | by_reference.cpp:136:16:136:16 | a | a flows from $@ | by_reference.cpp:96:8:96:17 | call to user_input | call to user_input |
-| by_reference.cpp:146:8:146:8 | s | by_reference.cpp:140:9:140:27 | (char *)... | by_reference.cpp:146:8:146:8 | s | s flows from $@ | by_reference.cpp:140:9:140:27 | (char *)... | (char *)... |
-| by_reference.cpp:146:8:146:8 | s | by_reference.cpp:140:9:140:27 | (const char *)... | by_reference.cpp:146:8:146:8 | s | s flows from $@ | by_reference.cpp:140:9:140:27 | (const char *)... | (const char *)... |
-| by_reference.cpp:146:8:146:8 | s | by_reference.cpp:140:16:140:25 | call to user_input | by_reference.cpp:146:8:146:8 | s | s flows from $@ | by_reference.cpp:140:16:140:25 | call to user_input | call to user_input |
| complex.cpp:42:18:42:18 | call to a | complex.cpp:53:19:53:28 | call to user_input | complex.cpp:42:18:42:18 | call to a | call to a flows from $@ | complex.cpp:53:19:53:28 | call to user_input | call to user_input |
| complex.cpp:42:18:42:18 | call to a | complex.cpp:55:19:55:28 | call to user_input | complex.cpp:42:18:42:18 | call to a | call to a flows from $@ | complex.cpp:55:19:55:28 | call to user_input | call to user_input |
| complex.cpp:43:18:43:18 | call to b | complex.cpp:54:19:54:28 | call to user_input | complex.cpp:43:18:43:18 | call to b | call to b flows from $@ | complex.cpp:54:19:54:28 | call to user_input | call to user_input |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected
index d7939750db6..996836a65b4 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-diff.expected
@@ -1,12 +1,12 @@
| A.cpp:25:13:25:13 | c | AST only |
| A.cpp:27:28:27:28 | c | AST only |
-| A.cpp:28:29:28:29 | this | IR only |
| A.cpp:31:20:31:20 | c | AST only |
| A.cpp:40:5:40:6 | cc | AST only |
| A.cpp:41:5:41:6 | ct | AST only |
| A.cpp:42:10:42:12 | & ... | AST only |
| A.cpp:43:10:43:12 | & ... | AST only |
| A.cpp:48:20:48:20 | c | AST only |
+| A.cpp:49:10:49:10 | b | AST only |
| A.cpp:49:13:49:13 | c | AST only |
| A.cpp:55:5:55:5 | b | AST only |
| A.cpp:56:10:56:10 | b | AST only |
@@ -14,11 +14,15 @@
| A.cpp:57:28:57:30 | call to get | AST only |
| A.cpp:64:10:64:15 | this | AST only |
| A.cpp:64:17:64:18 | b1 | AST only |
+| A.cpp:65:10:65:11 | b1 | AST only |
| A.cpp:65:14:65:14 | c | AST only |
+| A.cpp:66:10:66:11 | b2 | AST only |
| A.cpp:66:14:66:14 | c | AST only |
| A.cpp:73:10:73:19 | this | AST only |
| A.cpp:73:21:73:22 | b1 | AST only |
+| A.cpp:74:10:74:11 | b1 | AST only |
| A.cpp:74:14:74:14 | c | AST only |
+| A.cpp:75:10:75:11 | b2 | AST only |
| A.cpp:75:14:75:14 | c | AST only |
| A.cpp:81:10:81:15 | this | AST only |
| A.cpp:81:17:81:18 | b1 | AST only |
@@ -30,61 +34,85 @@
| A.cpp:100:9:100:9 | a | AST only |
| A.cpp:101:5:101:6 | this | AST only |
| A.cpp:101:8:101:9 | c1 | AST only |
+| A.cpp:107:12:107:13 | c1 | AST only |
| A.cpp:107:16:107:16 | a | AST only |
+| A.cpp:120:12:120:13 | c1 | AST only |
| A.cpp:120:16:120:16 | a | AST only |
| A.cpp:126:5:126:5 | b | AST only |
| A.cpp:131:5:131:6 | this | AST only |
| A.cpp:131:8:131:8 | b | AST only |
+| A.cpp:132:10:132:10 | b | AST only |
| A.cpp:132:13:132:13 | c | AST only |
| A.cpp:142:10:142:10 | c | AST only |
| A.cpp:143:13:143:13 | b | AST only |
| A.cpp:151:18:151:18 | b | AST only |
| A.cpp:151:21:151:21 | this | AST only |
+| A.cpp:152:10:152:10 | d | AST only |
| A.cpp:152:13:152:13 | b | AST only |
+| A.cpp:153:10:153:10 | d | AST only |
+| A.cpp:153:13:153:13 | b | AST only |
| A.cpp:153:16:153:16 | c | AST only |
+| A.cpp:154:10:154:10 | b | AST only |
| A.cpp:154:13:154:13 | c | AST only |
| A.cpp:160:29:160:29 | b | AST only |
| A.cpp:161:38:161:39 | l1 | AST only |
| A.cpp:162:38:162:39 | l2 | AST only |
+| A.cpp:163:10:163:11 | l3 | AST only |
| A.cpp:163:14:163:17 | head | AST only |
+| A.cpp:164:10:164:11 | l3 | AST only |
+| A.cpp:164:14:164:17 | next | AST only |
| A.cpp:164:20:164:23 | head | AST only |
+| A.cpp:165:10:165:11 | l3 | AST only |
+| A.cpp:165:14:165:17 | next | AST only |
+| A.cpp:165:20:165:23 | next | AST only |
| A.cpp:165:26:165:29 | head | AST only |
+| A.cpp:166:10:166:11 | l3 | AST only |
+| A.cpp:166:14:166:17 | next | AST only |
+| A.cpp:166:20:166:23 | next | AST only |
+| A.cpp:166:26:166:29 | next | AST only |
| A.cpp:166:32:166:35 | head | AST only |
-| A.cpp:167:47:167:50 | l | IR only |
+| A.cpp:169:12:169:12 | l | AST only |
| A.cpp:169:15:169:18 | head | AST only |
| A.cpp:183:7:183:10 | head | AST only |
| A.cpp:184:13:184:16 | next | AST only |
| B.cpp:7:25:7:25 | e | AST only |
| B.cpp:8:25:8:26 | b1 | AST only |
+| B.cpp:9:10:9:11 | b2 | AST only |
+| B.cpp:9:14:9:17 | box1 | AST only |
| B.cpp:9:20:9:24 | elem1 | AST only |
+| B.cpp:10:10:10:11 | b2 | AST only |
+| B.cpp:10:14:10:17 | box1 | AST only |
| B.cpp:10:20:10:24 | elem2 | AST only |
| B.cpp:16:37:16:37 | e | AST only |
| B.cpp:17:25:17:26 | b1 | AST only |
+| B.cpp:18:10:18:11 | b2 | AST only |
+| B.cpp:18:14:18:17 | box1 | AST only |
| B.cpp:18:20:18:24 | elem1 | AST only |
+| B.cpp:19:10:19:11 | b2 | AST only |
+| B.cpp:19:14:19:17 | box1 | AST only |
| B.cpp:19:20:19:24 | elem2 | AST only |
| B.cpp:35:13:35:17 | elem1 | AST only |
| B.cpp:36:13:36:17 | elem2 | AST only |
| B.cpp:46:13:46:16 | box1 | AST only |
| C.cpp:19:5:19:5 | c | AST only |
| C.cpp:24:11:24:12 | s3 | AST only |
-| C.cpp:29:10:29:11 | this | IR only |
-| C.cpp:30:10:30:11 | this | IR only |
-| C.cpp:31:10:31:11 | this | IR only |
| D.cpp:9:21:9:24 | elem | AST only |
-| D.cpp:10:30:10:33 | this | IR only |
| D.cpp:11:29:11:32 | elem | AST only |
| D.cpp:16:21:16:23 | box | AST only |
-| D.cpp:17:30:17:32 | this | IR only |
| D.cpp:18:29:18:31 | box | AST only |
| D.cpp:22:10:22:11 | b2 | AST only |
| D.cpp:22:14:22:20 | call to getBox1 | AST only |
| D.cpp:22:25:22:31 | call to getElem | AST only |
+| D.cpp:30:5:30:5 | b | AST only |
+| D.cpp:30:8:30:10 | box | AST only |
| D.cpp:30:13:30:16 | elem | AST only |
| D.cpp:31:14:31:14 | b | AST only |
+| D.cpp:37:5:37:5 | b | AST only |
| D.cpp:37:8:37:10 | box | AST only |
| D.cpp:37:21:37:21 | e | AST only |
| D.cpp:38:14:38:14 | b | AST only |
| D.cpp:44:5:44:5 | b | AST only |
+| D.cpp:44:8:44:14 | call to getBox1 | AST only |
| D.cpp:44:19:44:22 | elem | AST only |
| D.cpp:45:14:45:14 | b | AST only |
| D.cpp:51:5:51:5 | b | AST only |
@@ -92,14 +120,26 @@
| D.cpp:51:27:51:27 | e | AST only |
| D.cpp:52:14:52:14 | b | AST only |
| D.cpp:57:5:57:12 | boxfield | AST only |
+| D.cpp:58:5:58:12 | boxfield | AST only |
+| D.cpp:58:5:58:12 | this | AST only |
+| D.cpp:58:15:58:17 | box | AST only |
| D.cpp:58:20:58:23 | elem | AST only |
| D.cpp:59:5:59:7 | this | AST only |
+| D.cpp:64:10:64:17 | boxfield | AST only |
+| D.cpp:64:10:64:17 | this | AST only |
+| D.cpp:64:20:64:22 | box | AST only |
| D.cpp:64:25:64:28 | elem | AST only |
+| E.cpp:21:10:21:10 | p | AST only |
+| E.cpp:21:13:21:16 | data | AST only |
| E.cpp:21:18:21:23 | buffer | AST only |
| E.cpp:28:21:28:23 | raw | AST only |
+| E.cpp:29:21:29:21 | b | AST only |
| E.cpp:29:24:29:29 | buffer | AST only |
+| E.cpp:30:21:30:21 | p | AST only |
+| E.cpp:30:23:30:26 | data | AST only |
| E.cpp:30:28:30:33 | buffer | AST only |
| E.cpp:31:10:31:12 | raw | AST only |
+| E.cpp:32:10:32:10 | b | AST only |
| E.cpp:32:13:32:18 | buffer | AST only |
| E.cpp:33:18:33:19 | & ... | AST only |
| aliasing.cpp:9:6:9:7 | m1 | AST only |
@@ -107,92 +147,79 @@
| aliasing.cpp:17:5:17:6 | m1 | AST only |
| aliasing.cpp:25:17:25:19 | & ... | AST only |
| aliasing.cpp:26:19:26:20 | s2 | AST only |
-| aliasing.cpp:29:11:29:12 | s1 | IR only |
-| aliasing.cpp:30:11:30:12 | s2 | IR only |
-| aliasing.cpp:31:11:31:12 | s3 | IR only |
| aliasing.cpp:37:8:37:9 | m1 | AST only |
-| aliasing.cpp:38:11:38:12 | s1 | IR only |
| aliasing.cpp:42:6:42:7 | m1 | AST only |
-| aliasing.cpp:43:13:43:14 | ref2 | IR only |
| aliasing.cpp:49:9:49:10 | m1 | AST only |
-| aliasing.cpp:50:11:50:12 | s1 | IR only |
| aliasing.cpp:54:6:54:7 | m1 | AST only |
-| aliasing.cpp:55:14:55:15 | copy2 | IR only |
| aliasing.cpp:60:6:60:7 | m1 | AST only |
-| aliasing.cpp:62:14:62:15 | copy2 | IR only |
-| aliasing.cpp:71:11:71:11 | w | IR only |
| aliasing.cpp:72:5:72:6 | m1 | AST only |
-| aliasing.cpp:73:10:73:10 | w | IR only |
-| aliasing.cpp:73:12:73:13 | s | IR only |
-| aliasing.cpp:78:13:78:13 | w | IR only |
| aliasing.cpp:79:6:79:7 | m1 | AST only |
-| aliasing.cpp:80:10:80:10 | w | IR only |
-| aliasing.cpp:80:12:80:13 | s | IR only |
-| aliasing.cpp:85:12:85:12 | w | IR only |
| aliasing.cpp:86:5:86:6 | m1 | AST only |
-| aliasing.cpp:87:10:87:10 | w | IR only |
-| aliasing.cpp:87:12:87:13 | s | IR only |
+| aliasing.cpp:92:3:92:3 | w | AST only |
| aliasing.cpp:92:7:92:8 | m1 | AST only |
-| aliasing.cpp:93:10:93:10 | w | IR only |
-| aliasing.cpp:93:12:93:13 | s | IR only |
| aliasing.cpp:98:5:98:6 | m1 | AST only |
-| aliasing.cpp:101:21:101:22 | s_copy | IR only |
| aliasing.cpp:106:3:106:5 | * ... | AST only |
| aliasing.cpp:111:15:111:19 | & ... | AST only |
-| aliasing.cpp:112:10:112:11 | s | IR only |
| aliasing.cpp:121:15:121:16 | xs | AST only |
| aliasing.cpp:126:15:126:20 | ... - ... | AST only |
| aliasing.cpp:131:15:131:16 | xs | AST only |
| aliasing.cpp:136:15:136:17 | + ... | AST only |
+| aliasing.cpp:141:15:141:15 | s | AST only |
| aliasing.cpp:141:17:141:20 | data | AST only |
-| aliasing.cpp:143:10:143:13 | s | IR only |
| aliasing.cpp:147:15:147:22 | & ... | AST only |
-| aliasing.cpp:148:13:148:14 | access to array | IR only |
+| aliasing.cpp:158:15:158:15 | s | AST only |
| aliasing.cpp:158:17:158:20 | data | AST only |
-| aliasing.cpp:159:11:159:14 | s | IR only |
+| aliasing.cpp:164:15:164:15 | s | AST only |
| aliasing.cpp:164:17:164:20 | data | AST only |
-| aliasing.cpp:165:10:165:13 | s | IR only |
| aliasing.cpp:175:15:175:22 | & ... | AST only |
-| aliasing.cpp:176:11:176:11 | s2 | IR only |
-| aliasing.cpp:176:13:176:14 | s | IR only |
+| aliasing.cpp:175:16:175:17 | s2 | AST only |
| aliasing.cpp:181:15:181:22 | & ... | AST only |
-| aliasing.cpp:182:11:182:11 | s2 | IR only |
-| aliasing.cpp:182:13:182:14 | s | IR only |
+| aliasing.cpp:181:16:181:17 | s2 | AST only |
| aliasing.cpp:187:15:187:22 | & ... | AST only |
-| aliasing.cpp:189:13:189:13 | s2_2 | IR only |
-| aliasing.cpp:189:15:189:16 | s | IR only |
+| aliasing.cpp:187:16:187:17 | s2 | AST only |
| aliasing.cpp:194:15:194:22 | & ... | AST only |
-| aliasing.cpp:196:13:196:13 | s2_2 | IR only |
-| aliasing.cpp:196:15:196:16 | s | IR only |
+| aliasing.cpp:194:16:194:17 | s2 | AST only |
| aliasing.cpp:200:15:200:24 | & ... | AST only |
-| aliasing.cpp:201:13:201:13 | ps2 | IR only |
-| aliasing.cpp:201:15:201:16 | s | IR only |
+| aliasing.cpp:200:16:200:18 | ps2 | AST only |
| aliasing.cpp:205:15:205:24 | & ... | AST only |
-| aliasing.cpp:206:13:206:13 | ps2 | IR only |
-| aliasing.cpp:206:15:206:16 | s | IR only |
-| aliasing.cpp:211:8:211:9 | m1 | AST only |
-| aliasing.cpp:212:12:212:12 | s2 | IR only |
-| aliasing.cpp:213:10:213:11 | s | IR only |
-| aliasing.cpp:218:8:218:9 | m1 | AST only |
-| aliasing.cpp:219:12:219:12 | s2 | IR only |
-| aliasing.cpp:220:10:220:11 | s | IR only |
-| aliasing.cpp:225:15:225:22 | & ... | AST only |
-| aliasing.cpp:226:12:226:12 | s2 | IR only |
-| aliasing.cpp:227:10:227:11 | s | IR only |
-| aliasing.cpp:232:15:232:22 | & ... | AST only |
-| aliasing.cpp:233:12:233:12 | s2 | IR only |
-| aliasing.cpp:234:10:234:11 | s | IR only |
+| aliasing.cpp:205:16:205:18 | ps2 | AST only |
| arrays.cpp:6:3:6:8 | access to array | AST only |
| arrays.cpp:6:3:6:23 | arr | IR only |
| arrays.cpp:15:3:15:10 | * ... | AST only |
+| arrays.cpp:36:3:36:3 | o | AST only |
+| arrays.cpp:36:5:36:10 | nested | AST only |
| arrays.cpp:36:19:36:22 | data | AST only |
+| arrays.cpp:37:8:37:8 | o | AST only |
+| arrays.cpp:37:8:37:22 | access to array | AST only |
+| arrays.cpp:37:10:37:15 | nested | AST only |
| arrays.cpp:37:24:37:27 | data | AST only |
+| arrays.cpp:38:8:38:8 | o | AST only |
+| arrays.cpp:38:8:38:22 | access to array | AST only |
+| arrays.cpp:38:10:38:15 | nested | AST only |
| arrays.cpp:38:24:38:27 | data | AST only |
+| arrays.cpp:42:3:42:3 | o | AST only |
+| arrays.cpp:42:3:42:20 | access to array | AST only |
+| arrays.cpp:42:5:42:12 | indirect | AST only |
| arrays.cpp:42:22:42:25 | data | AST only |
+| arrays.cpp:43:8:43:8 | o | AST only |
+| arrays.cpp:43:8:43:25 | access to array | AST only |
+| arrays.cpp:43:10:43:17 | indirect | AST only |
| arrays.cpp:43:27:43:30 | data | AST only |
+| arrays.cpp:44:8:44:8 | o | AST only |
+| arrays.cpp:44:8:44:25 | access to array | AST only |
+| arrays.cpp:44:10:44:17 | indirect | AST only |
| arrays.cpp:44:27:44:30 | data | AST only |
+| arrays.cpp:48:3:48:3 | o | AST only |
+| arrays.cpp:48:3:48:20 | access to array | AST only |
+| arrays.cpp:48:5:48:12 | indirect | AST only |
| arrays.cpp:48:22:48:25 | data | AST only |
+| arrays.cpp:49:8:49:8 | o | AST only |
+| arrays.cpp:49:8:49:25 | access to array | AST only |
+| arrays.cpp:49:10:49:17 | indirect | AST only |
| arrays.cpp:49:27:49:30 | data | AST only |
+| arrays.cpp:50:8:50:8 | o | AST only |
+| arrays.cpp:50:8:50:25 | access to array | AST only |
+| arrays.cpp:50:10:50:17 | indirect | AST only |
| arrays.cpp:50:27:50:30 | data | AST only |
| by_reference.cpp:12:8:12:8 | a | AST only |
| by_reference.cpp:16:11:16:11 | a | AST only |
@@ -200,8 +227,6 @@
| by_reference.cpp:20:23:20:27 | value | AST only |
| by_reference.cpp:24:19:24:22 | this | AST only |
| by_reference.cpp:24:25:24:29 | value | AST only |
-| by_reference.cpp:32:15:32:15 | s | IR only |
-| by_reference.cpp:36:18:36:18 | this | IR only |
| by_reference.cpp:50:3:50:3 | s | AST only |
| by_reference.cpp:50:17:50:26 | call to user_input | AST only |
| by_reference.cpp:51:10:51:20 | call to getDirectly | AST only |
@@ -219,63 +244,87 @@
| by_reference.cpp:92:3:92:5 | * ... | AST only |
| by_reference.cpp:96:3:96:4 | pa | AST only |
| by_reference.cpp:102:21:102:39 | & ... | AST only |
+| by_reference.cpp:103:21:103:25 | outer | AST only |
| by_reference.cpp:103:27:103:35 | inner_ptr | AST only |
| by_reference.cpp:104:15:104:22 | & ... | AST only |
| by_reference.cpp:106:21:106:41 | & ... | AST only |
+| by_reference.cpp:107:21:107:26 | pouter | AST only |
| by_reference.cpp:107:29:107:37 | inner_ptr | AST only |
| by_reference.cpp:108:15:108:24 | & ... | AST only |
+| by_reference.cpp:110:8:110:12 | outer | AST only |
+| by_reference.cpp:110:14:110:25 | inner_nested | AST only |
| by_reference.cpp:110:27:110:27 | a | AST only |
+| by_reference.cpp:111:8:111:12 | outer | AST only |
+| by_reference.cpp:111:14:111:22 | inner_ptr | AST only |
| by_reference.cpp:111:25:111:25 | a | AST only |
+| by_reference.cpp:112:8:112:12 | outer | AST only |
| by_reference.cpp:112:14:112:14 | a | AST only |
+| by_reference.cpp:114:8:114:13 | pouter | AST only |
+| by_reference.cpp:114:16:114:27 | inner_nested | AST only |
| by_reference.cpp:114:29:114:29 | a | AST only |
+| by_reference.cpp:115:8:115:13 | pouter | AST only |
+| by_reference.cpp:115:16:115:24 | inner_ptr | AST only |
| by_reference.cpp:115:27:115:27 | a | AST only |
+| by_reference.cpp:116:8:116:13 | pouter | AST only |
| by_reference.cpp:116:16:116:16 | a | AST only |
| by_reference.cpp:122:27:122:38 | inner_nested | AST only |
| by_reference.cpp:123:21:123:36 | * ... | AST only |
+| by_reference.cpp:123:22:123:26 | outer | AST only |
| by_reference.cpp:124:21:124:21 | a | AST only |
| by_reference.cpp:126:29:126:40 | inner_nested | AST only |
| by_reference.cpp:127:21:127:38 | * ... | AST only |
+| by_reference.cpp:127:22:127:27 | pouter | AST only |
| by_reference.cpp:128:23:128:23 | a | AST only |
+| by_reference.cpp:130:8:130:12 | outer | AST only |
+| by_reference.cpp:130:14:130:25 | inner_nested | AST only |
| by_reference.cpp:130:27:130:27 | a | AST only |
+| by_reference.cpp:131:8:131:12 | outer | AST only |
+| by_reference.cpp:131:14:131:22 | inner_ptr | AST only |
| by_reference.cpp:131:25:131:25 | a | AST only |
+| by_reference.cpp:132:8:132:12 | outer | AST only |
| by_reference.cpp:132:14:132:14 | a | AST only |
+| by_reference.cpp:134:8:134:13 | pouter | AST only |
+| by_reference.cpp:134:16:134:27 | inner_nested | AST only |
| by_reference.cpp:134:29:134:29 | a | AST only |
+| by_reference.cpp:135:8:135:13 | pouter | AST only |
+| by_reference.cpp:135:16:135:24 | inner_ptr | AST only |
| by_reference.cpp:135:27:135:27 | a | AST only |
+| by_reference.cpp:136:8:136:13 | pouter | AST only |
| by_reference.cpp:136:16:136:16 | a | AST only |
-| by_reference.cpp:140:3:140:5 | * ... | AST only |
-| by_reference.cpp:145:15:145:16 | & ... | AST only |
-| complex.cpp:9:20:9:21 | this | IR only |
-| complex.cpp:10:20:10:21 | this | IR only |
| complex.cpp:11:22:11:23 | a_ | AST only |
| complex.cpp:12:22:12:23 | b_ | AST only |
+| complex.cpp:42:8:42:8 | b | AST only |
| complex.cpp:42:16:42:16 | f | AST only |
+| complex.cpp:43:8:43:8 | b | AST only |
| complex.cpp:43:16:43:16 | f | AST only |
+| complex.cpp:53:3:53:4 | b1 | AST only |
| complex.cpp:53:12:53:12 | f | AST only |
+| complex.cpp:54:3:54:4 | b2 | AST only |
| complex.cpp:54:12:54:12 | f | AST only |
+| complex.cpp:55:3:55:4 | b3 | AST only |
| complex.cpp:55:12:55:12 | f | AST only |
+| complex.cpp:56:3:56:4 | b3 | AST only |
| complex.cpp:56:12:56:12 | f | AST only |
| complex.cpp:59:7:59:8 | b1 | AST only |
| complex.cpp:62:7:62:8 | b2 | AST only |
| complex.cpp:65:7:65:8 | b3 | AST only |
| complex.cpp:68:7:68:8 | b4 | AST only |
| conflated.cpp:10:3:10:7 | * ... | AST only |
-| conflated.cpp:11:12:11:12 | ra | IR only |
+| conflated.cpp:10:4:10:5 | ra | AST only |
| conflated.cpp:19:19:19:21 | raw | AST only |
| conflated.cpp:20:8:20:10 | raw | AST only |
+| conflated.cpp:29:3:29:4 | pa | AST only |
| conflated.cpp:29:7:29:7 | x | AST only |
-| conflated.cpp:30:12:30:12 | pa | IR only |
+| conflated.cpp:36:3:36:4 | pa | AST only |
| conflated.cpp:36:7:36:7 | x | AST only |
-| conflated.cpp:37:12:37:12 | pa | IR only |
| conflated.cpp:53:7:53:10 | next | AST only |
+| conflated.cpp:54:3:54:4 | ll | AST only |
+| conflated.cpp:54:7:54:10 | next | AST only |
| conflated.cpp:54:13:54:13 | y | AST only |
-| conflated.cpp:55:12:55:15 | ll | IR only |
-| conflated.cpp:55:18:55:18 | next | IR only |
| conflated.cpp:59:35:59:38 | next | AST only |
+| conflated.cpp:60:3:60:4 | ll | AST only |
+| conflated.cpp:60:7:60:10 | next | AST only |
| conflated.cpp:60:13:60:13 | y | AST only |
-| conflated.cpp:61:12:61:15 | ll | IR only |
-| conflated.cpp:61:18:61:18 | next | IR only |
-| constructors.cpp:18:22:18:23 | this | IR only |
-| constructors.cpp:19:22:19:23 | this | IR only |
| constructors.cpp:20:24:20:25 | a_ | AST only |
| constructors.cpp:21:24:21:25 | b_ | AST only |
| constructors.cpp:28:10:28:10 | f | AST only |
@@ -287,55 +336,68 @@
| qualifiers.cpp:9:36:9:36 | a | AST only |
| qualifiers.cpp:12:56:12:56 | a | AST only |
| qualifiers.cpp:13:57:13:57 | a | AST only |
-| qualifiers.cpp:18:32:18:36 | this | IR only |
| qualifiers.cpp:22:5:22:9 | outer | AST only |
+| qualifiers.cpp:22:11:22:18 | call to getInner | AST only |
| qualifiers.cpp:22:23:22:23 | a | AST only |
+| qualifiers.cpp:23:10:23:14 | outer | AST only |
+| qualifiers.cpp:23:16:23:20 | inner | AST only |
| qualifiers.cpp:23:23:23:23 | a | AST only |
| qualifiers.cpp:27:5:27:9 | outer | AST only |
| qualifiers.cpp:27:11:27:18 | call to getInner | AST only |
| qualifiers.cpp:27:28:27:37 | call to user_input | AST only |
+| qualifiers.cpp:28:10:28:14 | outer | AST only |
+| qualifiers.cpp:28:16:28:20 | inner | AST only |
| qualifiers.cpp:28:23:28:23 | a | AST only |
| qualifiers.cpp:32:17:32:21 | outer | AST only |
| qualifiers.cpp:32:23:32:30 | call to getInner | AST only |
| qualifiers.cpp:32:35:32:44 | call to user_input | AST only |
+| qualifiers.cpp:33:10:33:14 | outer | AST only |
+| qualifiers.cpp:33:16:33:20 | inner | AST only |
| qualifiers.cpp:33:23:33:23 | a | AST only |
| qualifiers.cpp:37:19:37:35 | * ... | AST only |
| qualifiers.cpp:37:20:37:24 | outer | AST only |
| qualifiers.cpp:37:38:37:47 | call to user_input | AST only |
+| qualifiers.cpp:38:10:38:14 | outer | AST only |
+| qualifiers.cpp:38:16:38:20 | inner | AST only |
| qualifiers.cpp:38:23:38:23 | a | AST only |
+| qualifiers.cpp:42:6:42:22 | * ... | AST only |
| qualifiers.cpp:42:7:42:11 | outer | AST only |
| qualifiers.cpp:42:25:42:25 | a | AST only |
+| qualifiers.cpp:43:10:43:14 | outer | AST only |
+| qualifiers.cpp:43:16:43:20 | inner | AST only |
| qualifiers.cpp:43:23:43:23 | a | AST only |
| qualifiers.cpp:47:6:47:11 | & ... | AST only |
+| qualifiers.cpp:47:15:47:22 | call to getInner | AST only |
| qualifiers.cpp:47:27:47:27 | a | AST only |
+| qualifiers.cpp:48:10:48:14 | outer | AST only |
+| qualifiers.cpp:48:16:48:20 | inner | AST only |
| qualifiers.cpp:48:23:48:23 | a | AST only |
| realistic.cpp:26:5:26:10 | offset | AST only |
| realistic.cpp:42:20:42:20 | o | AST only |
+| realistic.cpp:49:9:49:11 | foo | AST only |
| realistic.cpp:49:20:49:22 | baz | AST only |
+| realistic.cpp:53:9:53:11 | foo | AST only |
+| realistic.cpp:53:9:53:18 | access to array | AST only |
+| realistic.cpp:53:20:53:22 | baz | AST only |
+| realistic.cpp:53:25:53:33 | userInput | AST only |
| realistic.cpp:53:35:53:43 | bufferLen | AST only |
+| realistic.cpp:54:16:54:18 | foo | AST only |
+| realistic.cpp:54:16:54:25 | access to array | AST only |
+| realistic.cpp:54:27:54:29 | baz | AST only |
+| realistic.cpp:54:32:54:40 | userInput | AST only |
| realistic.cpp:54:42:54:47 | buffer | AST only |
-| realistic.cpp:55:16:55:18 | foo | IR only |
-| realistic.cpp:55:23:55:25 | access to array | IR only |
-| realistic.cpp:55:28:55:36 | baz | IR only |
-| realistic.cpp:55:38:55:46 | userInput | IR only |
-| realistic.cpp:57:92:57:94 | foo | IR only |
-| realistic.cpp:57:99:57:101 | access to array | IR only |
-| realistic.cpp:57:104:57:112 | baz | IR only |
-| realistic.cpp:57:114:57:122 | userInput | IR only |
| realistic.cpp:60:16:60:18 | dst | AST only |
-| realistic.cpp:60:25:60:27 | foo | IR only |
-| realistic.cpp:60:32:60:34 | access to array | IR only |
-| realistic.cpp:60:37:60:45 | baz | IR only |
-| realistic.cpp:60:47:60:52 | userInput | IR only |
-| realistic.cpp:60:59:60:61 | foo | IR only |
-| realistic.cpp:60:66:60:68 | access to array | IR only |
-| realistic.cpp:60:71:60:79 | baz | IR only |
-| realistic.cpp:60:81:60:89 | userInput | IR only |
+| realistic.cpp:61:21:61:23 | foo | AST only |
+| realistic.cpp:61:21:61:30 | access to array | AST only |
+| realistic.cpp:61:32:61:34 | baz | AST only |
+| realistic.cpp:61:37:61:45 | userInput | AST only |
| realistic.cpp:61:47:61:55 | bufferLen | AST only |
+| realistic.cpp:65:21:65:23 | foo | AST only |
+| realistic.cpp:65:21:65:30 | access to array | AST only |
+| realistic.cpp:65:32:65:34 | baz | AST only |
+| realistic.cpp:65:37:65:45 | userInput | AST only |
| realistic.cpp:65:47:65:52 | buffer | AST only |
| realistic.cpp:66:21:66:23 | dst | AST only |
-| simple.cpp:18:22:18:23 | this | IR only |
-| simple.cpp:19:22:19:23 | this | IR only |
| simple.cpp:20:24:20:25 | a_ | AST only |
| simple.cpp:21:24:21:25 | b_ | AST only |
| simple.cpp:28:10:28:10 | f | AST only |
@@ -349,24 +411,31 @@
| simple.cpp:51:9:51:9 | h | AST only |
| simple.cpp:54:9:54:9 | i | AST only |
| simple.cpp:65:7:65:7 | i | AST only |
-| simple.cpp:67:13:67:13 | a2 | IR only |
-| simple.cpp:79:16:79:17 | this | IR only |
-| simple.cpp:79:19:79:20 | f2 | IR only |
+| simple.cpp:83:9:83:10 | this | AST only |
| simple.cpp:83:12:83:13 | f1 | AST only |
| simple.cpp:84:14:84:20 | this | AST only |
| simple.cpp:92:7:92:7 | i | AST only |
-| simple.cpp:94:13:94:13 | a2 | IR only |
-| simple.cpp:104:9:104:9 | i | AST only |
-| simple.cpp:106:13:106:13 | b2 | IR only |
-| simple.cpp:106:15:106:15 | a | IR only |
+| struct_init.c:15:8:15:9 | ab | AST only |
| struct_init.c:15:12:15:12 | a | AST only |
+| struct_init.c:16:8:16:9 | ab | AST only |
| struct_init.c:16:12:16:12 | b | AST only |
+| struct_init.c:22:8:22:9 | ab | AST only |
| struct_init.c:22:11:22:11 | a | AST only |
+| struct_init.c:23:8:23:9 | ab | AST only |
| struct_init.c:23:11:23:11 | b | AST only |
| struct_init.c:24:10:24:12 | & ... | AST only |
+| struct_init.c:31:8:31:12 | outer | AST only |
+| struct_init.c:31:14:31:21 | nestedAB | AST only |
| struct_init.c:31:23:31:23 | a | AST only |
+| struct_init.c:32:8:32:12 | outer | AST only |
+| struct_init.c:32:14:32:21 | nestedAB | AST only |
| struct_init.c:32:23:32:23 | b | AST only |
+| struct_init.c:33:8:33:12 | outer | AST only |
+| struct_init.c:33:14:33:22 | pointerAB | AST only |
| struct_init.c:33:25:33:25 | a | AST only |
+| struct_init.c:34:8:34:12 | outer | AST only |
+| struct_init.c:34:14:34:22 | pointerAB | AST only |
| struct_init.c:34:25:34:25 | b | AST only |
| struct_init.c:36:10:36:24 | & ... | AST only |
+| struct_init.c:46:10:46:14 | outer | AST only |
| struct_init.c:46:16:46:24 | pointerAB | AST only |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected
index 437a36cc2ba..294c46a5694 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition-ir.expected
@@ -1,338 +1,72 @@
| A.cpp:25:7:25:10 | this |
| A.cpp:27:22:27:25 | this |
-| A.cpp:28:23:28:26 | this |
-| A.cpp:49:10:49:10 | b |
-| A.cpp:65:10:65:11 | b1 |
-| A.cpp:66:10:66:11 | b2 |
-| A.cpp:74:10:74:11 | b1 |
-| A.cpp:75:10:75:11 | b2 |
| A.cpp:100:5:100:6 | c1 |
-| A.cpp:107:12:107:13 | c1 |
-| A.cpp:120:12:120:13 | c1 |
-| A.cpp:132:10:132:10 | b |
| A.cpp:142:7:142:7 | b |
| A.cpp:143:7:143:10 | this |
-| A.cpp:152:10:152:10 | d |
-| A.cpp:153:10:153:10 | d |
-| A.cpp:153:13:153:13 | b |
-| A.cpp:154:10:154:10 | b |
-| A.cpp:163:10:163:11 | l3 |
-| A.cpp:164:10:164:11 | l3 |
-| A.cpp:164:14:164:17 | next |
-| A.cpp:165:10:165:11 | l3 |
-| A.cpp:165:14:165:17 | next |
-| A.cpp:165:20:165:23 | next |
-| A.cpp:166:10:166:11 | l3 |
-| A.cpp:166:14:166:17 | next |
-| A.cpp:166:20:166:23 | next |
-| A.cpp:166:26:166:29 | next |
-| A.cpp:167:44:167:44 | l |
-| A.cpp:169:12:169:12 | l |
| A.cpp:183:7:183:10 | this |
| A.cpp:184:7:184:10 | this |
-| B.cpp:9:10:9:11 | b2 |
-| B.cpp:9:14:9:17 | box1 |
-| B.cpp:10:10:10:11 | b2 |
-| B.cpp:10:14:10:17 | box1 |
-| B.cpp:18:10:18:11 | b2 |
-| B.cpp:18:14:18:17 | box1 |
-| B.cpp:19:10:19:11 | b2 |
-| B.cpp:19:14:19:17 | box1 |
| B.cpp:35:7:35:10 | this |
| B.cpp:36:7:36:10 | this |
| B.cpp:46:7:46:10 | this |
| C.cpp:24:5:24:8 | this |
-| C.cpp:29:10:29:11 | this |
-| C.cpp:30:10:30:11 | this |
-| C.cpp:31:10:31:11 | this |
| D.cpp:9:21:9:24 | this |
-| D.cpp:10:30:10:33 | this |
| D.cpp:11:29:11:32 | this |
| D.cpp:16:21:16:23 | this |
-| D.cpp:17:30:17:32 | this |
| D.cpp:18:29:18:31 | this |
-| D.cpp:30:5:30:5 | b |
-| D.cpp:30:8:30:10 | box |
-| D.cpp:37:5:37:5 | b |
-| D.cpp:44:8:44:14 | call to getBox1 |
| D.cpp:57:5:57:12 | this |
-| D.cpp:58:5:58:12 | boxfield |
-| D.cpp:58:5:58:12 | this |
-| D.cpp:58:15:58:17 | box |
-| D.cpp:64:10:64:17 | boxfield |
-| D.cpp:64:10:64:17 | this |
-| D.cpp:64:20:64:22 | box |
-| E.cpp:21:10:21:10 | p |
-| E.cpp:21:13:21:16 | data |
-| E.cpp:29:21:29:21 | b |
-| E.cpp:30:21:30:21 | p |
-| E.cpp:30:23:30:26 | data |
-| E.cpp:32:10:32:10 | b |
| aliasing.cpp:9:3:9:3 | s |
| aliasing.cpp:13:3:13:3 | s |
| aliasing.cpp:17:3:17:3 | s |
-| aliasing.cpp:29:8:29:9 | s1 |
-| aliasing.cpp:30:8:30:9 | s2 |
-| aliasing.cpp:31:8:31:9 | s3 |
| aliasing.cpp:37:3:37:6 | ref1 |
-| aliasing.cpp:38:8:38:9 | s1 |
| aliasing.cpp:42:3:42:4 | s2 |
-| aliasing.cpp:43:8:43:11 | ref2 |
| aliasing.cpp:49:3:49:7 | copy1 |
-| aliasing.cpp:50:8:50:9 | s1 |
| aliasing.cpp:54:3:54:4 | s2 |
-| aliasing.cpp:55:8:55:12 | copy2 |
| aliasing.cpp:60:3:60:4 | s2 |
-| aliasing.cpp:62:8:62:12 | copy2 |
-| aliasing.cpp:71:9:71:9 | w |
| aliasing.cpp:72:3:72:3 | s |
-| aliasing.cpp:73:8:73:8 | w |
-| aliasing.cpp:73:10:73:10 | s |
-| aliasing.cpp:78:11:78:11 | w |
| aliasing.cpp:79:3:79:3 | s |
-| aliasing.cpp:80:8:80:8 | w |
-| aliasing.cpp:80:10:80:10 | s |
-| aliasing.cpp:85:10:85:10 | w |
| aliasing.cpp:86:3:86:3 | s |
-| aliasing.cpp:87:8:87:8 | w |
-| aliasing.cpp:87:10:87:10 | s |
-| aliasing.cpp:92:3:92:3 | w |
| aliasing.cpp:92:5:92:5 | s |
-| aliasing.cpp:93:8:93:8 | w |
-| aliasing.cpp:93:10:93:10 | s |
| aliasing.cpp:98:3:98:3 | s |
-| aliasing.cpp:101:14:101:19 | s_copy |
| aliasing.cpp:111:16:111:16 | s |
-| aliasing.cpp:112:8:112:8 | s |
-| aliasing.cpp:141:15:141:15 | s |
-| aliasing.cpp:143:8:143:8 | s |
| aliasing.cpp:147:16:147:19 | access to array |
-| aliasing.cpp:148:8:148:11 | access to array |
-| aliasing.cpp:158:15:158:15 | s |
-| aliasing.cpp:159:9:159:9 | s |
-| aliasing.cpp:164:15:164:15 | s |
-| aliasing.cpp:165:8:165:8 | s |
-| aliasing.cpp:175:16:175:17 | s2 |
| aliasing.cpp:175:19:175:19 | s |
-| aliasing.cpp:176:8:176:9 | s2 |
-| aliasing.cpp:176:11:176:11 | s |
-| aliasing.cpp:181:16:181:17 | s2 |
| aliasing.cpp:181:19:181:19 | s |
-| aliasing.cpp:182:8:182:9 | s2 |
-| aliasing.cpp:182:11:182:11 | s |
-| aliasing.cpp:187:16:187:17 | s2 |
| aliasing.cpp:187:19:187:19 | s |
-| aliasing.cpp:189:8:189:11 | s2_2 |
-| aliasing.cpp:189:13:189:13 | s |
-| aliasing.cpp:194:16:194:17 | s2 |
| aliasing.cpp:194:19:194:19 | s |
-| aliasing.cpp:196:8:196:11 | s2_2 |
-| aliasing.cpp:196:13:196:13 | s |
-| aliasing.cpp:200:16:200:18 | ps2 |
| aliasing.cpp:200:21:200:21 | s |
-| aliasing.cpp:201:8:201:10 | ps2 |
-| aliasing.cpp:201:13:201:13 | s |
-| aliasing.cpp:205:16:205:18 | ps2 |
| aliasing.cpp:205:21:205:21 | s |
-| aliasing.cpp:206:8:206:10 | ps2 |
-| aliasing.cpp:206:13:206:13 | s |
-| aliasing.cpp:211:3:211:4 | s2 |
-| aliasing.cpp:211:6:211:6 | s |
-| aliasing.cpp:212:9:212:10 | s2 |
-| aliasing.cpp:213:8:213:8 | s |
-| aliasing.cpp:218:3:218:4 | s2 |
-| aliasing.cpp:218:6:218:6 | s |
-| aliasing.cpp:219:9:219:10 | s2 |
-| aliasing.cpp:220:8:220:8 | s |
-| aliasing.cpp:225:16:225:17 | s2 |
-| aliasing.cpp:225:19:225:19 | s |
-| aliasing.cpp:226:9:226:10 | s2 |
-| aliasing.cpp:227:8:227:8 | s |
-| aliasing.cpp:232:16:232:17 | s2 |
-| aliasing.cpp:232:19:232:19 | s |
-| aliasing.cpp:233:9:233:10 | s2 |
-| aliasing.cpp:234:8:234:8 | s |
| arrays.cpp:6:3:6:5 | arr |
-| arrays.cpp:36:3:36:3 | o |
| arrays.cpp:36:3:36:17 | access to array |
-| arrays.cpp:36:5:36:10 | nested |
-| arrays.cpp:37:8:37:8 | o |
-| arrays.cpp:37:8:37:22 | access to array |
-| arrays.cpp:37:10:37:15 | nested |
-| arrays.cpp:38:8:38:8 | o |
-| arrays.cpp:38:8:38:22 | access to array |
-| arrays.cpp:38:10:38:15 | nested |
-| arrays.cpp:42:3:42:3 | o |
-| arrays.cpp:42:3:42:20 | access to array |
-| arrays.cpp:42:5:42:12 | indirect |
-| arrays.cpp:43:8:43:8 | o |
-| arrays.cpp:43:8:43:25 | access to array |
-| arrays.cpp:43:10:43:17 | indirect |
-| arrays.cpp:44:8:44:8 | o |
-| arrays.cpp:44:8:44:25 | access to array |
-| arrays.cpp:44:10:44:17 | indirect |
-| arrays.cpp:48:3:48:3 | o |
-| arrays.cpp:48:3:48:20 | access to array |
-| arrays.cpp:48:5:48:12 | indirect |
-| arrays.cpp:49:8:49:8 | o |
-| arrays.cpp:49:8:49:25 | access to array |
-| arrays.cpp:49:10:49:17 | indirect |
-| arrays.cpp:50:8:50:8 | o |
-| arrays.cpp:50:8:50:25 | access to array |
-| arrays.cpp:50:10:50:17 | indirect |
| by_reference.cpp:12:5:12:5 | s |
| by_reference.cpp:16:5:16:8 | this |
-| by_reference.cpp:32:12:32:12 | s |
-| by_reference.cpp:36:12:36:15 | this |
| by_reference.cpp:84:3:84:7 | inner |
| by_reference.cpp:88:3:88:7 | inner |
| by_reference.cpp:102:22:102:26 | outer |
-| by_reference.cpp:103:21:103:25 | outer |
| by_reference.cpp:104:16:104:20 | outer |
| by_reference.cpp:106:22:106:27 | pouter |
-| by_reference.cpp:107:21:107:26 | pouter |
| by_reference.cpp:108:16:108:21 | pouter |
-| by_reference.cpp:110:8:110:12 | outer |
-| by_reference.cpp:110:14:110:25 | inner_nested |
-| by_reference.cpp:111:8:111:12 | outer |
-| by_reference.cpp:111:14:111:22 | inner_ptr |
-| by_reference.cpp:112:8:112:12 | outer |
-| by_reference.cpp:114:8:114:13 | pouter |
-| by_reference.cpp:114:16:114:27 | inner_nested |
-| by_reference.cpp:115:8:115:13 | pouter |
-| by_reference.cpp:115:16:115:24 | inner_ptr |
-| by_reference.cpp:116:8:116:13 | pouter |
| by_reference.cpp:122:21:122:25 | outer |
-| by_reference.cpp:123:22:123:26 | outer |
| by_reference.cpp:124:15:124:19 | outer |
| by_reference.cpp:126:21:126:26 | pouter |
-| by_reference.cpp:127:22:127:27 | pouter |
| by_reference.cpp:128:15:128:20 | pouter |
-| by_reference.cpp:130:8:130:12 | outer |
-| by_reference.cpp:130:14:130:25 | inner_nested |
-| by_reference.cpp:131:8:131:12 | outer |
-| by_reference.cpp:131:14:131:22 | inner_ptr |
-| by_reference.cpp:132:8:132:12 | outer |
-| by_reference.cpp:134:8:134:13 | pouter |
-| by_reference.cpp:134:16:134:27 | inner_nested |
-| by_reference.cpp:135:8:135:13 | pouter |
-| by_reference.cpp:135:16:135:24 | inner_ptr |
-| by_reference.cpp:136:8:136:13 | pouter |
-| complex.cpp:9:20:9:21 | this |
-| complex.cpp:10:20:10:21 | this |
| complex.cpp:11:22:11:23 | this |
| complex.cpp:12:22:12:23 | this |
-| complex.cpp:42:8:42:8 | b |
| complex.cpp:42:10:42:14 | inner |
-| complex.cpp:43:8:43:8 | b |
| complex.cpp:43:10:43:14 | inner |
-| complex.cpp:53:3:53:4 | b1 |
| complex.cpp:53:6:53:10 | inner |
-| complex.cpp:54:3:54:4 | b2 |
| complex.cpp:54:6:54:10 | inner |
-| complex.cpp:55:3:55:4 | b3 |
| complex.cpp:55:6:55:10 | inner |
-| complex.cpp:56:3:56:4 | b3 |
| complex.cpp:56:6:56:10 | inner |
-| conflated.cpp:10:4:10:5 | ra |
-| conflated.cpp:11:9:11:10 | ra |
-| conflated.cpp:29:3:29:4 | pa |
-| conflated.cpp:30:8:30:9 | pa |
-| conflated.cpp:36:3:36:4 | pa |
-| conflated.cpp:37:8:37:9 | pa |
| conflated.cpp:53:3:53:4 | ll |
-| conflated.cpp:54:3:54:4 | ll |
-| conflated.cpp:54:7:54:10 | next |
-| conflated.cpp:55:8:55:9 | ll |
-| conflated.cpp:55:12:55:15 | next |
-| conflated.cpp:60:3:60:4 | ll |
-| conflated.cpp:60:7:60:10 | next |
-| conflated.cpp:61:8:61:9 | ll |
-| conflated.cpp:61:12:61:15 | next |
-| constructors.cpp:18:22:18:23 | this |
-| constructors.cpp:19:22:19:23 | this |
| constructors.cpp:20:24:20:25 | this |
| constructors.cpp:21:24:21:25 | this |
| qualifiers.cpp:9:30:9:33 | this |
| qualifiers.cpp:12:49:12:53 | inner |
| qualifiers.cpp:13:51:13:55 | inner |
-| qualifiers.cpp:18:32:18:36 | this |
-| qualifiers.cpp:22:11:22:18 | call to getInner |
-| qualifiers.cpp:23:10:23:14 | outer |
-| qualifiers.cpp:23:16:23:20 | inner |
-| qualifiers.cpp:28:10:28:14 | outer |
-| qualifiers.cpp:28:16:28:20 | inner |
-| qualifiers.cpp:33:10:33:14 | outer |
-| qualifiers.cpp:33:16:33:20 | inner |
-| qualifiers.cpp:38:10:38:14 | outer |
-| qualifiers.cpp:38:16:38:20 | inner |
-| qualifiers.cpp:42:6:42:22 | * ... |
-| qualifiers.cpp:43:10:43:14 | outer |
-| qualifiers.cpp:43:16:43:20 | inner |
-| qualifiers.cpp:47:15:47:22 | call to getInner |
-| qualifiers.cpp:48:10:48:14 | outer |
-| qualifiers.cpp:48:16:48:20 | inner |
-| realistic.cpp:49:9:49:11 | foo |
| realistic.cpp:49:9:49:18 | access to array |
-| realistic.cpp:53:9:53:11 | foo |
-| realistic.cpp:53:9:53:18 | access to array |
-| realistic.cpp:53:20:53:22 | baz |
-| realistic.cpp:53:25:53:33 | userInput |
-| realistic.cpp:54:16:54:18 | foo |
-| realistic.cpp:54:16:54:25 | access to array |
-| realistic.cpp:54:27:54:29 | baz |
-| realistic.cpp:54:32:54:40 | userInput |
-| realistic.cpp:55:12:55:14 | foo |
-| realistic.cpp:55:12:55:21 | access to array |
-| realistic.cpp:55:23:55:25 | baz |
-| realistic.cpp:55:28:55:36 | userInput |
-| realistic.cpp:57:88:57:90 | foo |
-| realistic.cpp:57:88:57:97 | access to array |
-| realistic.cpp:57:99:57:101 | baz |
-| realistic.cpp:57:104:57:112 | userInput |
-| realistic.cpp:60:21:60:23 | foo |
-| realistic.cpp:60:21:60:30 | access to array |
-| realistic.cpp:60:32:60:34 | baz |
-| realistic.cpp:60:37:60:45 | userInput |
-| realistic.cpp:60:55:60:57 | foo |
-| realistic.cpp:60:55:60:64 | access to array |
-| realistic.cpp:60:66:60:68 | baz |
-| realistic.cpp:60:71:60:79 | userInput |
-| realistic.cpp:61:21:61:23 | foo |
-| realistic.cpp:61:21:61:30 | access to array |
-| realistic.cpp:61:32:61:34 | baz |
-| realistic.cpp:61:37:61:45 | userInput |
-| realistic.cpp:65:21:65:23 | foo |
-| realistic.cpp:65:21:65:30 | access to array |
-| realistic.cpp:65:32:65:34 | baz |
-| realistic.cpp:65:37:65:45 | userInput |
-| simple.cpp:18:22:18:23 | this |
-| simple.cpp:19:22:19:23 | this |
| simple.cpp:20:24:20:25 | this |
| simple.cpp:21:24:21:25 | this |
| simple.cpp:65:5:65:5 | a |
-| simple.cpp:67:10:67:11 | a2 |
-| simple.cpp:79:16:79:17 | f2 |
-| simple.cpp:79:16:79:17 | this |
| simple.cpp:83:9:83:10 | f2 |
-| simple.cpp:83:9:83:10 | this |
| simple.cpp:92:5:92:5 | a |
-| simple.cpp:94:10:94:11 | a2 |
-| simple.cpp:104:5:104:5 | b |
-| simple.cpp:104:7:104:7 | a |
-| simple.cpp:106:10:106:11 | b2 |
-| simple.cpp:106:13:106:13 | a |
-| struct_init.c:15:8:15:9 | ab |
-| struct_init.c:16:8:16:9 | ab |
-| struct_init.c:22:8:22:9 | ab |
-| struct_init.c:23:8:23:9 | ab |
-| struct_init.c:31:8:31:12 | outer |
-| struct_init.c:31:14:31:21 | nestedAB |
-| struct_init.c:32:8:32:12 | outer |
-| struct_init.c:32:14:32:21 | nestedAB |
-| struct_init.c:33:8:33:12 | outer |
-| struct_init.c:33:14:33:22 | pointerAB |
-| struct_init.c:34:8:34:12 | outer |
-| struct_init.c:34:14:34:22 | pointerAB |
| struct_init.c:36:11:36:15 | outer |
-| struct_init.c:46:10:46:14 | outer |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected b/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected
index 9821d527b87..9b03e4f8039 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/partial-definition.expected
@@ -220,18 +220,6 @@
| aliasing.cpp:205:15:205:24 | & ... |
| aliasing.cpp:205:16:205:18 | ps2 |
| aliasing.cpp:205:21:205:21 | s |
-| aliasing.cpp:211:3:211:4 | s2 |
-| aliasing.cpp:211:6:211:6 | s |
-| aliasing.cpp:211:8:211:9 | m1 |
-| aliasing.cpp:218:3:218:4 | s2 |
-| aliasing.cpp:218:6:218:6 | s |
-| aliasing.cpp:218:8:218:9 | m1 |
-| aliasing.cpp:225:15:225:22 | & ... |
-| aliasing.cpp:225:16:225:17 | s2 |
-| aliasing.cpp:225:19:225:19 | s |
-| aliasing.cpp:232:15:232:22 | & ... |
-| aliasing.cpp:232:16:232:17 | s2 |
-| aliasing.cpp:232:19:232:19 | s |
| arrays.cpp:6:3:6:8 | access to array |
| arrays.cpp:15:3:15:10 | * ... |
| arrays.cpp:36:3:36:3 | o |
@@ -352,8 +340,6 @@
| by_reference.cpp:135:27:135:27 | a |
| by_reference.cpp:136:8:136:13 | pouter |
| by_reference.cpp:136:16:136:16 | a |
-| by_reference.cpp:140:3:140:5 | * ... |
-| by_reference.cpp:145:15:145:16 | & ... |
| complex.cpp:11:22:11:23 | a_ |
| complex.cpp:11:22:11:23 | this |
| complex.cpp:12:22:12:23 | b_ |
@@ -498,9 +484,6 @@
| simple.cpp:84:14:84:20 | this |
| simple.cpp:92:5:92:5 | a |
| simple.cpp:92:7:92:7 | i |
-| simple.cpp:104:5:104:5 | b |
-| simple.cpp:104:7:104:7 | a |
-| simple.cpp:104:9:104:9 | i |
| struct_init.c:15:8:15:9 | ab |
| struct_init.c:15:12:15:12 | a |
| struct_init.c:16:8:16:9 | ab |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected b/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected
index 6ea374d0ddf..6604dde87d4 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected
+++ b/cpp/ql/test/library-tests/dataflow/fields/path-flow.expected
@@ -160,7 +160,6 @@ edges
| aliasing.cpp:106:4:106:5 | pa [inner post update] | aliasing.cpp:175:15:175:22 | ref arg & ... |
| aliasing.cpp:106:4:106:5 | pa [inner post update] | aliasing.cpp:187:15:187:22 | ref arg & ... |
| aliasing.cpp:106:4:106:5 | pa [inner post update] | aliasing.cpp:200:15:200:24 | ref arg & ... |
-| aliasing.cpp:106:4:106:5 | pa [inner post update] | aliasing.cpp:225:15:225:22 | ref arg & ... |
| aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:106:4:106:5 | pa [inner post update] |
| aliasing.cpp:158:15:158:15 | s [post update] [data] | aliasing.cpp:159:9:159:9 | s [data] |
| aliasing.cpp:158:17:158:20 | ref arg data | aliasing.cpp:158:15:158:15 | s [post update] [data] |
@@ -188,20 +187,6 @@ edges
| aliasing.cpp:200:23:200:24 | m1 [inner post update] | aliasing.cpp:200:21:200:21 | s [post update] [m1] |
| aliasing.cpp:201:8:201:10 | ps2 [s, m1] | aliasing.cpp:201:13:201:13 | s [m1] |
| aliasing.cpp:201:13:201:13 | s [m1] | aliasing.cpp:201:15:201:16 | m1 |
-| aliasing.cpp:211:3:211:4 | s2 [post update] [s, m1] | aliasing.cpp:212:9:212:10 | s2 [s, m1] |
-| aliasing.cpp:211:3:211:24 | ... = ... | aliasing.cpp:211:6:211:6 | s [post update] [m1] |
-| aliasing.cpp:211:6:211:6 | s [post update] [m1] | aliasing.cpp:211:3:211:4 | s2 [post update] [s, m1] |
-| aliasing.cpp:211:13:211:22 | call to user_input | aliasing.cpp:211:3:211:24 | ... = ... |
-| aliasing.cpp:212:9:212:10 | s2 [s, m1] | aliasing.cpp:212:12:212:12 | s [m1] |
-| aliasing.cpp:212:12:212:12 | s [m1] | aliasing.cpp:213:8:213:8 | s [m1] |
-| aliasing.cpp:213:8:213:8 | s [m1] | aliasing.cpp:213:10:213:11 | m1 |
-| aliasing.cpp:225:15:225:22 | ref arg & ... | aliasing.cpp:225:21:225:22 | m1 [inner post update] |
-| aliasing.cpp:225:16:225:17 | s2 [post update] [s, m1] | aliasing.cpp:226:9:226:10 | s2 [s, m1] |
-| aliasing.cpp:225:19:225:19 | s [post update] [m1] | aliasing.cpp:225:16:225:17 | s2 [post update] [s, m1] |
-| aliasing.cpp:225:21:225:22 | m1 [inner post update] | aliasing.cpp:225:19:225:19 | s [post update] [m1] |
-| aliasing.cpp:226:9:226:10 | s2 [s, m1] | aliasing.cpp:226:12:226:12 | s [m1] |
-| aliasing.cpp:226:12:226:12 | s [m1] | aliasing.cpp:227:8:227:8 | s [m1] |
-| aliasing.cpp:227:8:227:8 | s [m1] | aliasing.cpp:227:10:227:11 | m1 |
| arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:7:8:7:13 | access to array |
| arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:8:8:8:13 | access to array |
| arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:9:8:9:11 | * ... |
@@ -323,9 +308,6 @@ edges
| by_reference.cpp:135:8:135:13 | pouter [inner_ptr, a] | by_reference.cpp:135:16:135:24 | inner_ptr [a] |
| by_reference.cpp:135:16:135:24 | inner_ptr [a] | by_reference.cpp:135:27:135:27 | a |
| by_reference.cpp:136:8:136:13 | pouter [a] | by_reference.cpp:136:16:136:16 | a |
-| by_reference.cpp:140:4:140:5 | pa [inner post update] | by_reference.cpp:145:15:145:16 | ref arg & ... |
-| by_reference.cpp:140:16:140:25 | call to user_input | by_reference.cpp:140:4:140:5 | pa [inner post update] |
-| by_reference.cpp:145:15:145:16 | ref arg & ... | by_reference.cpp:146:8:146:8 | s |
| complex.cpp:40:17:40:17 | b [inner, f, a_] | complex.cpp:42:8:42:8 | b [inner, f, a_] |
| complex.cpp:40:17:40:17 | b [inner, f, b_] | complex.cpp:43:8:43:8 | b [inner, f, b_] |
| complex.cpp:42:8:42:8 | b [inner, f, a_] | complex.cpp:42:10:42:14 | inner [f, a_] |
@@ -467,12 +449,6 @@ edges
| simple.cpp:92:5:92:22 | ... = ... | simple.cpp:92:5:92:5 | a [post update] [i] |
| simple.cpp:92:11:92:20 | call to user_input | simple.cpp:92:5:92:22 | ... = ... |
| simple.cpp:94:10:94:11 | a2 [i] | simple.cpp:94:13:94:13 | i |
-| simple.cpp:104:5:104:5 | b [post update] [a, i] | simple.cpp:106:10:106:11 | b2 [a, i] |
-| simple.cpp:104:5:104:24 | ... = ... | simple.cpp:104:7:104:7 | a [post update] [i] |
-| simple.cpp:104:7:104:7 | a [post update] [i] | simple.cpp:104:5:104:5 | b [post update] [a, i] |
-| simple.cpp:104:13:104:22 | call to user_input | simple.cpp:104:5:104:24 | ... = ... |
-| simple.cpp:106:10:106:11 | b2 [a, i] | simple.cpp:106:13:106:13 | a [i] |
-| simple.cpp:106:13:106:13 | a [i] | simple.cpp:106:15:106:15 | i |
| struct_init.c:14:24:14:25 | ab [a] | struct_init.c:15:8:15:9 | ab [a] |
| struct_init.c:15:8:15:9 | ab [a] | struct_init.c:15:12:15:12 | a |
| struct_init.c:20:17:20:36 | {...} [a] | struct_init.c:22:8:22:9 | ab [a] |
@@ -713,22 +689,6 @@ nodes
| aliasing.cpp:201:8:201:10 | ps2 [s, m1] | semmle.label | ps2 [s, m1] |
| aliasing.cpp:201:13:201:13 | s [m1] | semmle.label | s [m1] |
| aliasing.cpp:201:15:201:16 | m1 | semmle.label | m1 |
-| aliasing.cpp:211:3:211:4 | s2 [post update] [s, m1] | semmle.label | s2 [post update] [s, m1] |
-| aliasing.cpp:211:3:211:24 | ... = ... | semmle.label | ... = ... |
-| aliasing.cpp:211:6:211:6 | s [post update] [m1] | semmle.label | s [post update] [m1] |
-| aliasing.cpp:211:13:211:22 | call to user_input | semmle.label | call to user_input |
-| aliasing.cpp:212:9:212:10 | s2 [s, m1] | semmle.label | s2 [s, m1] |
-| aliasing.cpp:212:12:212:12 | s [m1] | semmle.label | s [m1] |
-| aliasing.cpp:213:8:213:8 | s [m1] | semmle.label | s [m1] |
-| aliasing.cpp:213:10:213:11 | m1 | semmle.label | m1 |
-| aliasing.cpp:225:15:225:22 | ref arg & ... | semmle.label | ref arg & ... |
-| aliasing.cpp:225:16:225:17 | s2 [post update] [s, m1] | semmle.label | s2 [post update] [s, m1] |
-| aliasing.cpp:225:19:225:19 | s [post update] [m1] | semmle.label | s [post update] [m1] |
-| aliasing.cpp:225:21:225:22 | m1 [inner post update] | semmle.label | m1 [inner post update] |
-| aliasing.cpp:226:9:226:10 | s2 [s, m1] | semmle.label | s2 [s, m1] |
-| aliasing.cpp:226:12:226:12 | s [m1] | semmle.label | s [m1] |
-| aliasing.cpp:227:8:227:8 | s [m1] | semmle.label | s [m1] |
-| aliasing.cpp:227:10:227:11 | m1 | semmle.label | m1 |
| arrays.cpp:6:12:6:21 | call to user_input | semmle.label | call to user_input |
| arrays.cpp:7:8:7:13 | access to array | semmle.label | access to array |
| arrays.cpp:8:8:8:13 | access to array | semmle.label | access to array |
@@ -858,10 +818,6 @@ nodes
| by_reference.cpp:135:27:135:27 | a | semmle.label | a |
| by_reference.cpp:136:8:136:13 | pouter [a] | semmle.label | pouter [a] |
| by_reference.cpp:136:16:136:16 | a | semmle.label | a |
-| by_reference.cpp:140:4:140:5 | pa [inner post update] | semmle.label | pa [inner post update] |
-| by_reference.cpp:140:16:140:25 | call to user_input | semmle.label | call to user_input |
-| by_reference.cpp:145:15:145:16 | ref arg & ... | semmle.label | ref arg & ... |
-| by_reference.cpp:146:8:146:8 | s | semmle.label | s |
| complex.cpp:40:17:40:17 | b [inner, f, a_] | semmle.label | b [inner, f, a_] |
| complex.cpp:40:17:40:17 | b [inner, f, b_] | semmle.label | b [inner, f, b_] |
| complex.cpp:42:8:42:8 | b [inner, f, a_] | semmle.label | b [inner, f, a_] |
@@ -1024,13 +980,6 @@ nodes
| simple.cpp:92:11:92:20 | call to user_input | semmle.label | call to user_input |
| simple.cpp:94:10:94:11 | a2 [i] | semmle.label | a2 [i] |
| simple.cpp:94:13:94:13 | i | semmle.label | i |
-| simple.cpp:104:5:104:5 | b [post update] [a, i] | semmle.label | b [post update] [a, i] |
-| simple.cpp:104:5:104:24 | ... = ... | semmle.label | ... = ... |
-| simple.cpp:104:7:104:7 | a [post update] [i] | semmle.label | a [post update] [i] |
-| simple.cpp:104:13:104:22 | call to user_input | semmle.label | call to user_input |
-| simple.cpp:106:10:106:11 | b2 [a, i] | semmle.label | b2 [a, i] |
-| simple.cpp:106:13:106:13 | a [i] | semmle.label | a [i] |
-| simple.cpp:106:15:106:15 | i | semmle.label | i |
| struct_init.c:14:24:14:25 | ab [a] | semmle.label | ab [a] |
| struct_init.c:15:8:15:9 | ab [a] | semmle.label | ab [a] |
| struct_init.c:15:12:15:12 | a | semmle.label | a |
@@ -1096,8 +1045,6 @@ nodes
| aliasing.cpp:176:13:176:14 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:176:13:176:14 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
| aliasing.cpp:189:15:189:16 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:189:15:189:16 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
| aliasing.cpp:201:15:201:16 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:201:15:201:16 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
-| aliasing.cpp:213:10:213:11 | m1 | aliasing.cpp:211:13:211:22 | call to user_input | aliasing.cpp:213:10:213:11 | m1 | m1 flows from $@ | aliasing.cpp:211:13:211:22 | call to user_input | call to user_input |
-| aliasing.cpp:227:10:227:11 | m1 | aliasing.cpp:106:9:106:18 | call to user_input | aliasing.cpp:227:10:227:11 | m1 | m1 flows from $@ | aliasing.cpp:106:9:106:18 | call to user_input | call to user_input |
| arrays.cpp:7:8:7:13 | access to array | arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:7:8:7:13 | access to array | access to array flows from $@ | arrays.cpp:6:12:6:21 | call to user_input | call to user_input |
| arrays.cpp:8:8:8:13 | access to array | arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:8:8:8:13 | access to array | access to array flows from $@ | arrays.cpp:6:12:6:21 | call to user_input | call to user_input |
| arrays.cpp:9:8:9:11 | * ... | arrays.cpp:6:12:6:21 | call to user_input | arrays.cpp:9:8:9:11 | * ... | * ... flows from $@ | arrays.cpp:6:12:6:21 | call to user_input | call to user_input |
@@ -1124,7 +1071,6 @@ nodes
| by_reference.cpp:134:29:134:29 | a | by_reference.cpp:88:13:88:22 | call to user_input | by_reference.cpp:134:29:134:29 | a | a flows from $@ | by_reference.cpp:88:13:88:22 | call to user_input | call to user_input |
| by_reference.cpp:135:27:135:27 | a | by_reference.cpp:88:13:88:22 | call to user_input | by_reference.cpp:135:27:135:27 | a | a flows from $@ | by_reference.cpp:88:13:88:22 | call to user_input | call to user_input |
| by_reference.cpp:136:16:136:16 | a | by_reference.cpp:96:8:96:17 | call to user_input | by_reference.cpp:136:16:136:16 | a | a flows from $@ | by_reference.cpp:96:8:96:17 | call to user_input | call to user_input |
-| by_reference.cpp:146:8:146:8 | s | by_reference.cpp:140:16:140:25 | call to user_input | by_reference.cpp:146:8:146:8 | s | s flows from $@ | by_reference.cpp:140:16:140:25 | call to user_input | call to user_input |
| complex.cpp:42:18:42:18 | call to a | complex.cpp:53:19:53:28 | call to user_input | complex.cpp:42:18:42:18 | call to a | call to a flows from $@ | complex.cpp:53:19:53:28 | call to user_input | call to user_input |
| complex.cpp:42:18:42:18 | call to a | complex.cpp:55:19:55:28 | call to user_input | complex.cpp:42:18:42:18 | call to a | call to a flows from $@ | complex.cpp:55:19:55:28 | call to user_input | call to user_input |
| complex.cpp:43:18:43:18 | call to b | complex.cpp:54:19:54:28 | call to user_input | complex.cpp:43:18:43:18 | call to b | call to b flows from $@ | complex.cpp:54:19:54:28 | call to user_input | call to user_input |
@@ -1152,7 +1098,6 @@ nodes
| simple.cpp:67:13:67:13 | i | simple.cpp:65:11:65:20 | call to user_input | simple.cpp:67:13:67:13 | i | i flows from $@ | simple.cpp:65:11:65:20 | call to user_input | call to user_input |
| simple.cpp:84:14:84:20 | call to getf2f1 | simple.cpp:83:17:83:26 | call to user_input | simple.cpp:84:14:84:20 | call to getf2f1 | call to getf2f1 flows from $@ | simple.cpp:83:17:83:26 | call to user_input | call to user_input |
| simple.cpp:94:13:94:13 | i | simple.cpp:92:11:92:20 | call to user_input | simple.cpp:94:13:94:13 | i | i flows from $@ | simple.cpp:92:11:92:20 | call to user_input | call to user_input |
-| simple.cpp:106:15:106:15 | i | simple.cpp:104:13:104:22 | call to user_input | simple.cpp:106:15:106:15 | i | i flows from $@ | simple.cpp:104:13:104:22 | call to user_input | call to user_input |
| struct_init.c:15:12:15:12 | a | struct_init.c:20:20:20:29 | call to user_input | struct_init.c:15:12:15:12 | a | a flows from $@ | struct_init.c:20:20:20:29 | call to user_input | call to user_input |
| struct_init.c:15:12:15:12 | a | struct_init.c:27:7:27:16 | call to user_input | struct_init.c:15:12:15:12 | a | a flows from $@ | struct_init.c:27:7:27:16 | call to user_input | call to user_input |
| struct_init.c:15:12:15:12 | a | struct_init.c:40:20:40:29 | call to user_input | struct_init.c:15:12:15:12 | a | a flows from $@ | struct_init.c:40:20:40:29 | call to user_input | call to user_input |
diff --git a/cpp/ql/test/library-tests/dataflow/fields/simple.cpp b/cpp/ql/test/library-tests/dataflow/fields/simple.cpp
index 829974a1b67..e4d4f70edb0 100644
--- a/cpp/ql/test/library-tests/dataflow/fields/simple.cpp
+++ b/cpp/ql/test/library-tests/dataflow/fields/simple.cpp
@@ -94,16 +94,4 @@ void single_field_test_typedef(A_typedef a)
sink(a2.i); //$ ast,ir
}
-struct B {
- A a;
-};
-
-void single_field_test_depth_2()
-{
- B b;
- b.a.i = user_input();
- B b2 = b;
- sink(b2.a.i); //$ ast MISSING: ir
-}
-
} // namespace Simple
diff --git a/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected b/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected
index 4f709d960cc..6aeadb2f174 100644
--- a/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected
+++ b/cpp/ql/test/library-tests/syntax-zoo/dataflow-ir-consistency.expected
@@ -1464,93 +1464,117 @@ unreachableNodeCCtx
localCallNodes
postIsNotPre
postHasUniquePre
-| allocators.cpp:16:14:16:36 | Foo output argument | PostUpdateNode should have one pre-update node but has 0. |
-| condition_decls.cpp:16:19:16:20 | BoxedInt output argument | PostUpdateNode should have one pre-update node but has 0. |
-| condition_decls.cpp:26:23:26:24 | BoxedInt output argument | PostUpdateNode should have one pre-update node but has 0. |
-| condition_decls.cpp:41:22:41:23 | BoxedInt output argument | PostUpdateNode should have one pre-update node but has 0. |
-| condition_decls.cpp:48:22:48:24 | BoxedInt output argument | PostUpdateNode should have one pre-update node but has 0. |
-| condition_decls.cpp:48:34:48:36 | BoxedInt output argument | PostUpdateNode should have one pre-update node but has 0. |
-| condition_decls.cpp:48:52:48:53 | BoxedInt output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:30:9:30:13 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:30:18:30:22 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:33:9:33:13 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:33:18:33:22 | C1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:39:9:39:13 | C2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:39:18:39:22 | C2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:42:9:42:13 | C2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| conditional_destructors.cpp:42:18:42:22 | C2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| constructorinitializer.cpp:8:6:8:18 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:77:19:77:21 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:82:11:82:14 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:82:17:82:55 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:82:45:82:48 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:82:51:82:51 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:88:25:88:30 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp11.cpp:88:33:88:38 | Val output argument | PostUpdateNode should have one pre-update node but has 0. |
-| cpp17.cpp:15:5:15:45 | HasTwoArgCtor output argument | PostUpdateNode should have one pre-update node but has 0. |
-| destructors.cpp:50:9:50:13 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| destructors.cpp:51:36:51:38 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| file://:0:0:0:0 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:616:12:616:13 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:617:15:617:22 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:619:16:619:30 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:662:9:662:19 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:663:5:663:5 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:736:5:736:19 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:745:8:745:8 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:748:10:748:10 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:757:12:757:12 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:757:12:757:12 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:766:13:766:13 | Middle output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:766:13:766:13 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:775:15:775:15 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:775:15:775:15 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:784:15:784:15 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:784:15:784:15 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:793:15:793:15 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:793:15:793:15 | MiddleVB1 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:793:15:793:15 | MiddleVB2 output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:793:15:793:15 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:800:8:800:8 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:801:10:801:10 | Middle output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:802:11:802:11 | Derived output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:809:7:809:13 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:810:7:810:26 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:823:7:823:13 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:824:7:824:26 | Base output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:846:8:846:8 | PolymorphicBase output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:850:19:850:19 | PolymorphicBase output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:851:22:851:22 | PolymorphicDerived output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:868:3:868:12 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:944:3:944:14 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ir.cpp:945:3:945:27 | String output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ms_assume.cpp:28:18:28:23 | fgets output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ms_try_mix.cpp:11:12:11:15 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ms_try_mix.cpp:28:12:28:15 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ms_try_mix.cpp:48:10:48:13 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| newexpr.cpp:8:2:8:20 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| ops.cpp:26:31:26:53 | C_with_constr_destr output argument | PostUpdateNode should have one pre-update node but has 0. |
-| parameterinitializer.cpp:25:5:25:8 | c output argument | PostUpdateNode should have one pre-update node but has 0. |
-| static_init_templates.cpp:31:10:31:11 | MyClass output argument | PostUpdateNode should have one pre-update node but has 0. |
-| static_init_templates.cpp:236:7:236:7 | MyConstructorClass output argument | PostUpdateNode should have one pre-update node but has 0. |
-| static_init_templates.cpp:240:7:240:7 | MyConstructorClass output argument | PostUpdateNode should have one pre-update node but has 0. |
-| static_init_templates.cpp:249:21:249:23 | MyConstructorClass output argument | PostUpdateNode should have one pre-update node but has 0. |
-| static_init_templates.cpp:250:17:250:19 | MyDerivedClass output argument | PostUpdateNode should have one pre-update node but has 0. |
-| static_init_templates.cpp:251:20:251:23 | MyContainingClass output argument | PostUpdateNode should have one pre-update node but has 0. |
-| stmt_expr.cpp:13:18:13:19 | C output argument | PostUpdateNode should have one pre-update node but has 0. |
-| try_catch.cpp:7:8:7:8 | exception output argument | PostUpdateNode should have one pre-update node but has 0. |
-| try_catch.cpp:7:8:7:8 | exception output argument | PostUpdateNode should have one pre-update node but has 0. |
-| try_catch.cpp:13:5:13:16 | exn1 output argument | PostUpdateNode should have one pre-update node but has 0. |
+| assignexpr.cpp:9:2:9:12 | Store | PostUpdateNode should have one pre-update node but has 0. |
+| bad_asts.cpp:15:10:15:12 | Store | PostUpdateNode should have one pre-update node but has 0. |
+| cpp11.cpp:65:19:65:45 | Store | PostUpdateNode should have one pre-update node but has 0. |
+| ir.cpp:531:14:531:14 | Store | PostUpdateNode should have one pre-update node but has 0. |
uniquePostUpdate
postIsInSameCallable
reverseRead
argHasPostUpdate
postWithInFlow
-| cpp11.cpp:77:19:77:21 | Val output argument | PostUpdateNode should not be the target of local flow. |
-| cpp11.cpp:82:11:82:14 | Val output argument | PostUpdateNode should not be the target of local flow. |
-| cpp11.cpp:82:45:82:48 | Val output argument | PostUpdateNode should not be the target of local flow. |
-| cpp11.cpp:82:51:82:51 | Val output argument | PostUpdateNode should not be the target of local flow. |
-| ir.cpp:809:7:809:13 | Base output argument | PostUpdateNode should not be the target of local flow. |
-| ir.cpp:810:7:810:26 | Base output argument | PostUpdateNode should not be the target of local flow. |
-| ir.cpp:823:7:823:13 | Base output argument | PostUpdateNode should not be the target of local flow. |
-| ir.cpp:824:7:824:26 | Base output argument | PostUpdateNode should not be the target of local flow. |
+| aggregateinitializer.c:3:14:3:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| aggregateinitializer.c:3:21:3:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| allocators.cpp:3:27:3:27 | Chi | PostUpdateNode should not be the target of local flow. |
+| allocators.cpp:3:35:3:35 | Chi | PostUpdateNode should not be the target of local flow. |
+| allocators.cpp:4:11:4:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| allocators.cpp:4:17:4:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| assignexpr.cpp:9:2:9:12 | Store | PostUpdateNode should not be the target of local flow. |
+| bad_asts.cpp:15:10:15:12 | Store | PostUpdateNode should not be the target of local flow. |
+| builtin.c:14:26:14:26 | Chi | PostUpdateNode should not be the target of local flow. |
+| builtin.c:14:29:14:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| builtin.c:14:32:14:32 | Chi | PostUpdateNode should not be the target of local flow. |
+| builtin.c:14:35:14:35 | Chi | PostUpdateNode should not be the target of local flow. |
+| condition_decls.cpp:3:5:3:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| condition_decls.cpp:3:21:3:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| conditional_destructors.cpp:6:13:6:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| conditional_destructors.cpp:18:13:18:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| cpp11.cpp:65:19:65:45 | Store | PostUpdateNode should not be the target of local flow. |
+| cpp11.cpp:82:17:82:55 | Chi | PostUpdateNode should not be the target of local flow. |
+| cpp11.cpp:82:17:82:55 | Chi | PostUpdateNode should not be the target of local flow. |
+| cpp11.cpp:82:45:82:48 | Chi | PostUpdateNode should not be the target of local flow. |
+| defdestructordeleteexpr.cpp:4:9:4:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| deleteexpr.cpp:7:9:7:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. |
+| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. |
+| file://:0:0:0:0 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:177:5:177:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:178:5:178:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:183:5:183:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:184:5:184:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:342:5:342:10 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:428:5:428:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:429:5:429:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:504:19:504:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:504:22:504:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:505:16:505:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:505:19:505:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:506:16:506:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:506:16:506:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:513:14:513:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:513:14:513:16 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:514:14:514:26 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:514:19:514:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:514:22:514:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:515:19:515:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:515:22:515:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:515:29:515:29 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:515:32:515:32 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:516:17:516:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:516:19:516:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:516:24:516:28 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:516:26:516:26 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:521:19:521:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:521:22:521:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:521:25:521:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:522:16:522:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:522:19:522:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:531:14:531:14 | Store | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:577:16:577:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:577:19:577:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:578:19:578:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:578:22:578:22 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:579:16:579:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:579:19:579:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:643:9:643:21 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:644:9:644:23 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:645:9:645:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:659:9:659:14 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:660:13:660:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:661:9:661:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:662:9:662:19 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:663:5:663:5 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:745:8:745:8 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:745:8:745:8 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:748:10:748:10 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:754:8:754:8 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:757:12:757:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:763:8:763:8 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:766:13:766:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:775:15:775:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:784:15:784:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:793:15:793:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:943:3:943:11 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:947:3:947:25 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:962:17:962:47 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:962:17:962:47 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:962:17:962:47 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:962:26:962:30 | Chi | PostUpdateNode should not be the target of local flow. |
+| ir.cpp:962:41:962:45 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:130:5:130:11 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:131:5:131:13 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:154:32:154:32 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:154:35:154:35 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:154:40:154:40 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:154:43:154:43 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:157:14:157:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:158:14:158:18 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:160:31:160:33 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:160:31:160:33 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:220:3:223:3 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:221:10:221:10 | Chi | PostUpdateNode should not be the target of local flow. |
+| misc.c:222:10:222:10 | Chi | PostUpdateNode should not be the target of local flow. |
+| range_analysis.c:102:5:102:15 | Chi | PostUpdateNode should not be the target of local flow. |
+| static_init_templates.cpp:3:2:3:8 | Chi | PostUpdateNode should not be the target of local flow. |
+| static_init_templates.cpp:21:2:21:12 | Chi | PostUpdateNode should not be the target of local flow. |
+| static_init_templates.cpp:240:7:240:7 | Chi | PostUpdateNode should not be the target of local flow. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected
index 0299afff063..9876b9695ad 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected
@@ -59,21 +59,18 @@ edges
| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | local_size |
| test.cpp:241:2:241:32 | Chi [array content] | test.cpp:279:17:279:20 | get_size output argument [array content] |
| test.cpp:241:2:241:32 | Chi [array content] | test.cpp:295:18:295:21 | get_size output argument [array content] |
-| test.cpp:241:2:241:32 | ChiTotal [post update] [array content] | test.cpp:241:2:241:32 | Chi [array content] |
-| test.cpp:241:2:241:32 | ChiTotal [post update] [array content] | test.cpp:279:17:279:20 | get_size output argument [array content] |
-| test.cpp:241:2:241:32 | ChiTotal [post update] [array content] | test.cpp:295:18:295:21 | get_size output argument [array content] |
-| test.cpp:241:18:241:23 | call to getenv | test.cpp:241:2:241:32 | ChiTotal [post update] [array content] |
-| test.cpp:241:18:241:31 | (const char *)... | test.cpp:241:2:241:32 | ChiTotal [post update] [array content] |
+| test.cpp:241:18:241:23 | call to getenv | test.cpp:241:2:241:32 | Chi [array content] |
+| test.cpp:241:18:241:31 | (const char *)... | test.cpp:241:2:241:32 | Chi [array content] |
| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... |
| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... |
| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... |
| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... |
-| test.cpp:279:17:279:20 | get_size output argument | test.cpp:281:11:281:28 | ... * ... |
-| test.cpp:279:17:279:20 | get_size output argument | test.cpp:281:11:281:28 | ... * ... |
-| test.cpp:279:17:279:20 | get_size output argument [array content] | test.cpp:279:17:279:20 | get_size output argument |
-| test.cpp:295:18:295:21 | get_size output argument | test.cpp:298:10:298:27 | ... * ... |
-| test.cpp:295:18:295:21 | get_size output argument | test.cpp:298:10:298:27 | ... * ... |
-| test.cpp:295:18:295:21 | get_size output argument [array content] | test.cpp:295:18:295:21 | get_size output argument |
+| test.cpp:279:17:279:20 | Chi | test.cpp:281:11:281:28 | ... * ... |
+| test.cpp:279:17:279:20 | Chi | test.cpp:281:11:281:28 | ... * ... |
+| test.cpp:279:17:279:20 | get_size output argument [array content] | test.cpp:279:17:279:20 | Chi |
+| test.cpp:295:18:295:21 | Chi | test.cpp:298:10:298:27 | ... * ... |
+| test.cpp:295:18:295:21 | Chi | test.cpp:298:10:298:27 | ... * ... |
+| test.cpp:295:18:295:21 | get_size output argument [array content] | test.cpp:295:18:295:21 | Chi |
| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... |
| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... |
| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... |
@@ -147,7 +144,6 @@ nodes
| test.cpp:237:2:237:8 | Argument 0 | semmle.label | Argument 0 |
| test.cpp:241:2:241:32 | Chi [array content] | semmle.label | Chi [array content] |
| test.cpp:241:2:241:32 | ChiPartial | semmle.label | ChiPartial |
-| test.cpp:241:2:241:32 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
| test.cpp:241:18:241:23 | call to getenv | semmle.label | call to getenv |
| test.cpp:241:18:241:31 | (const char *)... | semmle.label | (const char *)... |
| test.cpp:249:20:249:25 | call to getenv | semmle.label | call to getenv |
@@ -155,12 +151,12 @@ nodes
| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... |
| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... |
| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... |
-| test.cpp:279:17:279:20 | get_size output argument | semmle.label | get_size output argument |
+| test.cpp:279:17:279:20 | Chi | semmle.label | Chi |
| test.cpp:279:17:279:20 | get_size output argument [array content] | semmle.label | get_size output argument [array content] |
| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... |
| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... |
| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... |
-| test.cpp:295:18:295:21 | get_size output argument | semmle.label | get_size output argument |
+| test.cpp:295:18:295:21 | Chi | semmle.label | Chi |
| test.cpp:295:18:295:21 | get_size output argument [array content] | semmle.label | get_size output argument [array content] |
| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... |
| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected
index c684f1da6ce..ca8dd38fc3b 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected
@@ -43,23 +43,19 @@ edges
| test.cpp:8:9:8:12 | call to rand | test.cpp:8:9:8:12 | Store |
| test.cpp:8:9:8:12 | call to rand | test.cpp:8:9:8:12 | Store |
| test.cpp:13:2:13:15 | Chi [array content] | test.cpp:30:13:30:14 | get_rand2 output argument [array content] |
-| test.cpp:13:2:13:15 | ChiTotal [post update] [array content] | test.cpp:13:2:13:15 | Chi [array content] |
-| test.cpp:13:2:13:15 | ChiTotal [post update] [array content] | test.cpp:30:13:30:14 | get_rand2 output argument [array content] |
-| test.cpp:13:10:13:13 | call to rand | test.cpp:13:2:13:15 | ChiTotal [post update] [array content] |
-| test.cpp:13:10:13:13 | call to rand | test.cpp:13:2:13:15 | ChiTotal [post update] [array content] |
+| test.cpp:13:10:13:13 | call to rand | test.cpp:13:2:13:15 | Chi [array content] |
+| test.cpp:13:10:13:13 | call to rand | test.cpp:13:2:13:15 | Chi [array content] |
| test.cpp:18:2:18:14 | Chi [array content] | test.cpp:36:13:36:13 | get_rand3 output argument [array content] |
-| test.cpp:18:2:18:14 | ChiTotal [post update] [array content] | test.cpp:18:2:18:14 | Chi [array content] |
-| test.cpp:18:2:18:14 | ChiTotal [post update] [array content] | test.cpp:36:13:36:13 | get_rand3 output argument [array content] |
-| test.cpp:18:9:18:12 | call to rand | test.cpp:18:2:18:14 | ChiTotal [post update] [array content] |
-| test.cpp:18:9:18:12 | call to rand | test.cpp:18:2:18:14 | ChiTotal [post update] [array content] |
+| test.cpp:18:9:18:12 | call to rand | test.cpp:18:2:18:14 | Chi [array content] |
+| test.cpp:18:9:18:12 | call to rand | test.cpp:18:2:18:14 | Chi [array content] |
| test.cpp:24:11:24:18 | call to get_rand | test.cpp:25:7:25:7 | r |
| test.cpp:24:11:24:18 | call to get_rand | test.cpp:25:7:25:7 | r |
-| test.cpp:30:13:30:14 | get_rand2 output argument | test.cpp:31:7:31:7 | r |
-| test.cpp:30:13:30:14 | get_rand2 output argument | test.cpp:31:7:31:7 | r |
-| test.cpp:30:13:30:14 | get_rand2 output argument [array content] | test.cpp:30:13:30:14 | get_rand2 output argument |
-| test.cpp:36:13:36:13 | get_rand3 output argument | test.cpp:37:7:37:7 | r |
-| test.cpp:36:13:36:13 | get_rand3 output argument | test.cpp:37:7:37:7 | r |
-| test.cpp:36:13:36:13 | get_rand3 output argument [array content] | test.cpp:36:13:36:13 | get_rand3 output argument |
+| test.cpp:30:13:30:14 | Chi | test.cpp:31:7:31:7 | r |
+| test.cpp:30:13:30:14 | Chi | test.cpp:31:7:31:7 | r |
+| test.cpp:30:13:30:14 | get_rand2 output argument [array content] | test.cpp:30:13:30:14 | Chi |
+| test.cpp:36:13:36:13 | Chi | test.cpp:37:7:37:7 | r |
+| test.cpp:36:13:36:13 | Chi | test.cpp:37:7:37:7 | r |
+| test.cpp:36:13:36:13 | get_rand3 output argument [array content] | test.cpp:36:13:36:13 | Chi |
nodes
| test.c:18:13:18:16 | call to rand | semmle.label | call to rand |
| test.c:18:13:18:16 | call to rand | semmle.label | call to rand |
@@ -114,24 +110,22 @@ nodes
| test.cpp:8:9:8:12 | call to rand | semmle.label | call to rand |
| test.cpp:13:2:13:15 | Chi [array content] | semmle.label | Chi [array content] |
| test.cpp:13:2:13:15 | ChiPartial | semmle.label | ChiPartial |
-| test.cpp:13:2:13:15 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
| test.cpp:13:10:13:13 | call to rand | semmle.label | call to rand |
| test.cpp:13:10:13:13 | call to rand | semmle.label | call to rand |
| test.cpp:18:2:18:14 | Chi [array content] | semmle.label | Chi [array content] |
| test.cpp:18:2:18:14 | ChiPartial | semmle.label | ChiPartial |
-| test.cpp:18:2:18:14 | ChiTotal [post update] [array content] | semmle.label | ChiTotal [post update] [array content] |
| test.cpp:18:9:18:12 | call to rand | semmle.label | call to rand |
| test.cpp:18:9:18:12 | call to rand | semmle.label | call to rand |
| test.cpp:24:11:24:18 | call to get_rand | semmle.label | call to get_rand |
| test.cpp:25:7:25:7 | r | semmle.label | r |
| test.cpp:25:7:25:7 | r | semmle.label | r |
| test.cpp:25:7:25:7 | r | semmle.label | r |
-| test.cpp:30:13:30:14 | get_rand2 output argument | semmle.label | get_rand2 output argument |
+| test.cpp:30:13:30:14 | Chi | semmle.label | Chi |
| test.cpp:30:13:30:14 | get_rand2 output argument [array content] | semmle.label | get_rand2 output argument [array content] |
| test.cpp:31:7:31:7 | r | semmle.label | r |
| test.cpp:31:7:31:7 | r | semmle.label | r |
| test.cpp:31:7:31:7 | r | semmle.label | r |
-| test.cpp:36:13:36:13 | get_rand3 output argument | semmle.label | get_rand3 output argument |
+| test.cpp:36:13:36:13 | Chi | semmle.label | Chi |
| test.cpp:36:13:36:13 | get_rand3 output argument [array content] | semmle.label | get_rand3 output argument [array content] |
| test.cpp:37:7:37:7 | r | semmle.label | r |
| test.cpp:37:7:37:7 | r | semmle.label | r |
From 691a316460622fbd4d9e2b2af42d0128b0a38687 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Wed, 3 Feb 2021 09:07:27 +0100
Subject: [PATCH 082/429] C++: Add tests to
cpp/unsigned-difference-expression-compared-zero and remove a couple of
classes of FPs.
---
...nsignedDifferenceExpressionComparedZero.ql | 19 ++++-
...dDifferenceExpressionComparedZero.expected | 10 +++
...gnedDifferenceExpressionComparedZero.qlref | 1 +
.../test.cpp | 77 +++++++++++++++++++
4 files changed, 106 insertions(+), 1 deletion(-)
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref
create mode 100644 cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp
diff --git a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql b/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
index 300b2f944b5..7b110ec24db 100644
--- a/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
+++ b/cpp/ql/src/experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
@@ -13,11 +13,28 @@
import cpp
import semmle.code.cpp.commons.Exclusions
+import semmle.code.cpp.valuenumbering.GlobalValueNumbering
+import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+import semmle.code.cpp.controlflow.Guards
+
+/** Holds if `sub` will never be nonnegative. */
+predicate nonNegative(SubExpr sub) {
+ not exprMightOverflowNegatively(sub.getFullyConverted())
+ or
+ // The subtraction is guarded by a check of the form `left >= right`.
+ exists(GuardCondition guard, Expr left, Expr right |
+ left = globalValueNumber(sub.getLeftOperand()).getAnExpr() and
+ right = globalValueNumber(sub.getRightOperand()).getAnExpr() and
+ guard.controls(sub.getBasicBlock(), true) and
+ guard.ensuresLt(left, right, 0, sub.getBasicBlock(), false)
+ )
+}
from RelationalOperation ro, SubExpr sub
where
not isFromMacroDefinition(ro) and
ro.getLesserOperand().getValue().toInt() = 0 and
ro.getGreaterOperand() = sub and
- sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned()
+ sub.getFullyConverted().getUnspecifiedType().(IntegralType).isUnsigned() and
+ not nonNegative(sub)
select ro, "Difference in condition is always greater than or equal to zero"
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected
new file mode 100644
index 00000000000..6e1b80a4c8c
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.expected
@@ -0,0 +1,10 @@
+| test.cpp:6:5:6:13 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:10:8:10:24 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:15:9:15:25 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:32:12:32:20 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:39:12:39:20 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:47:5:47:13 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:55:5:55:13 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:62:5:62:13 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:69:5:69:13 | ... > ... | Difference in condition is always greater than or equal to zero |
+| test.cpp:75:8:75:16 | ... > ... | Difference in condition is always greater than or equal to zero |
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref
new file mode 100644
index 00000000000..2d15983a540
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/UnsignedDifferenceExpressionComparedZero.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero.ql
diff --git a/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp
new file mode 100644
index 00000000000..11de69e8c62
--- /dev/null
+++ b/cpp/ql/test/experimental/query-tests/Security/CWE/CWE-191/UnsignedDifferenceExpressionComparedZero/test.cpp
@@ -0,0 +1,77 @@
+int getAnInt();
+
+bool cond();
+
+void test(unsigned x, unsigned y, bool unknown) {
+ if(x - y > 0) { } // BAD
+
+ unsigned total = getAnInt();
+ unsigned limit = getAnInt();
+ while(limit - total > 0) { // BAD
+ total += getAnInt();
+ }
+
+ if(total <= limit) {
+ while(limit - total > 0) { // GOOD [FALSE POSITIVE]
+ total += getAnInt();
+ if(total > limit) break;
+ }
+ }
+
+ if(x >= y) {
+ bool b = x - y > 0; // GOOD
+ }
+
+ if((int)(x - y) >= 0) { } // GOOD. Maybe an overflow happened, but the result is converted to the "likely intended" result before the comparison
+
+ if(unknown) {
+ y = x & 0xFF;
+ } else {
+ y = x;
+ }
+ bool b1 = x - y > 0; // GOOD [FALSE POSITIVE]
+
+ x = getAnInt();
+ y = getAnInt();
+ if(y > x) {
+ y = x - 1;
+ }
+ bool b2 = x - y > 0; // GOOD [FALSE POSITIVE]
+
+ int N = getAnInt();
+ y = x;
+ while(cond()) {
+ if(unknown) { y--; }
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ x = y;
+ while(cond()) {
+ if(unknown) break;
+ y--;
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ y = 0;
+ for(int i = 0; i < x; ++i) {
+ if(unknown) { ++y; }
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ x = y;
+ while(cond()) {
+ if(unknown) { x++; }
+ }
+
+ if(x - y > 0) { } // GOOD [FALSE POSITIVE]
+
+ int n = getAnInt();
+ if (n > x - y) { n = x - y; }
+ if (n > 0) {
+ y += n; // NOTE: `n` is at most `x - y` at this point.
+ if (x - y > 0) {} // GOOD [FALSE POSITIVE]
+ }
+}
\ No newline at end of file
From 12ee49748545effc470898a36101b2caf3882973 Mon Sep 17 00:00:00 2001
From: CaptainFreak
Date: Wed, 3 Feb 2021 15:48:02 +0530
Subject: [PATCH 083/429] move query to src, rename and refactor
---
.../CWE-073/TemplateObjectInjection.ql} | 21 +++++++------------
.../TemplateObjectInjection.js} | 0
.../TemplateObjectInjection_fixed.js} | 0
3 files changed, 8 insertions(+), 13 deletions(-)
rename javascript/ql/{test/experimental/Security/CWE-073/ExpressHbsLFR.ql => src/experimental/Security/CWE-073/TemplateObjectInjection.ql} (55%)
rename javascript/ql/{test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js => src/experimental/Security/CWE-073/documentation-examples/TemplateObjectInjection.js} (100%)
rename javascript/ql/{test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js => src/experimental/Security/CWE-073/documentation-examples/TemplateObjectInjection_fixed.js} (100%)
diff --git a/javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql b/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
similarity index 55%
rename from javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql
rename to javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
index de66932a7af..b1074ab618f 100644
--- a/javascript/ql/test/experimental/Security/CWE-073/ExpressHbsLFR.ql
+++ b/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
@@ -1,10 +1,10 @@
/**
- * @name Express-Hbs Local File Read and Potential RCE
- * @description Writing user input directly to res.render of ExpressJS used with Hbs can lead to LFR
+ * @name Template Object Injection
+ * @description Instantiating a template using a user-controlled object is vulnerable to local file read and potential remote code execution.
* @kind path-problem
* @problem.severity error
* @precision high
- * @id js/express-hbs-lfr
+ * @id js/template-object-injection
* @tags security
* external/cwe/cwe-073
* external/cwe/cwe-094
@@ -13,15 +13,9 @@
import javascript
import DataFlow
import PathGraph
-import Express
-import semmle.javascript.DynamicPropertyAccess
predicate isUsingHbsEngine() {
- exists(MethodCallExpr method |
- method.getMethodName() = "set" and
- Express::appCreation().flowsToExpr(method.getReceiver()) and
- method.getArgument(1).getStringValue().matches("hbs")
- )
+ Express::appCreation().getAMethodCall("set").getArgument(1).mayHaveStringValue("hbs")
}
class HbsLFRTaint extends TaintTracking::Configuration {
@@ -39,6 +33,7 @@ class HbsLFRTaint extends TaintTracking::Configuration {
}
}
-from HbsLFRTaint cfg, Node source, Node sink
-where cfg.hasFlow(source, sink)
-select source, sink
+from HbsLFRTaint cfg, PathNode source, PathNode sink
+where cfg.hasFlowPath(source, sink)
+select sink.getNode(), source, sink, "Template object injection due to $@.", source.getNode(),
+ "user-provided value"
diff --git a/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js b/javascript/ql/src/experimental/Security/CWE-073/documentation-examples/TemplateObjectInjection.js
similarity index 100%
rename from javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR.js
rename to javascript/ql/src/experimental/Security/CWE-073/documentation-examples/TemplateObjectInjection.js
diff --git a/javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js b/javascript/ql/src/experimental/Security/CWE-073/documentation-examples/TemplateObjectInjection_fixed.js
similarity index 100%
rename from javascript/ql/test/experimental/Security/CWE-073/documentation-examples/ExpressHbsLFR_fixed.js
rename to javascript/ql/src/experimental/Security/CWE-073/documentation-examples/TemplateObjectInjection_fixed.js
From c6a22844e2e781f766ebe747f89e701b966f67c3 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 3 Feb 2021 12:16:57 +0100
Subject: [PATCH 084/429] add test for `js/template-object-injection`
---
.../CWE-073/TemplateObjectInjection.expected | 37 +++++++++++++++++++
.../CWE-073/TemplateObjectInjection.qlref | 1 +
.../test/experimental/Security/CWE-073/tst.js | 17 +++++++++
3 files changed, 55 insertions(+)
create mode 100644 javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected
create mode 100644 javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.qlref
create mode 100644 javascript/ql/test/experimental/Security/CWE-073/tst.js
diff --git a/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected b/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected
new file mode 100644
index 00000000000..455498d5d58
--- /dev/null
+++ b/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected
@@ -0,0 +1,37 @@
+nodes
+| tst.js:5:9:5:46 | bodyParameter |
+| tst.js:5:25:5:32 | req.body |
+| tst.js:5:25:5:32 | req.body |
+| tst.js:5:25:5:46 | req.bod ... rameter |
+| tst.js:6:9:6:49 | queryParameter |
+| tst.js:6:26:6:49 | req.que ... rameter |
+| tst.js:6:26:6:49 | req.que ... rameter |
+| tst.js:8:28:8:40 | bodyParameter |
+| tst.js:8:28:8:40 | bodyParameter |
+| tst.js:9:28:9:41 | queryParameter |
+| tst.js:9:28:9:41 | queryParameter |
+| tst.js:12:32:12:44 | bodyParameter |
+| tst.js:12:32:12:44 | bodyParameter |
+| tst.js:14:28:14:41 | queryParameter |
+| tst.js:14:28:14:46 | queryParameter + "" |
+| tst.js:14:28:14:46 | queryParameter + "" |
+edges
+| tst.js:5:9:5:46 | bodyParameter | tst.js:8:28:8:40 | bodyParameter |
+| tst.js:5:9:5:46 | bodyParameter | tst.js:8:28:8:40 | bodyParameter |
+| tst.js:5:9:5:46 | bodyParameter | tst.js:12:32:12:44 | bodyParameter |
+| tst.js:5:9:5:46 | bodyParameter | tst.js:12:32:12:44 | bodyParameter |
+| tst.js:5:25:5:32 | req.body | tst.js:5:25:5:46 | req.bod ... rameter |
+| tst.js:5:25:5:32 | req.body | tst.js:5:25:5:46 | req.bod ... rameter |
+| tst.js:5:25:5:46 | req.bod ... rameter | tst.js:5:9:5:46 | bodyParameter |
+| tst.js:6:9:6:49 | queryParameter | tst.js:9:28:9:41 | queryParameter |
+| tst.js:6:9:6:49 | queryParameter | tst.js:9:28:9:41 | queryParameter |
+| tst.js:6:9:6:49 | queryParameter | tst.js:14:28:14:41 | queryParameter |
+| tst.js:6:26:6:49 | req.que ... rameter | tst.js:6:9:6:49 | queryParameter |
+| tst.js:6:26:6:49 | req.que ... rameter | tst.js:6:9:6:49 | queryParameter |
+| tst.js:14:28:14:41 | queryParameter | tst.js:14:28:14:46 | queryParameter + "" |
+| tst.js:14:28:14:41 | queryParameter | tst.js:14:28:14:46 | queryParameter + "" |
+#select
+| tst.js:8:28:8:40 | bodyParameter | tst.js:5:25:5:32 | req.body | tst.js:8:28:8:40 | bodyParameter | Template object injection due to $@. | tst.js:5:25:5:32 | req.body | user-provided value |
+| tst.js:9:28:9:41 | queryParameter | tst.js:6:26:6:49 | req.que ... rameter | tst.js:9:28:9:41 | queryParameter | Template object injection due to $@. | tst.js:6:26:6:49 | req.que ... rameter | user-provided value |
+| tst.js:12:32:12:44 | bodyParameter | tst.js:5:25:5:32 | req.body | tst.js:12:32:12:44 | bodyParameter | Template object injection due to $@. | tst.js:5:25:5:32 | req.body | user-provided value |
+| tst.js:14:28:14:46 | queryParameter + "" | tst.js:6:26:6:49 | req.que ... rameter | tst.js:14:28:14:46 | queryParameter + "" | Template object injection due to $@. | tst.js:6:26:6:49 | req.que ... rameter | user-provided value |
diff --git a/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.qlref b/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.qlref
new file mode 100644
index 00000000000..4fcdae0f812
--- /dev/null
+++ b/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.qlref
@@ -0,0 +1 @@
+experimental/Security/CWE-073/TemplateObjectInjection.ql
diff --git a/javascript/ql/test/experimental/Security/CWE-073/tst.js b/javascript/ql/test/experimental/Security/CWE-073/tst.js
new file mode 100644
index 00000000000..b42ccea740a
--- /dev/null
+++ b/javascript/ql/test/experimental/Security/CWE-073/tst.js
@@ -0,0 +1,17 @@
+var app = require('express')();
+app.set('view engine', 'hbs');
+
+app.post('/path', function(req, res) {
+ var bodyParameter = req.body.bodyParameter;
+ var queryParameter = req.query.queryParameter;
+
+ res.render('template', bodyParameter); // NOT OK
+ res.render('template', queryParameter); // NOT OK
+
+ if (typeof bodyParameter === "string") {
+ res.render('template', bodyParameter); // OK - but still flagged [INCONSISTENCY]
+ }
+ res.render('template', queryParameter + ""); // OK - but still flagged [INCONSISTENCY]
+
+ res.render('template', {profile: bodyParameter}); // OK
+});
\ No newline at end of file
From a5bde53bfe28df903ab846e2b51f8b4407b7d70d Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 3 Feb 2021 12:26:37 +0100
Subject: [PATCH 085/429] use the TaintedObject library in
`js/template-object-injection`
---
.../CWE-073/TemplateObjectInjection.ql | 27 +++++++++---
.../CWE-073/TemplateObjectInjection.expected | 42 +++++++++++++------
.../test/experimental/Security/CWE-073/tst.js | 17 ++++++--
3 files changed, 65 insertions(+), 21 deletions(-)
diff --git a/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql b/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
index b1074ab618f..583255dfeb4 100644
--- a/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
+++ b/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
@@ -11,8 +11,8 @@
*/
import javascript
-import DataFlow
-import PathGraph
+import DataFlow::PathGraph
+import semmle.javascript.security.TaintedObject
predicate isUsingHbsEngine() {
Express::appCreation().getAMethodCall("set").getArgument(1).mayHaveStringValue("hbs")
@@ -21,19 +21,34 @@ predicate isUsingHbsEngine() {
class HbsLFRTaint extends TaintTracking::Configuration {
HbsLFRTaint() { this = "HbsLFRTaint" }
- override predicate isSource(Node node) { node instanceof RemoteFlowSource }
+ override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
- override predicate isSink(Node node) {
+ override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
+ TaintedObject::isSource(source, label)
+ }
+
+ override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
+ label = TaintedObject::label() and
exists(MethodCallExpr mc |
Express::isResponse(mc.getReceiver()) and
mc.getMethodName() = "render" and
- node.asExpr() = mc.getArgument(1) and
+ sink.asExpr() = mc.getArgument(1) and
isUsingHbsEngine()
)
}
+
+ override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
+ guard instanceof TaintedObject::SanitizerGuard
+ }
+
+ override predicate isAdditionalFlowStep(
+ DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl
+ ) {
+ TaintedObject::step(src, trg, inlbl, outlbl)
+ }
}
-from HbsLFRTaint cfg, PathNode source, PathNode sink
+from HbsLFRTaint cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Template object injection due to $@.", source.getNode(),
"user-provided value"
diff --git a/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected b/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected
index 455498d5d58..f8dc8d8f47d 100644
--- a/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected
+++ b/javascript/ql/test/experimental/Security/CWE-073/TemplateObjectInjection.expected
@@ -4,34 +4,52 @@ nodes
| tst.js:5:25:5:32 | req.body |
| tst.js:5:25:5:46 | req.bod ... rameter |
| tst.js:6:9:6:49 | queryParameter |
+| tst.js:6:9:6:49 | queryParameter |
+| tst.js:6:26:6:49 | req.que ... rameter |
| tst.js:6:26:6:49 | req.que ... rameter |
| tst.js:6:26:6:49 | req.que ... rameter |
| tst.js:8:28:8:40 | bodyParameter |
| tst.js:8:28:8:40 | bodyParameter |
| tst.js:9:28:9:41 | queryParameter |
| tst.js:9:28:9:41 | queryParameter |
-| tst.js:12:32:12:44 | bodyParameter |
-| tst.js:12:32:12:44 | bodyParameter |
-| tst.js:14:28:14:41 | queryParameter |
-| tst.js:14:28:14:46 | queryParameter + "" |
-| tst.js:14:28:14:46 | queryParameter + "" |
+| tst.js:18:19:18:32 | queryParameter |
+| tst.js:18:19:18:32 | queryParameter |
+| tst.js:21:24:21:26 | obj |
+| tst.js:21:24:21:26 | obj |
+| tst.js:22:28:22:30 | obj |
+| tst.js:22:28:22:30 | obj |
+| tst.js:24:11:24:24 | str |
+| tst.js:24:17:24:19 | obj |
+| tst.js:24:17:24:24 | obj + "" |
+| tst.js:27:28:27:42 | JSON.parse(str) |
+| tst.js:27:28:27:42 | JSON.parse(str) |
+| tst.js:27:39:27:41 | str |
edges
| tst.js:5:9:5:46 | bodyParameter | tst.js:8:28:8:40 | bodyParameter |
| tst.js:5:9:5:46 | bodyParameter | tst.js:8:28:8:40 | bodyParameter |
-| tst.js:5:9:5:46 | bodyParameter | tst.js:12:32:12:44 | bodyParameter |
-| tst.js:5:9:5:46 | bodyParameter | tst.js:12:32:12:44 | bodyParameter |
| tst.js:5:25:5:32 | req.body | tst.js:5:25:5:46 | req.bod ... rameter |
| tst.js:5:25:5:32 | req.body | tst.js:5:25:5:46 | req.bod ... rameter |
| tst.js:5:25:5:46 | req.bod ... rameter | tst.js:5:9:5:46 | bodyParameter |
| tst.js:6:9:6:49 | queryParameter | tst.js:9:28:9:41 | queryParameter |
| tst.js:6:9:6:49 | queryParameter | tst.js:9:28:9:41 | queryParameter |
-| tst.js:6:9:6:49 | queryParameter | tst.js:14:28:14:41 | queryParameter |
+| tst.js:6:9:6:49 | queryParameter | tst.js:18:19:18:32 | queryParameter |
+| tst.js:6:9:6:49 | queryParameter | tst.js:18:19:18:32 | queryParameter |
| tst.js:6:26:6:49 | req.que ... rameter | tst.js:6:9:6:49 | queryParameter |
| tst.js:6:26:6:49 | req.que ... rameter | tst.js:6:9:6:49 | queryParameter |
-| tst.js:14:28:14:41 | queryParameter | tst.js:14:28:14:46 | queryParameter + "" |
-| tst.js:14:28:14:41 | queryParameter | tst.js:14:28:14:46 | queryParameter + "" |
+| tst.js:6:26:6:49 | req.que ... rameter | tst.js:6:9:6:49 | queryParameter |
+| tst.js:6:26:6:49 | req.que ... rameter | tst.js:6:9:6:49 | queryParameter |
+| tst.js:18:19:18:32 | queryParameter | tst.js:21:24:21:26 | obj |
+| tst.js:18:19:18:32 | queryParameter | tst.js:21:24:21:26 | obj |
+| tst.js:21:24:21:26 | obj | tst.js:22:28:22:30 | obj |
+| tst.js:21:24:21:26 | obj | tst.js:22:28:22:30 | obj |
+| tst.js:21:24:21:26 | obj | tst.js:24:17:24:19 | obj |
+| tst.js:24:11:24:24 | str | tst.js:27:39:27:41 | str |
+| tst.js:24:17:24:19 | obj | tst.js:24:17:24:24 | obj + "" |
+| tst.js:24:17:24:24 | obj + "" | tst.js:24:11:24:24 | str |
+| tst.js:27:39:27:41 | str | tst.js:27:28:27:42 | JSON.parse(str) |
+| tst.js:27:39:27:41 | str | tst.js:27:28:27:42 | JSON.parse(str) |
#select
| tst.js:8:28:8:40 | bodyParameter | tst.js:5:25:5:32 | req.body | tst.js:8:28:8:40 | bodyParameter | Template object injection due to $@. | tst.js:5:25:5:32 | req.body | user-provided value |
| tst.js:9:28:9:41 | queryParameter | tst.js:6:26:6:49 | req.que ... rameter | tst.js:9:28:9:41 | queryParameter | Template object injection due to $@. | tst.js:6:26:6:49 | req.que ... rameter | user-provided value |
-| tst.js:12:32:12:44 | bodyParameter | tst.js:5:25:5:32 | req.body | tst.js:12:32:12:44 | bodyParameter | Template object injection due to $@. | tst.js:5:25:5:32 | req.body | user-provided value |
-| tst.js:14:28:14:46 | queryParameter + "" | tst.js:6:26:6:49 | req.que ... rameter | tst.js:14:28:14:46 | queryParameter + "" | Template object injection due to $@. | tst.js:6:26:6:49 | req.que ... rameter | user-provided value |
+| tst.js:22:28:22:30 | obj | tst.js:6:26:6:49 | req.que ... rameter | tst.js:22:28:22:30 | obj | Template object injection due to $@. | tst.js:6:26:6:49 | req.que ... rameter | user-provided value |
+| tst.js:27:28:27:42 | JSON.parse(str) | tst.js:6:26:6:49 | req.que ... rameter | tst.js:27:28:27:42 | JSON.parse(str) | Template object injection due to $@. | tst.js:6:26:6:49 | req.que ... rameter | user-provided value |
diff --git a/javascript/ql/test/experimental/Security/CWE-073/tst.js b/javascript/ql/test/experimental/Security/CWE-073/tst.js
index b42ccea740a..9ff93d14cb5 100644
--- a/javascript/ql/test/experimental/Security/CWE-073/tst.js
+++ b/javascript/ql/test/experimental/Security/CWE-073/tst.js
@@ -9,9 +9,20 @@ app.post('/path', function(req, res) {
res.render('template', queryParameter); // NOT OK
if (typeof bodyParameter === "string") {
- res.render('template', bodyParameter); // OK - but still flagged [INCONSISTENCY]
+ res.render('template', bodyParameter); // OK
}
- res.render('template', queryParameter + ""); // OK - but still flagged [INCONSISTENCY]
+ res.render('template', queryParameter + ""); // OK
res.render('template', {profile: bodyParameter}); // OK
-});
\ No newline at end of file
+
+ indirect(res, queryParameter);
+});
+
+function indirect(res, obj) {
+ res.render("template", obj); // NOT OK
+
+ const str = obj + "";
+ res.render("template", str); // OK
+
+ res.render("template", JSON.parse(str)); // NOT OK
+}
\ No newline at end of file
From d016ba225286fd828c36833cbf6c35c861b6c5c6 Mon Sep 17 00:00:00 2001
From: Erik Krogh Kristensen
Date: Wed, 3 Feb 2021 12:29:23 +0100
Subject: [PATCH 086/429] rename name dataflow configuration in
`js/template-object-injection`
---
.../Security/CWE-073/TemplateObjectInjection.ql | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql b/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
index 583255dfeb4..8c5c19e6096 100644
--- a/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
+++ b/javascript/ql/src/experimental/Security/CWE-073/TemplateObjectInjection.ql
@@ -18,8 +18,8 @@ predicate isUsingHbsEngine() {
Express::appCreation().getAMethodCall("set").getArgument(1).mayHaveStringValue("hbs")
}
-class HbsLFRTaint extends TaintTracking::Configuration {
- HbsLFRTaint() { this = "HbsLFRTaint" }
+class TemplateObjInjectionConfig extends TaintTracking::Configuration {
+ TemplateObjInjectionConfig() { this = "TemplateObjInjectionConfig" }
override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource }
@@ -48,7 +48,7 @@ class HbsLFRTaint extends TaintTracking::Configuration {
}
}
-from HbsLFRTaint cfg, DataFlow::PathNode source, DataFlow::PathNode sink
+from DataFlow::Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Template object injection due to $@.", source.getNode(),
"user-provided value"
From 2ace10fcdfb54ee3fe7f93a5cdddfd99aedfa534 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Feb 2021 12:21:31 +0000
Subject: [PATCH 087/429] Use PostUpdateNode for wrapper method calls
---
.../Security/CWE/CWE-522/InsecureLdapAuth.ql | 9 +++--
.../CWE-522/InsecureLdapAuth.expected | 36 +++++++++----------
2 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
index b6e7b5ed702..8411a128c9c 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.ql
@@ -9,6 +9,7 @@
*/
import java
+import DataFlow
import semmle.code.java.frameworks.Jndi
import semmle.code.java.frameworks.Networking
import semmle.code.java.dataflow.TaintTracking
@@ -167,7 +168,9 @@ class BasicAuthFlowConfig extends DataFlow::Configuration {
/** Source of `simple` configuration. */
override predicate isSource(DataFlow::Node src) {
- exists(MethodAccess ma | isBasicAuthEnv(ma) and ma.getQualifier() = src.asExpr())
+ exists(MethodAccess ma |
+ isBasicAuthEnv(ma) and ma.getQualifier() = src.(PostUpdateNode).getPreUpdateNode().asExpr()
+ )
}
/** Sink of directory context creation. */
@@ -187,7 +190,9 @@ class SSLFlowConfig extends DataFlow::Configuration {
/** Source of `ssl` configuration. */
override predicate isSource(DataFlow::Node src) {
- exists(MethodAccess ma | isSSLEnv(ma) and ma.getQualifier() = src.asExpr())
+ exists(MethodAccess ma |
+ isSSLEnv(ma) and ma.getQualifier() = src.(PostUpdateNode).getPreUpdateNode().asExpr()
+ )
}
/** Sink of directory context creation. */
diff --git a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
index 1af36e67d05..863e8e55dcf 100644
--- a/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
+++ b/java/ql/test/experimental/query-tests/security/CWE-522/InsecureLdapAuth.expected
@@ -1,23 +1,21 @@
edges
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:20:49:20:59 | environment |
-| InsecureLdapAuth.java:17:52:17:59 | "simple" : String | InsecureLdapAuth.java:20:49:20:59 | environment |
+| InsecureLdapAuth.java:17:3:17:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:20:49:20:59 | environment |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | InsecureLdapAuth.java:34:49:34:59 | environment |
-| InsecureLdapAuth.java:31:52:31:59 | "simple" : String | InsecureLdapAuth.java:34:49:34:59 | environment |
-| InsecureLdapAuth.java:45:52:45:59 | "simple" : String | InsecureLdapAuth.java:48:49:48:59 | environment |
+| InsecureLdapAuth.java:31:3:31:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:34:49:34:59 | environment |
+| InsecureLdapAuth.java:45:3:45:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:48:49:48:59 | environment |
| InsecureLdapAuth.java:53:20:53:50 | "ldap://ad.your-server.com:636" : String | InsecureLdapAuth.java:63:49:63:59 | environment |
-| InsecureLdapAuth.java:59:52:59:59 | "simple" : String | InsecureLdapAuth.java:63:49:63:59 | environment |
-| InsecureLdapAuth.java:62:46:62:50 | "ssl" : String | InsecureLdapAuth.java:63:49:63:59 | environment |
+| InsecureLdapAuth.java:59:3:59:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:63:49:63:59 | environment |
+| InsecureLdapAuth.java:62:3:62:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:63:49:63:59 | environment |
| InsecureLdapAuth.java:68:20:68:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:77:49:77:59 | environment |
-| InsecureLdapAuth.java:88:52:88:59 | "simple" : String | InsecureLdapAuth.java:91:49:91:59 | environment |
+| InsecureLdapAuth.java:88:3:88:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:91:49:91:59 | environment |
| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:105:59:105:69 | environment |
-| InsecureLdapAuth.java:102:52:102:59 | "simple" : String | InsecureLdapAuth.java:105:59:105:69 | environment |
+| InsecureLdapAuth.java:102:3:102:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:105:59:105:69 | environment |
| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | InsecureLdapAuth.java:120:49:120:59 | environment |
-| InsecureLdapAuth.java:117:58:117:65 | "simple" : String | InsecureLdapAuth.java:120:49:120:59 | environment |
+| InsecureLdapAuth.java:117:3:117:13 | environment [post update] : Hashtable | InsecureLdapAuth.java:120:49:120:59 | environment |
| InsecureLdapAuth.java:124:3:124:5 | env [post update] : Hashtable | InsecureLdapAuth.java:137:10:137:20 | environment [post update] : Hashtable |
-| InsecureLdapAuth.java:124:38:124:42 | "ssl" : String | InsecureLdapAuth.java:124:3:124:5 | env [post update] : Hashtable |
| InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable | InsecureLdapAuth.java:141:16:141:26 | environment [post update] : Hashtable |
| InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable | InsecureLdapAuth.java:152:16:152:26 | environment [post update] : Hashtable |
-| InsecureLdapAuth.java:128:44:128:51 | "simple" : String | InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable |
| InsecureLdapAuth.java:135:20:135:39 | ... + ... : String | InsecureLdapAuth.java:142:50:142:60 | environment |
| InsecureLdapAuth.java:137:10:137:20 | environment [post update] : Hashtable | InsecureLdapAuth.java:142:50:142:60 | environment |
| InsecureLdapAuth.java:141:16:141:26 | environment [post update] : Hashtable | InsecureLdapAuth.java:142:50:142:60 | environment |
@@ -25,37 +23,35 @@ edges
| InsecureLdapAuth.java:152:16:152:26 | environment [post update] : Hashtable | InsecureLdapAuth.java:153:50:153:60 | environment |
nodes
| InsecureLdapAuth.java:11:20:11:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:17:52:17:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:17:3:17:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:20:49:20:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:20:49:20:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:25:20:25:39 | ... + ... : String | semmle.label | ... + ... : String |
-| InsecureLdapAuth.java:31:52:31:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:31:3:31:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:34:49:34:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:34:49:34:59 | environment | semmle.label | environment |
-| InsecureLdapAuth.java:45:52:45:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:45:3:45:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:48:49:48:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:53:20:53:50 | "ldap://ad.your-server.com:636" : String | semmle.label | "ldap://ad.your-server.com:636" : String |
-| InsecureLdapAuth.java:59:52:59:59 | "simple" : String | semmle.label | "simple" : String |
-| InsecureLdapAuth.java:62:46:62:50 | "ssl" : String | semmle.label | "ssl" : String |
+| InsecureLdapAuth.java:59:3:59:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
+| InsecureLdapAuth.java:62:3:62:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:63:49:63:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:63:49:63:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:63:49:63:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:68:20:68:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
| InsecureLdapAuth.java:77:49:77:59 | environment | semmle.label | environment |
-| InsecureLdapAuth.java:88:52:88:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:88:3:88:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:91:49:91:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:96:20:96:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:102:52:102:59 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:102:3:102:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:105:59:105:69 | environment | semmle.label | environment |
| InsecureLdapAuth.java:105:59:105:69 | environment | semmle.label | environment |
| InsecureLdapAuth.java:111:20:111:50 | "ldap://ad.your-server.com:389" : String | semmle.label | "ldap://ad.your-server.com:389" : String |
-| InsecureLdapAuth.java:117:58:117:65 | "simple" : String | semmle.label | "simple" : String |
+| InsecureLdapAuth.java:117:3:117:13 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:120:49:120:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:120:49:120:59 | environment | semmle.label | environment |
| InsecureLdapAuth.java:124:3:124:5 | env [post update] : Hashtable | semmle.label | env [post update] : Hashtable |
-| InsecureLdapAuth.java:124:38:124:42 | "ssl" : String | semmle.label | "ssl" : String |
| InsecureLdapAuth.java:128:3:128:5 | env [post update] : Hashtable | semmle.label | env [post update] : Hashtable |
-| InsecureLdapAuth.java:128:44:128:51 | "simple" : String | semmle.label | "simple" : String |
| InsecureLdapAuth.java:135:20:135:39 | ... + ... : String | semmle.label | ... + ... : String |
| InsecureLdapAuth.java:137:10:137:20 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
| InsecureLdapAuth.java:141:16:141:26 | environment [post update] : Hashtable | semmle.label | environment [post update] : Hashtable |
From 8cf8b704c580191c34ec68a6fe3026160e866491 Mon Sep 17 00:00:00 2001
From: Mathias Vorreiter Pedersen
Date: Wed, 3 Feb 2021 16:16:48 +0100
Subject: [PATCH 088/429] C++: Add more indirection flow in dataflow models.
Also revert the additions to DataFlowUtil added in #5035 as they can add too
much flow.
---
.../cpp/ir/dataflow/internal/DataFlowUtil.qll | 21 ---------------
.../implementations/IdentityFunction.qll | 2 ++
.../cpp/models/implementations/Iterator.qll | 7 +++++
.../cpp/models/implementations/StdString.qll | 27 +++++++++++++++++++
.../cpp/models/implementations/Strset.qll | 3 +++
5 files changed, 39 insertions(+), 21 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
index aba8e3bceec..37bcd335172 100644
--- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
+++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll
@@ -911,27 +911,6 @@ private predicate modelFlow(Operand opFrom, Instruction iTo) {
)
)
)
- or
- impliedModelFlow(opFrom, iTo)
-}
-
-/**
- * When a `DataFlowFunction` specifies dataflow from a parameter `p` to the return value there should
- * also be dataflow from the parameter dereference (i.e., `*p`) to the return value dereference.
- */
-private predicate impliedModelFlow(Operand opFrom, Instruction iTo) {
- exists(
- CallInstruction call, DataFlowFunction func, FunctionInput modelIn, FunctionOutput modelOut,
- int index
- |
- call.getStaticCallTarget() = func and
- func.hasDataFlow(modelIn, modelOut)
- |
- modelIn.isParameterOrQualifierAddress(index) and
- modelOut.isReturnValue() and
- opFrom = getSideEffectFor(call, index).(ReadSideEffectInstruction).getSideEffectOperand() and
- iTo = call // TODO: Add write side effects for return values
- )
}
/**
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll
index 0178cd0f7ec..bd188cffe49 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/IdentityFunction.qll
@@ -32,5 +32,7 @@ private class IdentityFunction extends DataFlowFunction, SideEffectFunction, Ali
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// These functions simply return the argument value.
input.isParameter(0) and output.isReturnValue()
+ or
+ input.isParameterDeref(0) and output.isReturnValueDeref()
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll
index 3a789a6aa25..a5b1c0e83ec 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Iterator.qll
@@ -109,6 +109,8 @@ private class IteratorCrementOperator extends Operator, DataFlowFunction {
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input = iteratorInput and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and output.isReturnValueDeref()
}
}
@@ -159,6 +161,8 @@ private class IteratorAssignArithmeticOperator extends Operator, DataFlowFunctio
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -201,6 +205,9 @@ private class IteratorCrementMemberOperator extends MemberFunction, DataFlowFunc
or
input.isReturnValueDeref() and
output.isQualifierObject()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll
index 8fd2f79cfdd..9f4c458815f 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/StdString.qll
@@ -293,6 +293,9 @@ private class StdIStreamIn extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -319,6 +322,9 @@ private class StdIStreamInNonMember extends DataFlowFunction, TaintFunction {
// flow from first parameter to return value
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -361,6 +367,9 @@ private class StdIStreamRead extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -397,6 +406,9 @@ private class StdIStreamPutBack extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -430,6 +442,9 @@ private class StdIStreamGetLine extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -453,6 +468,9 @@ private class StdGetLine extends DataFlowFunction, TaintFunction {
// flow from first parameter to return value
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -486,6 +504,9 @@ private class StdOStreamOut extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -522,6 +543,9 @@ private class StdOStreamOutNonMember extends DataFlowFunction, TaintFunction {
// flow from first parameter to return value
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
@@ -605,6 +629,9 @@ private class StdStreamFunction extends DataFlowFunction, TaintFunction {
// returns reference to `*this`
input.isQualifierAddress() and
output.isReturnValue()
+ or
+ input.isQualifierObject() and
+ output.isReturnValueDeref()
}
override predicate hasTaintFlow(FunctionInput input, FunctionOutput output) {
diff --git a/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll b/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll
index f4a80cbabac..bf6430e548b 100644
--- a/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll
+++ b/cpp/ql/src/semmle/code/cpp/models/implementations/Strset.qll
@@ -40,6 +40,9 @@ private class StrsetFunction extends ArrayFunction, DataFlowFunction, AliasFunct
// flow from the input string to the output string
input.isParameter(0) and
output.isReturnValue()
+ or
+ input.isParameterDeref(0) and
+ output.isReturnValueDeref()
}
override predicate parameterNeverEscapes(int index) { none() }
From 3fafb47b1680fc7fb42418a1ca6816986262ab05 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 16:41:22 +0100
Subject: [PATCH 089/429] Python: Fix global flow
A slightly odd fix, but still morally okay, I think. The main issue
here was that global variables have their first occurrence in an inner
scope inside a so-called "scope entry definition", that then
subsequently flows to the first use of this variable. This meant that
that first use was _not_ a `LocalSourceNode` (since _something_ flowed
into it), and this blocked `trackUseNode` from type-tracking to it (as
it expects all nodes to be `LocalSourceNode`s).
The answer, then, is to say that a `LocalSourceNode` is simply one
that doesn't have flow to it from _any `CfgNode`_ (through one or more
steps). This disregards the flow from the scope entry definition, as
that is flow from an `EssaNode`.
Additionally, it makes sense to exclude `ModuleVariableNode`s. These
should never be considered local sources, since they always have flow
from (at least) the place where the corresponding global variable is
introduced.
---
.../semmle/python/dataflow/new/internal/DataFlowPublic.qll | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
index f2f90723d77..1cf756069f0 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
@@ -443,7 +443,10 @@ class BarrierGuard extends GuardNode {
* - Function parameters
*/
class LocalSourceNode extends Node {
- LocalSourceNode() { not simpleLocalFlowStep(_, this) }
+ LocalSourceNode() {
+ not simpleLocalFlowStep+(any(CfgNode n), this) and
+ not this instanceof ModuleVariableNode
+ }
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
cached
From 93f91d8746182f425b5472f7c84f39dc72800591 Mon Sep 17 00:00:00 2001
From: Rasmus Wriedt Larsen
Date: Wed, 3 Feb 2021 17:44:04 +0100
Subject: [PATCH 090/429] Python: Apply suggestions from code review
Co-authored-by: intrigus-lgtm <60750685+intrigus-lgtm@users.noreply.github.com>
---
python/ql/src/semmle/python/frameworks/Tornado.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/python/ql/src/semmle/python/frameworks/Tornado.qll b/python/ql/src/semmle/python/frameworks/Tornado.qll
index 1f5859c4945..4351188d8bb 100644
--- a/python/ql/src/semmle/python/frameworks/Tornado.qll
+++ b/python/ql/src/semmle/python/frameworks/Tornado.qll
@@ -567,7 +567,7 @@ private module Tornado {
// Response modeling
// ---------------------------------------------------------------------------
/**
- * A call to `tornado.web.RequestHandler.redirect` method.
+ * A call to the `tornado.web.RequestHandler.redirect` method.
*
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.redirect
*/
@@ -591,7 +591,7 @@ private module Tornado {
}
/**
- * A call to `tornado.web.RequestHandler.write` method.
+ * A call to the `tornado.web.RequestHandler.write` method.
*
* See https://www.tornadoweb.org/en/stable/web.html?highlight=write#tornado.web.RequestHandler.write
*/
From 724c3e00e02be120f2a48630c2bc6ab6dfa9fd32 Mon Sep 17 00:00:00 2001
From: luchua-bc
Date: Wed, 3 Feb 2021 16:45:15 +0000
Subject: [PATCH 091/429] Update help file
---
.../experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp | 5 -----
1 file changed, 5 deletions(-)
diff --git a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
index ee4afabbed6..c729759a06e 100644
--- a/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
+++ b/java/ql/src/experimental/Security/CWE/CWE-522/InsecureLdapAuth.qhelp
@@ -15,11 +15,6 @@
-
Oracle:
LDAP and LDAPS URLs
From 6ce160c51c9276704d0b414119737f4a5bce830e Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 19:49:53 +0100
Subject: [PATCH 092/429] Python: Use call instead of invocation
---
python/ql/src/semmle/python/ApiGraphs.qll | 2 +-
.../python/dataflow/new/internal/DataFlowPublic.qll | 12 ++++++------
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 2c1ff3b6cf7..e8072a91540 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -292,7 +292,7 @@ module API {
or
// Calling a node that is a use of `base`
lbl = Label::return() and
- ref = pred.getAnInvocation()
+ ref = pred.getACall()
)
}
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
index 1cf756069f0..b2e2298182e 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll
@@ -477,9 +477,9 @@ class LocalSourceNode extends Node {
AttrRead getAnAttributeRead() { result = getAnAttributeReference() }
/**
- * Gets an invocation (with our without `new`) of this node.
+ * Gets a call to this node.
*/
- Node getAnInvocation() { Cached::invocation(this, result) }
+ Node getACall() { Cached::call(this, result) }
}
cached
@@ -521,12 +521,12 @@ private module Cached {
}
/**
- * Holds if `func` flows to the callee of `invoke`.
+ * Holds if `func` flows to the callee of `call`.
*/
cached
- predicate invocation(LocalSourceNode func, Node invoke) {
- exists(CfgNode n, CallNode call |
- invoke.asCfgNode() = call and n.asCfgNode() = call.getFunction()
+ predicate call(LocalSourceNode func, Node call) {
+ exists(CfgNode n, CallNode call_node |
+ call.asCfgNode() = call_node and n.asCfgNode() = call_node.getFunction()
|
hasLocalSource(n, func)
)
From c5d6792c1eb727cd5fd1f8a6e84fd296c2e0e5bd Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 19:50:21 +0100
Subject: [PATCH 093/429] Python: Make `toString` abstract
---
python/ql/src/semmle/python/ApiGraphs.qll | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index e8072a91540..d4091a7a2bf 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -146,9 +146,7 @@ module API {
/**
* Gets a textual representation of this node.
*/
- string toString() {
- none() // defined in subclasses
- }
+ abstract string toString();
/**
* Gets a path of the given `length` from the root to this node.
From 05f290f7347c8c213461c66a999dad26c941a5b6 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 19:51:34 +0100
Subject: [PATCH 094/429] Python: Better explanation in `use/3`
---
python/ql/src/semmle/python/ApiGraphs.qll | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index d4091a7a2bf..04eeb785c5b 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -282,6 +282,13 @@ module API {
cached
predicate use(TApiNode base, string lbl, DataFlow::Node ref) {
exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode pred |
+ // First, we find a predecessor of the node `ref` that we want to determine. The predecessor
+ // is any node that is a type-tracked use of a data flow node (`src`), which is itself a
+ // reference to the API node `base`.
+ //
+ // Once we have identified the predecessor, we define its relation to the successor `ref` as
+ // well as the label on the edge from `pred` to `ref`. This label describes the nature of
+ // the relationship between `pred` and `ref`.
use(base, src) and pred = trackUseNode(src)
|
// Reading an attribute on a node that is a use of `base`:
From 56515c570879f89baeed251c091cc8b528ade9fd Mon Sep 17 00:00:00 2001
From: Taus
Date: Wed, 3 Feb 2021 21:29:15 +0100
Subject: [PATCH 095/429] Python: Improve documentation for `moduleImport`
Co-authored-by: Rasmus Wriedt Larsen
---
python/ql/src/semmle/python/ApiGraphs.qll | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 04eeb785c5b..54ae9f86875 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -196,7 +196,13 @@ module API {
/** Gets the root node. */
Root root() { any() }
- /** Gets a node corresponding to an import of module `m`. */
+ /**
+ * Gets a node corresponding to an import of module `m`.
+ *
+ * Note: You should only use this predicate for top level modules. If you want nodes corresponding to a submodule,
+ * you should use `.getMember` on the parent module. For example, for nodes corresponding to the module `foo.bar`,
+ * use `moduleImport("foo").getMember("bar")`.
+ */
Node moduleImport(string m) { result = Impl::MkModuleImport(m) }
/**
From cccca879d959caf17d0ed26350e82385a0ba5294 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 3 Feb 2021 21:52:00 +0100
Subject: [PATCH 096/429] C#: Add initial DB scheme
---
.../initial/semmlecode.csharp.dbscheme | 1707 +++++++++++++++++
1 file changed, 1707 insertions(+)
create mode 100644 csharp/upgrades/initial/semmlecode.csharp.dbscheme
diff --git a/csharp/upgrades/initial/semmlecode.csharp.dbscheme b/csharp/upgrades/initial/semmlecode.csharp.dbscheme
new file mode 100644
index 00000000000..34565707dfb
--- /dev/null
+++ b/csharp/upgrades/initial/semmlecode.csharp.dbscheme
@@ -0,0 +1,1707 @@
+/*
+ * External artifacts
+ */
+
+externalDefects(
+ unique int id: @externalDefect,
+ varchar(900) queryPath: string ref,
+ int location: @location ref,
+ varchar(900) message: string ref,
+ float severity: float ref);
+
+externalMetrics(
+ unique int id: @externalMetric,
+ varchar(900) queryPath: string ref,
+ int location: @location ref,
+ float value: float ref);
+
+externalData(
+ int id: @externalDataElement,
+ varchar(900) path: string ref,
+ int column: int ref,
+ varchar(900) value: string ref);
+
+snapshotDate(
+ unique date snapshotDate: date ref);
+
+sourceLocationPrefix(
+ varchar(900) prefix: string ref);
+
+/*
+ * Duplicate code
+ */
+
+duplicateCode(
+ unique int id: @duplication,
+ varchar(900) relativePath: string ref,
+ int equivClass: int ref);
+
+similarCode(
+ unique int id: @similarity,
+ varchar(900) relativePath: string ref,
+ int equivClass: int ref);
+
+@duplication_or_similarity = @duplication | @similarity
+
+tokens(
+ int id: @duplication_or_similarity ref,
+ int offset: int ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref);
+
+/*
+ * Version history
+ */
+
+svnentries(
+ int id: @svnentry,
+ varchar(500) revision: string ref,
+ varchar(500) author: string ref,
+ date revisionDate: date ref,
+ int changeSize: int ref);
+
+svnaffectedfiles(
+ int id: @svnentry ref,
+ int file: @file ref,
+ varchar(500) action: string ref);
+
+svnentrymsg(
+ int id: @svnentry ref,
+ varchar(500) message: string ref
+)
+
+svnchurn(
+ int commit: @svnentry ref,
+ int file: @file ref,
+ int addedLines: int ref,
+ int deletedLines: int ref);
+
+/*
+ * C# dbscheme
+ */
+
+/** ELEMENTS **/
+
+@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration
+ | @using_directive | @type_parameter_constraints | @external_element
+ | @xmllocatable | @asp_element | @namespace;
+
+@declaration = @callable | @generic | @assignable;
+
+@named_element = @namespace | @declaration;
+
+@declaration_with_accessors = @property | @indexer | @event;
+
+@assignable = @variable | @assignable_with_accessors | @event;
+
+@assignable_with_accessors = @property | @indexer;
+
+@external_element = @externalMetric | @externalDefect | @externalDataElement;
+
+@attributable = @assembly | @field | @parameter | @operator | @method | @constructor
+ | @destructor | @callable_accessor | @value_or_ref_type | @declaration_with_accessors;
+
+/** LOCATIONS, ASEMMBLIES, MODULES, FILES and FOLDERS **/
+
+@location = @location_default | @assembly;
+
+locations_default(
+ unique int id: @location_default,
+ int file: @file ref,
+ int beginLine: int ref,
+ int beginColumn: int ref,
+ int endLine: int ref,
+ int endColumn: int ref);
+
+@sourceline = @file | @callable | @xmllocatable;
+
+numlines(
+ unique int element_id: @sourceline ref,
+ int num_lines: int ref,
+ int num_code: int ref,
+ int num_comment: int ref);
+
+assemblies(
+ unique int id: @assembly,
+ int file: @file ref,
+ varchar(900) fullname: string ref,
+ varchar(900) name: string ref,
+ varchar(900) version: string ref);
+
+/*
+ fromSource(0) = unknown,
+ fromSource(1) = from source,
+ fromSource(2) = from library
+*/
+files(
+ unique int id: @file,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref,
+ varchar(900) ext: string ref,
+ int fromSource: int ref);
+
+folders(
+ unique int id: @folder,
+ varchar(900) name: string ref,
+ varchar(900) simple: string ref);
+
+@container = @folder | @file ;
+
+containerparent(
+ int parent: @container ref,
+ unique int child: @container ref);
+
+file_extraction_mode(
+ unique int file: @file ref,
+ int mode: int ref
+ /* 0 = normal, 1 = standalone extractor */
+ );
+
+/** NAMESPACES **/
+
+@type_container = @namespace | @type;
+
+namespaces(
+ unique int id: @namespace,
+ varchar(900) name: string ref);
+
+namespace_declarations(
+ unique int id: @namespace_declaration,
+ int namespace_id: @namespace ref);
+
+namespace_declaration_location(
+ unique int id: @namespace_declaration ref,
+ int loc: @location ref);
+
+parent_namespace(
+ unique int child_id: @type_container ref,
+ int namespace_id: @namespace ref);
+
+@declaration_or_directive = @namespace_declaration | @type | @using_directive;
+
+parent_namespace_declaration(
+ int child_id: @declaration_or_directive ref, // cannot be unique because of partial classes
+ int namespace_id: @namespace_declaration ref);
+
+@using_directive = @using_namespace_directive | @using_static_directive;
+
+using_namespace_directives(
+ unique int id: @using_namespace_directive,
+ int namespace_id: @namespace ref);
+
+using_static_directives(
+ unique int id: @using_static_directive,
+ int type_id: @type_or_ref ref);
+
+using_directive_location(
+ unique int id: @using_directive ref,
+ int loc: @location ref);
+
+/** TYPES **/
+
+types(
+ unique int id: @type,
+ int kind: int ref,
+ varchar(900) name: string ref);
+
+case @type.kind of
+ 1 = @bool_type
+| 2 = @char_type
+| 3 = @decimal_type
+| 4 = @sbyte_type
+| 5 = @short_type
+| 6 = @int_type
+| 7 = @long_type
+| 8 = @byte_type
+| 9 = @ushort_type
+| 10 = @uint_type
+| 11 = @ulong_type
+| 12 = @float_type
+| 13 = @double_type
+| 14 = @enum_type
+| 15 = @struct_type
+| 17 = @class_type
+| 19 = @interface_type
+| 20 = @delegate_type
+| 21 = @null_type
+| 22 = @type_parameter
+| 23 = @pointer_type
+| 24 = @nullable_type
+| 25 = @array_type
+| 26 = @void_type
+| 27 = @int_ptr_type
+| 28 = @uint_ptr_type
+| 29 = @dynamic_type
+| 30 = @arglist_type
+| 31 = @unknown_type
+| 32 = @tuple_type
+ ;
+
+@simple_type = @bool_type | @char_type | @integral_type | @floating_point_type | @decimal_type;
+@integral_type = @signed_integral_type | @unsigned_integral_type;
+@signed_integral_type = @sbyte_type | @short_type | @int_type | @long_type;
+@unsigned_integral_type = @byte_type | @ushort_type | @uint_type | @ulong_type;
+@floating_point_type = @float_type | @double_type;
+@value_type = @simple_type | @enum_type | @struct_type | @nullable_type | @int_ptr_type
+ | @uint_ptr_type | @tuple_type;
+@ref_type = @class_type | @interface_type | @array_type | @delegate_type | @null_type
+ | @dynamic_type;
+@value_or_ref_type = @value_type | @ref_type;
+
+typerefs(
+ unique int id: @typeref,
+ varchar(900) name: string ref);
+
+typeref_type(
+ unique int id: @typeref ref,
+ unique int typeId: @type ref);
+
+@type_or_ref = @type | @typeref;
+
+array_element_type(
+ unique int array: @array_type ref,
+ int dimension: int ref,
+ int rank: int ref,
+ int element: @type_or_ref ref);
+
+nullable_underlying_type(
+ unique int nullable: @nullable_type ref,
+ int underlying: @type_or_ref ref);
+
+pointer_referent_type(
+ unique int pointer: @pointer_type ref,
+ int referent: @type_or_ref ref);
+
+enum_underlying_type(
+ unique int enum_id: @enum_type ref,
+ int underlying_type_id: @type_or_ref ref);
+
+delegate_return_type(
+ unique int delegate_id: @delegate_type ref,
+ int return_type_id: @type_or_ref ref);
+
+extend(
+ unique int sub: @type ref,
+ int super: @type_or_ref ref);
+
+@interface_or_ref = @interface_type | @typeref;
+
+implement(
+ int sub: @type ref,
+ int super: @type_or_ref ref);
+
+type_location(
+ int id: @type ref,
+ int loc: @location ref);
+
+tuple_underlying_type(
+ unique int tuple: @tuple_type ref,
+ int struct: @struct_type ref);
+
+#keyset[tuple, index]
+tuple_element(
+ int tuple: @tuple_type ref,
+ int index: int ref,
+ unique int field: @field ref);
+
+attributes(
+ unique int id: @attribute,
+ int type_id: @type_or_ref ref,
+ int target: @attributable ref);
+
+attribute_location(
+ int id: @attribute ref,
+ int loc: @location ref);
+
+@type_mention_parent = @element | @type_mention;
+
+type_mention(
+ unique int id: @type_mention,
+ int type_id: @type_or_ref ref,
+ int parent: @type_mention_parent ref);
+
+type_mention_location(
+ unique int id: @type_mention ref,
+ int loc: @location ref);
+
+/** GENERICS **/
+
+@generic = @type | @method | @local_function;
+
+is_generic(unique int id: @generic ref);
+
+is_constructed(unique int id: @generic ref);
+
+type_parameters(
+ unique int id: @type_parameter ref,
+ int index: int ref,
+ int generic_id: @generic ref,
+ int variance: int ref /* none = 0, out = 1, in = 2 */);
+
+#keyset[constructed_id, index]
+type_arguments(
+ int id: @type_or_ref ref,
+ int index: int ref,
+ int constructed_id: @generic_or_ref ref);
+
+@generic_or_ref = @generic | @typeref;
+
+constructed_generic(
+ unique int constructed: @generic ref,
+ int generic: @generic_or_ref ref);
+
+type_parameter_constraints(
+ unique int id: @type_parameter_constraints,
+ int param_id: @type_parameter ref);
+
+type_parameter_constraints_location(
+ int id: @type_parameter_constraints ref,
+ int loc: @location ref);
+
+general_type_parameter_constraints(
+ int id: @type_parameter_constraints ref,
+ int kind: int ref /* class = 1, struct = 2, new = 3 */);
+
+specific_type_parameter_constraints(
+ int id: @type_parameter_constraints ref,
+ int base_id: @type_or_ref ref);
+
+
+/** MODIFIERS */
+
+@modifiable = @modifiable_direct | @event_accessor;
+
+@modifiable_direct = @member | @accessor;
+
+modifiers(
+ unique int id: @modifier,
+ varchar(900) name: string ref);
+
+has_modifiers(
+ int id: @modifiable_direct ref,
+ int mod_id: @modifier ref);
+
+compiler_generated(unique int id: @modifiable_direct ref);
+
+/** MEMBERS **/
+
+@member = @method | @constructor | @destructor | @field | @property | @event | @operator | @indexer | @type;
+
+@named_exprorstmt = @goto_stmt | @labeled_stmt | @literal_expr;
+
+@virtualizable = @method | @property | @indexer | @event;
+
+exprorstmt_name(
+ unique int parent_id: @named_exprorstmt ref,
+ varchar(900) name: string ref);
+
+nested_types(
+ unique int id: @type ref,
+ int declaring_type_id: @type ref,
+ int unbound_id: @type ref);
+
+properties(
+ unique int id: @property,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int type_id: @type_or_ref ref,
+ int unbound_id: @property ref);
+
+property_location(
+ int id: @property ref,
+ int loc: @location ref);
+
+indexers(
+ unique int id: @indexer,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int type_id: @type_or_ref ref,
+ int unbound_id: @indexer ref);
+
+indexer_location(
+ int id: @indexer ref,
+ int loc: @location ref);
+
+accessors(
+ unique int id: @accessor,
+ int kind: int ref,
+ varchar(900) name: string ref,
+ int declaring_member_id: @member ref,
+ int unbound_id: @accessor ref);
+
+case @accessor.kind of
+ 1 = @getter
+| 2 = @setter
+ ;
+
+accessor_location(
+ int id: @accessor ref,
+ int loc: @location ref);
+
+events(
+ unique int id: @event,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int type_id: @type_or_ref ref,
+ int unbound_id: @event ref);
+
+event_location(
+ int id: @event ref,
+ int loc: @location ref);
+
+event_accessors(
+ unique int id: @event_accessor,
+ int kind: int ref,
+ varchar(900) name: string ref,
+ int declaring_event_id: @event ref,
+ int unbound_id: @event_accessor ref);
+
+case @event_accessor.kind of
+ 1 = @add_event_accessor
+| 2 = @remove_event_accessor
+ ;
+
+event_accessor_location(
+ int id: @event_accessor ref,
+ int loc: @location ref);
+
+operators(
+ unique int id: @operator,
+ varchar(900) name: string ref,
+ varchar(900) symbol: string ref,
+ int declaring_type_id: @type ref,
+ int type_id: @type_or_ref ref,
+ int unbound_id: @operator ref);
+
+operator_location(
+ int id: @operator ref,
+ int loc: @location ref);
+
+constant_value(
+ unique int id: @variable ref,
+ varchar(900) value: string ref);
+
+/** CALLABLES **/
+
+@callable = @method | @constructor | @destructor | @operator | @callable_accessor | @anonymous_function_expr | @local_function;
+
+@callable_accessor = @accessor | @event_accessor;
+
+methods(
+ unique int id: @method,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int type_id: @type_or_ref ref,
+ int unbound_id: @method ref);
+
+method_location(
+ int id: @method ref,
+ int loc: @location ref);
+
+constructors(
+ unique int id: @constructor,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int unbound_id: @constructor ref);
+
+constructor_location(
+ int id: @constructor ref,
+ int loc: @location ref);
+
+destructors(
+ unique int id: @destructor,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int unbound_id: @destructor ref);
+
+destructor_location(
+ int id: @destructor ref,
+ int loc: @location ref);
+
+overrides(
+ unique int id: @callable ref,
+ int base_id: @callable ref);
+
+explicitly_implements(
+ unique int id: @member ref,
+ int interface_id: @interface_or_ref ref);
+
+local_functions(
+ unique int id: @local_function,
+ varchar(900) name: string ref,
+ int return_type: @type ref,
+ int unbound_id: @local_function ref);
+
+local_function_stmts(
+ unique int fn: @local_function_stmt ref,
+ int stmt: @local_function ref);
+
+@ref_callable = @local_function | @method | @delegate_type;
+
+ref_returns(int fn: @ref_callable ref);
+
+ref_readonly_returns(int fn: @ref_callable ref);
+
+/** VARIABLES **/
+
+@variable = @local_scope_variable | @field;
+
+@local_scope_variable = @local_variable | @parameter;
+
+fields(
+ unique int id: @field,
+ int kind: int ref,
+ varchar(900) name: string ref,
+ int declaring_type_id: @type ref,
+ int type_id: @type_or_ref ref,
+ int unbound_id: @field ref);
+
+case @field.kind of
+ 1 = @addressable_field
+| 2 = @constant
+ ;
+
+field_location(
+ int id: @field ref,
+ int loc: @location ref);
+
+localvars(
+ unique int id: @local_variable,
+ int kind: int ref,
+ varchar(900) name: string ref,
+ int implicitly_typed: int ref /* 0 = no, 1 = yes */,
+ int type_id: @type_or_ref ref,
+ int parent_id: @local_var_decl_expr ref);
+
+case @local_variable.kind of
+ 1 = @addressable_local_variable
+| 2 = @local_constant
+| 3 = @local_variable_ref
+ ;
+
+localvar_location(
+ unique int id: @local_variable ref,
+ int loc: @location ref);
+
+@parameterizable = @callable | @delegate_type | @indexer;
+
+#keyset[name, parent_id]
+#keyset[index, parent_id]
+params(
+ unique int id: @parameter,
+ varchar(900) name: string ref,
+ int type_id: @type_or_ref ref,
+ int index: int ref,
+ int mode: int ref, /* value = 0, ref = 1, out = 2, array = 3, this = 4 */
+ int parent_id: @parameterizable ref,
+ int unbound_id: @parameter ref);
+
+param_location(
+ int id: @parameter ref,
+ int loc: @location ref);
+
+/** STATEMENTS **/
+
+@exprorstmt_parent = @control_flow_element | @top_level_exprorstmt_parent;
+
+statements(
+ unique int id: @stmt,
+ int kind: int ref);
+
+#keyset[index, parent]
+stmt_parent(
+ unique int stmt: @stmt ref,
+ int index: int ref,
+ int parent: @control_flow_element ref);
+
+@top_level_stmt_parent = @callable;
+
+// [index, parent] is not a keyset because the same parent may be compiled multiple times
+stmt_parent_top_level(
+ unique int stmt: @stmt ref,
+ int index: int ref,
+ int parent: @top_level_stmt_parent ref);
+
+case @stmt.kind of
+ 1 = @block_stmt
+| 2 = @expr_stmt
+| 3 = @if_stmt
+| 4 = @switch_stmt
+| 5 = @while_stmt
+| 6 = @do_stmt
+| 7 = @for_stmt
+| 8 = @foreach_stmt
+| 9 = @break_stmt
+| 10 = @continue_stmt
+| 11 = @goto_stmt
+| 12 = @goto_case_stmt
+| 13 = @goto_default_stmt
+| 14 = @throw_stmt
+| 15 = @return_stmt
+| 16 = @yield_stmt
+| 17 = @try_stmt
+| 18 = @checked_stmt
+| 19 = @unchecked_stmt
+| 20 = @lock_stmt
+| 21 = @using_stmt
+| 22 = @var_decl_stmt
+| 23 = @const_decl_stmt
+| 24 = @empty_stmt
+| 25 = @unsafe_stmt
+| 26 = @fixed_stmt
+| 27 = @label_stmt
+| 28 = @catch
+| 29 = @case
+| 30 = @local_function_stmt
+ ;
+
+@labeled_stmt = @label_stmt | @case;
+
+@decl_stmt = @var_decl_stmt | @const_decl_stmt;
+
+@cond_stmt = @if_stmt | @switch_stmt;
+
+@loop_stmt = @while_stmt | @do_stmt | @for_stmt | @foreach_stmt;
+
+@jump_stmt = @break_stmt | @goto_any_stmt | @continue_stmt | @throw_stmt | @return_stmt
+ | @yield_stmt;
+
+@goto_any_stmt = @goto_default_stmt | @goto_case_stmt | @goto_stmt;
+
+
+stmt_location(
+ unique int id: @stmt ref,
+ int loc: @location ref);
+
+catch_type(
+ unique int catch_id: @catch ref,
+ int type_id: @type_or_ref ref,
+ int kind: int ref /* explicit = 1, implicit = 2 */);
+
+/** EXPRESSIONS **/
+
+expressions(
+ unique int id: @expr,
+ int kind: int ref,
+ int type_id: @type_or_ref ref);
+
+#keyset[index, parent]
+expr_parent(
+ unique int expr: @expr ref,
+ int index: int ref,
+ int parent: @control_flow_element ref);
+
+@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter;
+
+@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent;
+
+// [index, parent] is not a keyset because the same parent may be compiled multiple times
+expr_parent_top_level(
+ unique int expr: @expr ref,
+ int index: int ref,
+ int parent: @top_level_exprorstmt_parent ref);
+
+case @expr.kind of
+/* literal */
+ 1 = @bool_literal_expr
+| 2 = @char_literal_expr
+| 3 = @decimal_literal_expr
+| 4 = @int_literal_expr
+| 5 = @long_literal_expr
+| 6 = @uint_literal_expr
+| 7 = @ulong_literal_expr
+| 8 = @float_literal_expr
+| 9 = @double_literal_expr
+| 10 = @string_literal_expr
+| 11 = @null_literal_expr
+/* primary & unary */
+| 12 = @this_access_expr
+| 13 = @base_access_expr
+| 14 = @local_variable_access_expr
+| 15 = @parameter_access_expr
+| 16 = @field_access_expr
+| 17 = @property_access_expr
+| 18 = @method_access_expr
+| 19 = @event_access_expr
+| 20 = @indexer_access_expr
+| 21 = @array_access_expr
+| 22 = @type_access_expr
+| 23 = @typeof_expr
+| 24 = @method_invocation_expr
+| 25 = @delegate_invocation_expr
+| 26 = @operator_invocation_expr
+| 27 = @cast_expr
+| 28 = @object_creation_expr
+| 29 = @explicit_delegate_creation_expr
+| 30 = @implicit_delegate_creation_expr
+| 31 = @array_creation_expr
+| 32 = @default_expr
+| 33 = @plus_expr
+| 34 = @minus_expr
+| 35 = @bit_not_expr
+| 36 = @log_not_expr
+| 37 = @post_incr_expr
+| 38 = @post_decr_expr
+| 39 = @pre_incr_expr
+| 40 = @pre_decr_expr
+/* multiplicative */
+| 41 = @mul_expr
+| 42 = @div_expr
+| 43 = @rem_expr
+/* additive */
+| 44 = @add_expr
+| 45 = @sub_expr
+/* shift */
+| 46 = @lshift_expr
+| 47 = @rshift_expr
+/* relational */
+| 48 = @lt_expr
+| 49 = @gt_expr
+| 50 = @le_expr
+| 51 = @ge_expr
+/* equality */
+| 52 = @eq_expr
+| 53 = @ne_expr
+/* logical */
+| 54 = @bit_and_expr
+| 55 = @bit_xor_expr
+| 56 = @bit_or_expr
+| 57 = @log_and_expr
+| 58 = @log_or_expr
+/* type testing */
+| 59 = @is_expr
+| 60 = @as_expr
+/* null coalescing */
+| 61 = @null_coalescing_expr
+/* conditional */
+| 62 = @conditional_expr
+/* assignment */
+| 63 = @simple_assign_expr
+| 64 = @assign_add_expr
+| 65 = @assign_sub_expr
+| 66 = @assign_mul_expr
+| 67 = @assign_div_expr
+| 68 = @assign_rem_expr
+| 69 = @assign_and_expr
+| 70 = @assign_xor_expr
+| 71 = @assign_or_expr
+| 72 = @assign_lshift_expr
+| 73 = @assign_rshift_expr
+/* more */
+| 74 = @object_init_expr
+| 75 = @collection_init_expr
+| 76 = @array_init_expr
+| 77 = @checked_expr
+| 78 = @unchecked_expr
+| 79 = @constructor_init_expr
+| 80 = @add_event_expr
+| 81 = @remove_event_expr
+| 82 = @par_expr
+| 83 = @local_var_decl_expr
+| 84 = @lambda_expr
+| 85 = @anonymous_method_expr
+| 86 = @namespace_expr
+/* dynamic */
+| 92 = @dynamic_element_access_expr
+| 93 = @dynamic_member_access_expr
+/* unsafe */
+| 100 = @pointer_indirection_expr
+| 101 = @address_of_expr
+| 102 = @sizeof_expr
+/* async */
+| 103 = @await_expr
+/* C# 6.0 */
+| 104 = @nameof_expr
+| 105 = @interpolated_string_expr
+| 106 = @unknown_expr
+/* C# 7.0 */
+| 107 = @throw_expr
+| 108 = @tuple_expr
+| 109 = @local_function_invocation_expr
+| 110 = @ref_expr
+| 111 = @discard_expr
+;
+
+@integer_literal_expr = @int_literal_expr | @long_literal_expr | @uint_literal_expr | @ulong_literal_expr;
+@real_literal_expr = @float_literal_expr | @double_literal_expr | @decimal_literal_expr;
+@literal_expr = @bool_literal_expr | @char_literal_expr | @integer_literal_expr | @real_literal_expr
+ | @string_literal_expr | @null_literal_expr;
+
+@assign_expr = @simple_assign_expr | @assign_op_expr | @local_var_decl_expr;
+@assign_op_expr = @assign_arith_expr | @assign_bitwise_expr | @assign_event_expr;
+@assign_event_expr = @add_event_expr | @remove_event_expr;
+
+@assign_arith_expr = @assign_add_expr | @assign_sub_expr | @assign_mul_expr | @assign_div_expr
+ | @assign_rem_expr
+@assign_bitwise_expr = @assign_and_expr | @assign_or_expr | @assign_xor_expr
+ | @assign_lshift_expr | @assign_rshift_expr;
+
+@member_access_expr = @field_access_expr | @property_access_expr | @indexer_access_expr | @event_access_expr
+ | @method_access_expr | @type_access_expr | @dynamic_member_access_expr;
+@access_expr = @member_access_expr | @this_access_expr | @base_access_expr | @assignable_access_expr;
+@element_access_expr = @indexer_access_expr | @array_access_expr | @dynamic_element_access_expr;
+
+@local_variable_access = @local_variable_access_expr | @local_var_decl_expr;
+@local_scope_variable_access_expr = @parameter_access_expr | @local_variable_access;
+@variable_access_expr = @local_scope_variable_access_expr | @field_access_expr;
+
+@assignable_access_expr = @variable_access_expr | @property_access_expr | @element_access_expr
+ | @event_access_expr | @dynamic_member_access_expr;
+
+@objectorcollection_init_expr = @object_init_expr | @collection_init_expr;
+
+@delegate_creation_expr = @explicit_delegate_creation_expr | @implicit_delegate_creation_expr;
+
+@bin_arith_op_expr = @mul_expr | @div_expr | @rem_expr | @add_expr | @sub_expr;
+@incr_op_expr = @pre_incr_expr | @post_incr_expr;
+@decr_op_expr = @pre_decr_expr | @post_decr_expr;
+@mut_op_expr = @incr_op_expr | @decr_op_expr;
+@un_arith_op_expr = @plus_expr | @minus_expr | @mut_op_expr;
+@arith_op_expr = @bin_arith_op_expr | @un_arith_op_expr;
+
+@ternary_log_op_expr = @conditional_expr;
+@bin_log_op_expr = @log_and_expr | @log_or_expr | @null_coalescing_expr;
+@un_log_op_expr = @log_not_expr;
+@log_expr = @un_log_op_expr | @bin_log_op_expr | @ternary_log_op_expr;
+
+@bin_bit_op_expr = @bit_and_expr | @bit_or_expr | @bit_xor_expr | @lshift_expr
+ | @rshift_expr;
+@un_bit_op_expr = @bit_not_expr;
+@bit_expr = @un_bit_op_expr | @bin_bit_op_expr;
+
+@equality_op_expr = @eq_expr | @ne_expr;
+@rel_op_expr = @gt_expr | @lt_expr| @ge_expr | @le_expr;
+@comp_expr = @equality_op_expr | @rel_op_expr;
+
+@op_expr = @assign_expr | @un_op | @bin_op | @ternary_op;
+
+@ternary_op = @ternary_log_op_expr;
+@bin_op = @bin_arith_op_expr | @bin_log_op_expr | @bin_bit_op_expr | @comp_expr;
+@un_op = @un_arith_op_expr | @un_log_op_expr | @un_bit_op_expr | @sizeof_expr
+ | @pointer_indirection_expr | @address_of_expr;
+
+@anonymous_function_expr = @lambda_expr | @anonymous_method_expr;
+
+@call = @method_invocation_expr | @constructor_init_expr | @operator_invocation_expr
+ | @delegate_invocation_expr | @object_creation_expr | @call_access_expr
+ | @local_function_invocation_expr;
+
+@call_access_expr = @property_access_expr | @event_access_expr | @indexer_access_expr;
+
+@late_bindable_expr = @dynamic_element_access_expr | @dynamic_member_access_expr
+ | @object_creation_expr | @method_invocation_expr | @operator_invocation_expr;
+
+@throw_element = @throw_expr | @throw_stmt;
+
+implicitly_typed_array_creation(
+ unique int id: @array_creation_expr ref);
+
+explicitly_sized_array_creation(
+ unique int id: @array_creation_expr ref);
+
+mutator_invocation_mode(
+ unique int id: @operator_invocation_expr ref,
+ int mode: int ref /* prefix = 1, postfix = 2*/);
+
+expr_compiler_generated(
+ unique int id: @expr ref);
+
+expr_value(
+ unique int id: @expr ref,
+ varchar(900) value: string ref);
+
+expr_call(
+ unique int caller_id: @expr ref,
+ int target_id: @callable ref);
+
+expr_access(
+ unique int accesser_id: @access_expr ref,
+ int target_id: @accessible ref);
+
+@accessible = @method | @assignable | @local_function;
+
+expr_location(
+ unique int id: @expr ref,
+ int loc: @location ref);
+
+dynamic_member_name(
+ unique int id: @late_bindable_expr ref,
+ varchar(900) name: string ref);
+
+@qualifiable_expr = @member_access_expr
+ | @method_invocation_expr
+ | @element_access_expr;
+
+conditional_access(
+ unique int id: @qualifiable_expr ref);
+
+expr_argument(
+ unique int id: @expr ref,
+ int mode: int ref);
+ /* mode is the same as params: value = 0, ref = 1, out = 2 */
+
+expr_argument_name(
+ unique int id: @expr ref,
+ varchar(900) name: string ref);
+
+/** CONTROL/DATA FLOW **/
+
+@control_flow_element = @stmt | @expr;
+
+/* XML Files */
+
+xmlEncoding (
+ unique int id: @file ref,
+ varchar(900) encoding: string ref);
+
+xmlDTDs(
+ unique int id: @xmldtd,
+ varchar(900) root: string ref,
+ varchar(900) publicId: string ref,
+ varchar(900) systemId: string ref,
+ int fileid: @file ref);
+
+xmlElements(
+ unique int id: @xmlelement,
+ varchar(900) name: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int fileid: @file ref);
+
+xmlAttrs(
+ unique int id: @xmlattribute,
+ int elementid: @xmlelement ref,
+ varchar(900) name: string ref,
+ varchar(3600) value: string ref,
+ int idx: int ref,
+ int fileid: @file ref);
+
+xmlNs(
+ int id: @xmlnamespace,
+ varchar(900) prefixName: string ref,
+ varchar(900) URI: string ref,
+ int fileid: @file ref);
+
+xmlHasNs(
+ int elementId: @xmlnamespaceable ref,
+ int nsId: @xmlnamespace ref,
+ int fileid: @file ref);
+
+xmlComments(
+ unique int id: @xmlcomment,
+ varchar(3600) text: string ref,
+ int parentid: @xmlparent ref,
+ int fileid: @file ref);
+
+xmlChars(
+ unique int id: @xmlcharacters,
+ varchar(3600) text: string ref,
+ int parentid: @xmlparent ref,
+ int idx: int ref,
+ int isCDATA: int ref,
+ int fileid: @file ref);
+
+@xmlparent = @file | @xmlelement;
+@xmlnamespaceable = @xmlelement | @xmlattribute;
+
+xmllocations(
+ int xmlElement: @xmllocatable ref,
+ int location: @location_default ref);
+
+@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace;
+
+/* Comments */
+
+commentline(
+ unique int id: @commentline,
+ int kind: int ref,
+ varchar(800) text: string ref,
+ varchar(800) rawtext: string ref);
+
+case @commentline.kind of
+ 0 = @singlelinecomment
+| 1 = @xmldoccomment
+| 2 = @multilinecomment;
+
+commentline_location(
+ unique int id: @commentline ref,
+ int loc: @location ref);
+
+commentblock(
+ unique int id : @commentblock);
+
+commentblock_location(
+ unique int id: @commentblock ref,
+ int loc: @location ref);
+
+commentblock_binding(
+ int id: @commentblock ref,
+ int entity: @element ref,
+ int bindtype: int ref); /* 0: Parent, 1: Best, 2: Before, 3: After */
+
+commentblock_child(
+ int id: @commentblock ref,
+ int commentline: @commentline ref,
+ int index: int ref);
+
+/* ASP.NET */
+
+case @asp_element.kind of
+ 0=@asp_close_tag
+| 1=@asp_code
+| 2=@asp_comment
+| 3=@asp_data_binding
+| 4=@asp_directive
+| 5=@asp_open_tag
+| 6=@asp_quoted_string
+| 7=@asp_text
+| 8=@asp_xml_directive;
+
+@asp_attribute = @asp_code | @asp_data_binding | @asp_quoted_string;
+
+asp_elements(
+ unique int id: @asp_element,
+ int kind: int ref,
+ int loc: @location ref);
+
+asp_comment_server(unique int comment: @asp_comment ref);
+asp_code_inline(unique int code: @asp_code ref);
+asp_directive_attribute(
+ int directive: @asp_directive ref,
+ int index: int ref,
+ varchar(1000) name: string ref,
+ int value: @asp_quoted_string ref);
+asp_directive_name(
+ unique int directive: @asp_directive ref,
+ varchar(1000) name: string ref);
+asp_element_body(
+ unique int element: @asp_element ref,
+ varchar(1000) body: string ref);
+asp_tag_attribute(
+ int tag: @asp_open_tag ref,
+ int index: int ref,
+ varchar(1000) name: string ref,
+ int attribute: @asp_attribute ref);
+asp_tag_name(
+ unique int tag: @asp_open_tag ref,
+ varchar(1000) name: string ref);
+asp_tag_isempty(int tag: @asp_open_tag ref);
+
+/* Common Intermediate Language - CIL */
+
+case @cil_instruction.opcode of
+ 0 = @cil_nop
+| 1 = @cil_break
+| 2 = @cil_ldarg_0
+| 3 = @cil_ldarg_1
+| 4 = @cil_ldarg_2
+| 5 = @cil_ldarg_3
+| 6 = @cil_ldloc_0
+| 7 = @cil_ldloc_1
+| 8 = @cil_ldloc_2
+| 9 = @cil_ldloc_3
+| 10 = @cil_stloc_0
+| 11 = @cil_stloc_1
+| 12 = @cil_stloc_2
+| 13 = @cil_stloc_3
+| 14 = @cil_ldarg_s
+| 15 = @cil_ldarga_s
+| 16 = @cil_starg_s
+| 17 = @cil_ldloc_s
+| 18 = @cil_ldloca_s
+| 19 = @cil_stloc_s
+| 20 = @cil_ldnull
+| 21 = @cil_ldc_i4_m1
+| 22 = @cil_ldc_i4_0
+| 23 = @cil_ldc_i4_1
+| 24 = @cil_ldc_i4_2
+| 25 = @cil_ldc_i4_3
+| 26 = @cil_ldc_i4_4
+| 27 = @cil_ldc_i4_5
+| 28 = @cil_ldc_i4_6
+| 29 = @cil_ldc_i4_7
+| 30 = @cil_ldc_i4_8
+| 31 = @cil_ldc_i4_s
+| 32 = @cil_ldc_i4
+| 33 = @cil_ldc_i8
+| 34 = @cil_ldc_r4
+| 35 = @cil_ldc_r8
+| 37 = @cil_dup
+| 38 = @cil_pop
+| 39 = @cil_jmp
+| 40 = @cil_call
+| 41 = @cil_calli
+| 42 = @cil_ret
+| 43 = @cil_br_s
+| 44 = @cil_brfalse_s
+| 45 = @cil_brtrue_s
+| 46 = @cil_beq_s
+| 47 = @cil_bge_s
+| 48 = @cil_bgt_s
+| 49 = @cil_ble_s
+| 50 = @cil_blt_s
+| 51 = @cil_bne_un_s
+| 52 = @cil_bge_un_s
+| 53 = @cil_bgt_un_s
+| 54 = @cil_ble_un_s
+| 55 = @cil_blt_un_s
+| 56 = @cil_br
+| 57 = @cil_brfalse
+| 58 = @cil_brtrue
+| 59 = @cil_beq
+| 60 = @cil_bge
+| 61 = @cil_bgt
+| 62 = @cil_ble
+| 63 = @cil_blt
+| 64 = @cil_bne_un
+| 65 = @cil_bge_un
+| 66 = @cil_bgt_un
+| 67 = @cil_ble_un
+| 68 = @cil_blt_un
+| 69 = @cil_switch
+| 70 = @cil_ldind_i1
+| 71 = @cil_ldind_u1
+| 72 = @cil_ldind_i2
+| 73 = @cil_ldind_u2
+| 74 = @cil_ldind_i4
+| 75 = @cil_ldind_u4
+| 76 = @cil_ldind_i8
+| 77 = @cil_ldind_i
+| 78 = @cil_ldind_r4
+| 79 = @cil_ldind_r8
+| 80 = @cil_ldind_ref
+| 81 = @cil_stind_ref
+| 82 = @cil_stind_i1
+| 83 = @cil_stind_i2
+| 84 = @cil_stind_i4
+| 85 = @cil_stind_i8
+| 86 = @cil_stind_r4
+| 87 = @cil_stind_r8
+| 88 = @cil_add
+| 89 = @cil_sub
+| 90 = @cil_mul
+| 91 = @cil_div
+| 92 = @cil_div_un
+| 93 = @cil_rem
+| 94 = @cil_rem_un
+| 95 = @cil_and
+| 96 = @cil_or
+| 97 = @cil_xor
+| 98 = @cil_shl
+| 99 = @cil_shr
+| 100 = @cil_shr_un
+| 101 = @cil_neg
+| 102 = @cil_not
+| 103 = @cil_conv_i1
+| 104 = @cil_conv_i2
+| 105 = @cil_conv_i4
+| 106 = @cil_conv_i8
+| 107 = @cil_conv_r4
+| 108 = @cil_conv_r8
+| 109 = @cil_conv_u4
+| 110 = @cil_conv_u8
+| 111 = @cil_callvirt
+| 112 = @cil_cpobj
+| 113 = @cil_ldobj
+| 114 = @cil_ldstr
+| 115 = @cil_newobj
+| 116 = @cil_castclass
+| 117 = @cil_isinst
+| 118 = @cil_conv_r_un
+| 121 = @cil_unbox
+| 122 = @cil_throw
+| 123 = @cil_ldfld
+| 124 = @cil_ldflda
+| 125 = @cil_stfld
+| 126 = @cil_ldsfld
+| 127 = @cil_ldsflda
+| 128 = @cil_stsfld
+| 129 = @cil_stobj
+| 130 = @cil_conv_ovf_i1_un
+| 131 = @cil_conv_ovf_i2_un
+| 132 = @cil_conv_ovf_i4_un
+| 133 = @cil_conv_ovf_i8_un
+| 134 = @cil_conv_ovf_u1_un
+| 135 = @cil_conv_ovf_u2_un
+| 136 = @cil_conv_ovf_u4_un
+| 137 = @cil_conv_ovf_u8_un
+| 138 = @cil_conv_ovf_i_un
+| 139 = @cil_conv_ovf_u_un
+| 140 = @cil_box
+| 141 = @cil_newarr
+| 142 = @cil_ldlen
+| 143 = @cil_ldelema
+| 144 = @cil_ldelem_i1
+| 145 = @cil_ldelem_u1
+| 146 = @cil_ldelem_i2
+| 147 = @cil_ldelem_u2
+| 148 = @cil_ldelem_i4
+| 149 = @cil_ldelem_u4
+| 150 = @cil_ldelem_i8
+| 151 = @cil_ldelem_i
+| 152 = @cil_ldelem_r4
+| 153 = @cil_ldelem_r8
+| 154 = @cil_ldelem_ref
+| 155 = @cil_stelem_i
+| 156 = @cil_stelem_i1
+| 157 = @cil_stelem_i2
+| 158 = @cil_stelem_i4
+| 159 = @cil_stelem_i8
+| 160 = @cil_stelem_r4
+| 161 = @cil_stelem_r8
+| 162 = @cil_stelem_ref
+| 163 = @cil_ldelem
+| 164 = @cil_stelem
+| 165 = @cil_unbox_any
+| 179 = @cil_conv_ovf_i1
+| 180 = @cil_conv_ovf_u1
+| 181 = @cil_conv_ovf_i2
+| 182 = @cil_conv_ovf_u2
+| 183 = @cil_conv_ovf_i4
+| 184 = @cil_conv_ovf_u4
+| 185 = @cil_conv_ovf_i8
+| 186 = @cil_conv_ovf_u8
+| 194 = @cil_refanyval
+| 195 = @cil_ckinfinite
+| 198 = @cil_mkrefany
+| 208 = @cil_ldtoken
+| 209 = @cil_conv_u2
+| 210 = @cil_conv_u1
+| 211 = @cil_conv_i
+| 212 = @cil_conv_ovf_i
+| 213 = @cil_conv_ovf_u
+| 214 = @cil_add_ovf
+| 215 = @cil_add_ovf_un
+| 216 = @cil_mul_ovf
+| 217 = @cil_mul_ovf_un
+| 218 = @cil_sub_ovf
+| 219 = @cil_sub_ovf_un
+| 220 = @cil_endfinally
+| 221 = @cil_leave
+| 222 = @cil_leave_s
+| 223 = @cil_stind_i
+| 224 = @cil_conv_u
+| 65024 = @cil_arglist
+| 65025 = @cil_ceq
+| 65026 = @cil_cgt
+| 65027 = @cil_cgt_un
+| 65028 = @cil_clt
+| 65029 = @cil_clt_un
+| 65030 = @cil_ldftn
+| 65031 = @cil_ldvirtftn
+| 65033 = @cil_ldarg
+| 65034 = @cil_ldarga
+| 65035 = @cil_starg
+| 65036 = @cil_ldloc
+| 65037 = @cil_ldloca
+| 65038 = @cil_stloc
+| 65039 = @cil_localloc
+| 65041 = @cil_endfilter
+| 65042 = @cil_unaligned
+| 65043 = @cil_volatile
+| 65044 = @cil_tail
+| 65045 = @cil_initobj
+| 65046 = @cil_constrained
+| 65047 = @cil_cpblk
+| 65048 = @cil_initblk
+| 65050 = @cil_rethrow
+| 65052 = @cil_sizeof
+| 65053 = @cil_refanytype
+| 65054 = @cil_readonly
+;
+
+// CIL ignored instructions
+
+@cil_ignore = @cil_nop | @cil_break | @cil_volatile | @cil_unaligned;
+
+// CIL local/parameter/field access
+
+@cil_ldarg_any = @cil_ldarg_0 | @cil_ldarg_1 | @cil_ldarg_2 | @cil_ldarg_3 | @cil_ldarg_s | @cil_ldarga_s | @cil_ldarg | @cil_ldarga;
+@cil_starg_any = @cil_starg | @cil_starg_s;
+
+@cil_ldloc_any = @cil_ldloc_0 | @cil_ldloc_1 | @cil_ldloc_2 | @cil_ldloc_3 | @cil_ldloc_s | @cil_ldloca_s | @cil_ldloc | @cil_ldloca;
+@cil_stloc_any = @cil_stloc_0 | @cil_stloc_1 | @cil_stloc_2 | @cil_stloc_3 | @cil_stloc_s | @cil_stloc;
+
+@cil_ldfld_any = @cil_ldfld | @cil_ldsfld | @cil_ldsflda | @cil_ldflda;
+@cil_stfld_any = @cil_stfld | @cil_stsfld;
+
+@cil_local_access = @cil_stloc_any | @cil_ldloc_any;
+@cil_arg_access = @cil_starg_any | @cil_ldarg_any;
+@cil_read_access = @cil_ldloc_any | @cil_ldarg_any | @cil_ldfld_any;
+@cil_write_access = @cil_stloc_any | @cil_starg_any | @cil_stfld_any;
+
+@cil_stack_access = @cil_local_access | @cil_arg_access;
+@cil_field_access = @cil_ldfld_any | @cil_stfld_any;
+
+@cil_access = @cil_read_access | @cil_write_access;
+
+// CIL constant/literal instructions
+
+@cil_ldc_i = @cil_ldc_i4_any | @cil_ldc_i8;
+
+@cil_ldc_i4_any = @cil_ldc_i4_m1 | @cil_ldc_i4_0 | @cil_ldc_i4_1 | @cil_ldc_i4_2 | @cil_ldc_i4_3 |
+ @cil_ldc_i4_4 | @cil_ldc_i4_5 | @cil_ldc_i4_6 | @cil_ldc_i4_7 | @cil_ldc_i4_8 | @cil_ldc_i4_s | @cil_ldc_i4;
+
+@cil_ldc_r = @cil_ldc_r4 | @cil_ldc_r8;
+
+@cil_literal = @cil_ldnull | @cil_ldc_i | @cil_ldc_r | @cil_ldstr;
+
+// Control flow
+
+@cil_conditional_jump = @cil_binary_jump | @cil_unary_jump;
+@cil_binary_jump = @cil_beq_s | @cil_bge_s | @cil_bgt_s | @cil_ble_s | @cil_blt_s |
+ @cil_bne_un_s | @cil_bge_un_s | @cil_bgt_un_s | @cil_ble_un_s | @cil_blt_un_s |
+ @cil_beq | @cil_bge | @cil_bgt | @cil_ble | @cil_blt |
+ @cil_bne_un | @cil_bge_un | @cil_bgt_un | @cil_ble_un | @cil_blt_un;
+@cil_unary_jump = @cil_brfalse_s | @cil_brtrue_s | @cil_brfalse | @cil_brtrue | @cil_switch;
+@cil_unconditional_jump = @cil_br | @cil_br_s | @cil_leave_any;
+@cil_leave_any = @cil_leave | @cil_leave_s;
+@cil_jump = @cil_unconditional_jump | @cil_conditional_jump;
+
+// CIL call instructions
+
+@cil_call_any = @cil_jmp | @cil_call | @cil_calli | @cil_tail | @cil_callvirt | @cil_newobj;
+
+// CIL expression instructions
+
+@cil_expr = @cil_literal | @cil_binary_expr | @cil_unary_expr | @cil_call_any | @cil_read_access |
+ @cil_newarr | @cil_ldtoken | @cil_sizeof |
+ @cil_ldftn | @cil_ldvirtftn | @cil_localloc | @cil_mkrefany | @cil_refanytype | @cil_arglist | @cil_dup;
+
+@cil_unary_expr =
+ @cil_conversion_operation | @cil_unary_arithmetic_operation | @cil_unary_bitwise_operation|
+ @cil_ldlen | @cil_isinst | @cil_box | @cil_ldobj | @cil_castclass | @cil_unbox_any |
+ @cil_ldind | @cil_unbox;
+
+@cil_conversion_operation =
+ @cil_conv_i1 | @cil_conv_i2 | @cil_conv_i4 | @cil_conv_i8 |
+ @cil_conv_u1 | @cil_conv_u2 | @cil_conv_u4 | @cil_conv_u8 |
+ @cil_conv_ovf_i | @cil_conv_ovf_i_un | @cil_conv_ovf_i1 | @cil_conv_ovf_i1_un |
+ @cil_conv_ovf_i2 | @cil_conv_ovf_i2_un | @cil_conv_ovf_i4 | @cil_conv_ovf_i4_un |
+ @cil_conv_ovf_i8 | @cil_conv_ovf_i8_un | @cil_conv_ovf_u | @cil_conv_ovf_u_un |
+ @cil_conv_ovf_u1 | @cil_conv_ovf_u1_un | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un |
+ @cil_conv_ovf_u4 | @cil_conv_ovf_u4_un | @cil_conv_ovf_u8 | @cil_conv_ovf_u8_un |
+ @cil_conv_r4 | @cil_conv_r8 | @cil_conv_ovf_u2 | @cil_conv_ovf_u2_un |
+ @cil_conv_i | @cil_conv_u | @cil_conv_r_un;
+
+@cil_ldind = @cil_ldind_i | @cil_ldind_i1 | @cil_ldind_i2 | @cil_ldind_i4 | @cil_ldind_i8 |
+ @cil_ldind_r4 | @cil_ldind_r8 | @cil_ldind_ref | @cil_ldind_u1 | @cil_ldind_u2 | @cil_ldind_u4;
+
+@cil_stind = @cil_stind_i | @cil_stind_i1 | @cil_stind_i2 | @cil_stind_i4 | @cil_stind_i8 |
+ @cil_stind_r4 | @cil_stind_r8 | @cil_stind_ref;
+
+@cil_bitwise_operation = @cil_binary_bitwise_operation | @cil_unary_bitwise_operation;
+
+@cil_binary_bitwise_operation = @cil_and | @cil_or | @cil_xor | @cil_shr | @cil_shr | @cil_shr_un | @cil_shl;
+
+@cil_binary_arithmetic_operation = @cil_add | @cil_sub | @cil_mul | @cil_div | @cil_div_un |
+ @cil_rem | @cil_rem_un | @cil_add_ovf | @cil_add_ovf_un | @cil_mul_ovf | @cil_mul_ovf_un |
+ @cil_sub_ovf | @cil_sub_ovf_un;
+
+@cil_unary_bitwise_operation = @cil_not;
+
+@cil_binary_expr = @cil_binary_arithmetic_operation | @cil_binary_bitwise_operation | @cil_read_array | @cil_comparison_operation;
+
+@cil_unary_arithmetic_operation = @cil_neg;
+
+@cil_comparison_operation = @cil_cgt_un | @cil_ceq | @cil_cgt | @cil_clt | @cil_clt_un;
+
+// Elements that retrieve an address of something
+@cil_read_ref = @cil_ldloca_s | @cil_ldarga_s | @cil_ldflda | @cil_ldsflda | @cil_ldelema;
+
+// CIL array instructions
+
+@cil_read_array =
+ @cil_ldelem | @cil_ldelema | @cil_ldelem_i1 | @cil_ldelem_ref | @cil_ldelem_i |
+ @cil_ldelem_i1 | @cil_ldelem_i2 | @cil_ldelem_i4 | @cil_ldelem_i8 | @cil_ldelem_r4 |
+ @cil_ldelem_r8 | @cil_ldelem_u1 | @cil_ldelem_u2 | @cil_ldelem_u4;
+
+@cil_write_array = @cil_stelem | @cil_stelem_ref |
+ @cil_stelem_i | @cil_stelem_i1 | @cil_stelem_i2 | @cil_stelem_i4 | @cil_stelem_i8 |
+ @cil_stelem_r4 | @cil_stelem_r8;
+
+@cil_throw_any = @cil_throw | @cil_rethrow;
+
+#keyset[impl, index]
+cil_instruction(
+ unique int id: @cil_instruction,
+ int opcode: int ref,
+ int index: int ref,
+ int impl: @cil_method_implementation ref);
+
+cil_jump(
+ unique int instruction: @cil_jump ref,
+ int target: @cil_instruction ref);
+
+cil_access(
+ unique int instruction: @cil_instruction ref,
+ int target: @cil_accessible ref);
+
+cil_value(
+ unique int instruction: @cil_literal ref,
+ varchar(900) value: string ref);
+
+#keyset[instruction, index]
+cil_switch(
+ int instruction: @cil_switch ref,
+ int index: int ref,
+ int target: @cil_instruction ref);
+
+cil_instruction_location(
+ unique int id: @cil_instruction ref,
+ int loc: @location ref);
+
+cil_type_location(
+ int id: @cil_type ref,
+ int loc: @location ref);
+
+cil_method_location(
+ int id: @cil_method ref,
+ int loc: @location ref);
+
+@cil_namespace = @namespace;
+
+@cil_type_container = @cil_type | @cil_namespace | @cil_method;
+
+case @cil_type.kind of
+ 0 = @cil_valueorreftype
+| 1 = @cil_typeparameter
+| 2 = @cil_array_type
+| 3 = @cil_pointer_type
+;
+
+cil_type(
+ unique int id: @cil_type,
+ varchar(900) name: string ref,
+ int kind: int ref,
+ int parent: @cil_type_container ref,
+ int sourceDecl: @cil_type ref);
+
+cil_pointer_type(
+ unique int id: @cil_pointer_type ref,
+ int pointee: @cil_type ref);
+
+cil_array_type(
+ unique int id: @cil_array_type ref,
+ int element_type: @cil_type ref,
+ int rank: int ref);
+
+cil_method(
+ unique int id: @cil_method,
+ varchar(900) name: string ref,
+ int parent: @cil_type ref,
+ int return_type: @cil_type ref);
+
+cil_method_source_declaration(
+ unique int method: @cil_method ref,
+ int source: @cil_method ref);
+
+cil_method_implementation(
+ unique int id: @cil_method_implementation,
+ int method: @cil_method ref,
+ int location: @assembly ref);
+
+cil_implements(
+ int id: @cil_method ref,
+ int decl: @cil_method ref);
+
+#keyset[parent, name]
+cil_field(
+ unique int id: @cil_field,
+ int parent: @cil_type ref,
+ varchar(900) name: string ref,
+ int field_type: @cil_type ref);
+
+@cil_element = @cil_instruction | @cil_declaration | @cil_handler | @cil_attribute | @cil_namespace;
+@cil_named_element = @cil_declaration | @cil_namespace;
+@cil_declaration = @cil_variable | @cil_method | @cil_type | @cil_member;
+@cil_accessible = @cil_declaration;
+@cil_variable = @cil_field | @cil_stack_variable;
+@cil_stack_variable = @cil_local_variable | @cil_parameter;
+@cil_member = @cil_method | @cil_type | @cil_field | @cil_property | @cil_event;
+
+#keyset[method, index]
+cil_parameter(
+ unique int id: @cil_parameter,
+ int method: @cil_method ref,
+ int index: int ref,
+ int param_type: @cil_type ref);
+
+cil_parameter_in(unique int id: @cil_parameter ref);
+cil_parameter_out(unique int id: @cil_parameter ref);
+
+cil_setter(unique int prop: @cil_property ref,
+ int method: @cil_method ref);
+
+cil_getter(unique int prop: @cil_property ref,
+ int method: @cil_method ref);
+
+cil_adder(unique int event: @cil_event ref,
+ int method: @cil_method ref);
+
+cil_remover(unique int event: @cil_event ref, int method: @cil_method ref);
+
+cil_raiser(unique int event: @cil_event ref, int method: @cil_method ref);
+
+cil_property(
+ unique int id: @cil_property,
+ int parent: @cil_type ref,
+ varchar(900) name: string ref,
+ int property_type: @cil_type ref);
+
+#keyset[parent, name]
+cil_event(unique int id: @cil_event,
+ int parent: @cil_type ref,
+ varchar(900) name: string ref,
+ int event_type: @cil_type ref);
+
+#keyset[impl, index]
+cil_local_variable(
+ unique int id: @cil_local_variable,
+ int impl: @cil_method_implementation ref,
+ int index: int ref,
+ int var_type: @cil_type ref);
+
+// CIL handlers (exception handlers etc).
+
+case @cil_handler.kind of
+ 0 = @cil_catch_handler
+| 1 = @cil_filter_handler
+| 2 = @cil_finally_handler
+| 4 = @cil_fault_handler
+;
+
+#keyset[impl, index]
+cil_handler(
+ unique int id: @cil_handler,
+ int impl: @cil_method_implementation ref,
+ int index: int ref,
+ int kind: int ref,
+ int try_start: @cil_instruction ref,
+ int try_end: @cil_instruction ref,
+ int handler_start: @cil_instruction ref);
+
+cil_handler_filter(
+ unique int id: @cil_handler ref,
+ int filter_start: @cil_instruction ref);
+
+cil_handler_type(
+ unique int id: @cil_handler ref,
+ int catch_type: @cil_type ref);
+
+@cil_controlflow_node = @cil_entry_point | @cil_instruction;
+
+@cil_entry_point = @cil_method_implementation | @cil_handler;
+
+@cil_dataflow_node = @cil_instruction | @cil_variable | @cil_method;
+
+cil_method_stack_size(
+ unique int method: @cil_method_implementation ref,
+ int size: int ref);
+
+// CIL modifiers
+
+cil_public(int id: @cil_member ref);
+cil_private(int id: @cil_member ref);
+cil_protected(int id: @cil_member ref);
+cil_internal(int id: @cil_member ref);
+cil_static(int id: @cil_member ref);
+cil_sealed(int id: @cil_member ref);
+cil_virtual(int id: @cil_method ref);
+cil_abstract(int id: @cil_member ref);
+cil_class(int id: @cil_type ref);
+cil_interface(int id: @cil_type ref);
+cil_security(int id: @cil_member ref);
+cil_requiresecobject(int id: @cil_method ref);
+cil_specialname(int id: @cil_method ref);
+cil_newslot(int id: @cil_method ref);
+
+cil_base_class(unique int id: @cil_type ref, int base: @cil_type ref);
+cil_base_interface(int id: @cil_type ref, int base: @cil_type ref);
+
+#keyset[unbound, index]
+cil_type_parameter(
+ int unbound: @cil_member ref,
+ int index: int ref,
+ int param: @cil_typeparameter ref);
+
+#keyset[bound, index]
+cil_type_argument(
+ int bound: @cil_member ref,
+ int index: int ref,
+ int t: @cil_type ref);
+
+// CIL type parameter constraints
+
+cil_typeparam_covariant(int tp: @cil_typeparameter ref);
+cil_typeparam_contravariant(int tp: @cil_typeparameter ref);
+cil_typeparam_class(int tp: @cil_typeparameter ref);
+cil_typeparam_struct(int tp: @cil_typeparameter ref);
+cil_typeparam_new(int tp: @cil_typeparameter ref);
+cil_typeparam_constraint(int tp: @cil_typeparameter ref, int supertype: @cil_type ref);
+
+// CIL attributes
+
+cil_attribute(
+ unique int attributeid: @cil_attribute,
+ int element: @cil_declaration ref,
+ int constructor: @cil_method ref);
+
+#keyset[attribute_id, param]
+cil_attribute_named_argument(
+ int attribute_id: @cil_attribute ref,
+ varchar(100) param: string ref,
+ varchar(900) value: string ref);
+
+#keyset[attribute_id, index]
+cil_attribute_positional_argument(
+ int attribute_id: @cil_attribute ref,
+ int index: int ref,
+ varchar(900) value: string ref);
+
+
+// Common .Net data model covering both C# and CIL
+
+// Common elements
+@dotnet_element = @element | @cil_element;
+@dotnet_named_element = @named_element | @cil_named_element;
+@dotnet_callable = @callable | @cil_method;
+@dotnet_variable = @variable | @cil_variable;
+@dotnet_field = @field | @cil_field;
+@dotnet_parameter = @parameter | @cil_parameter;
+@dotnet_declaration = @declaration | @cil_declaration;
+@dotnet_member = @member | @cil_member;
+@dotnet_event = @event | @cil_event;
+@dotnet_property = @property | @cil_property | @indexer;
+
+// Common types
+@dotnet_type = @type | @cil_type;
+@dotnet_call = @call | @cil_call_any;
+@dotnet_throw = @throw_element | @cil_throw_any;
+@dotnet_valueorreftype = @cil_valueorreftype | @value_or_ref_type | @cil_array_type | @void_type;
+@dotnet_typeparameter = @type_parameter | @cil_typeparameter;
+@dotnet_array_type = @array_type | @cil_array_type;
+@dotnet_pointer_type = @pointer_type | @cil_pointer_type;
+@dotnet_type_parameter = @type_parameter | @cil_typeparameter;
+@dotnet_generic = @dotnet_valueorreftype | @dotnet_callable;
+
+// Attributes
+@dotnet_attribute = @attribute | @cil_attribute;
+
+// Expressions
+@dotnet_expr = @expr | @cil_expr;
+
+// Literals
+@dotnet_literal = @literal_expr | @cil_literal;
+@dotnet_string_literal = @string_literal_expr | @cil_ldstr;
+@dotnet_int_literal = @integer_literal_expr | @cil_ldc_i;
+@dotnet_float_literal = @float_literal_expr | @cil_ldc_r;
+@dotnet_null_literal = @null_literal_expr | @cil_ldnull;
+
+@metadata_entity = @cil_method | @cil_type | @cil_field | @cil_property | @field | @property |
+ @callable | @value_or_ref_type | @void_type;
+
+#keyset[entity, location]
+metadata_handle(int entity : @metadata_entity ref, int location: @assembly ref, int handle: int ref)
From b5633625b37460046eecf649fa6672233fa37ed8 Mon Sep 17 00:00:00 2001
From: yoff
Date: Wed, 3 Feb 2021 21:56:03 +0100
Subject: [PATCH 097/429] Update
python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
Co-authored-by: Rasmus Wriedt Larsen
---
.../semmle/python/dataflow/new/internal/DataFlowPrivate.qll | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index 2ab91694217..e17305ee355 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -1323,7 +1323,8 @@ module IterableUnpacking {
predicate iterableUnpackingForReadStep(CfgNode nodeFrom, Content c, Node nodeTo) {
exists(ForTarget target |
nodeFrom.asExpr() = target.getSource() and
- nodeTo = TIterableSequenceNode(target.(SequenceNode))
+ target instanceof SequenceNode and
+ nodeTo = TIterableSequenceNode(target)
) and
(
c instanceof ListElementContent
From a7ca065411166b2c0a388ee225ef95bfe748be88 Mon Sep 17 00:00:00 2001
From: Rasmus Lerchedahl Petersen
Date: Wed, 3 Feb 2021 22:14:15 +0100
Subject: [PATCH 098/429] Python: Fix `ForTarget`
---
.../dataflow/new/internal/DataFlowPrivate.qll | 2 +-
.../dataflow/coverage/dataflow.expected | 44 +++++++++++++++++++
.../experimental/dataflow/coverage/test.py | 4 +-
3 files changed, 47 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
index e17305ee355..10ced3c768c 100644
--- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
+++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll
@@ -1253,7 +1253,7 @@ module IterableUnpacking {
or
exists(Comp comp |
source = comp.getIterable() and
- this.getNode() = comp.getIterationVariable(0).getAStore()
+ this.getNode() = comp.getNthInnerLoop(0).getTarget()
)
}
diff --git a/python/ql/test/experimental/dataflow/coverage/dataflow.expected b/python/ql/test/experimental/dataflow/coverage/dataflow.expected
index 978b810ea23..ff3d518d1bc 100644
--- a/python/ql/test/experimental/dataflow/coverage/dataflow.expected
+++ b/python/ql/test/experimental/dataflow/coverage/dataflow.expected
@@ -95,6 +95,26 @@ edges
| test.py:199:33:199:40 | ControlFlowNode for List [List element] | test.py:199:28:199:28 | SSA variable z |
| test.py:199:34:199:39 | ControlFlowNode for SOURCE | test.py:199:33:199:40 | ControlFlowNode for List [List element] |
| test.py:200:10:200:10 | ControlFlowNode for x [List element] | test.py:200:10:200:13 | ControlFlowNode for Subscript |
+| test.py:205:9:205:47 | ControlFlowNode for ListComp [List element] | test.py:206:10:206:10 | ControlFlowNode for x [List element] |
+| test.py:205:10:205:10 | ControlFlowNode for a | test.py:205:9:205:47 | ControlFlowNode for ListComp [List element] |
+| test.py:205:17:205:17 | SSA variable a | test.py:205:10:205:10 | ControlFlowNode for a |
+| test.py:205:17:205:20 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:205:17:205:17 | SSA variable a |
+| test.py:205:17:205:20 | IterableSequence [Tuple element at index 0] | test.py:205:17:205:20 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:205:26:205:46 | ControlFlowNode for List [List element, Tuple element at index 0] | test.py:205:17:205:20 | IterableSequence [Tuple element at index 0] |
+| test.py:205:28:205:33 | ControlFlowNode for SOURCE | test.py:205:28:205:44 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:205:28:205:44 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:205:26:205:46 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:206:10:206:10 | ControlFlowNode for x [List element] | test.py:206:10:206:13 | ControlFlowNode for Subscript |
+| test.py:210:9:210:51 | ControlFlowNode for ListComp [List element] | test.py:211:10:211:10 | ControlFlowNode for x [List element] |
+| test.py:210:10:210:10 | ControlFlowNode for a [List element] | test.py:210:10:210:13 | ControlFlowNode for Subscript |
+| test.py:210:10:210:13 | ControlFlowNode for Subscript | test.py:210:9:210:51 | ControlFlowNode for ListComp [List element] |
+| test.py:210:20:210:21 | IterableElement | test.py:210:20:210:21 | SSA variable a [List element] |
+| test.py:210:20:210:21 | SSA variable a [List element] | test.py:210:10:210:10 | ControlFlowNode for a [List element] |
+| test.py:210:20:210:24 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:210:20:210:21 | IterableElement |
+| test.py:210:20:210:24 | IterableSequence [Tuple element at index 0] | test.py:210:20:210:24 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:210:30:210:50 | ControlFlowNode for List [List element, Tuple element at index 0] | test.py:210:20:210:24 | IterableSequence [Tuple element at index 0] |
+| test.py:210:32:210:37 | ControlFlowNode for SOURCE | test.py:210:32:210:48 | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:210:32:210:48 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:210:30:210:50 | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:211:10:211:10 | ControlFlowNode for x [List element] | test.py:211:10:211:13 | ControlFlowNode for Subscript |
| test.py:349:11:349:16 | ControlFlowNode for SOURCE | test.py:349:11:349:17 | ControlFlowNode for Tuple [Tuple element at index 0] |
| test.py:349:11:349:17 | ControlFlowNode for Tuple [Tuple element at index 0] | test.py:349:10:349:21 | ControlFlowNode for Subscript |
| test.py:353:10:353:17 | ControlFlowNode for List [List element] | test.py:353:10:353:20 | ControlFlowNode for Subscript |
@@ -440,6 +460,28 @@ nodes
| test.py:199:34:199:39 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
| test.py:200:10:200:10 | ControlFlowNode for x [List element] | semmle.label | ControlFlowNode for x [List element] |
| test.py:200:10:200:13 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| test.py:205:9:205:47 | ControlFlowNode for ListComp [List element] | semmle.label | ControlFlowNode for ListComp [List element] |
+| test.py:205:10:205:10 | ControlFlowNode for a | semmle.label | ControlFlowNode for a |
+| test.py:205:17:205:17 | SSA variable a | semmle.label | SSA variable a |
+| test.py:205:17:205:20 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:205:17:205:20 | IterableSequence [Tuple element at index 0] | semmle.label | IterableSequence [Tuple element at index 0] |
+| test.py:205:26:205:46 | ControlFlowNode for List [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:205:28:205:33 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:205:28:205:44 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:206:10:206:10 | ControlFlowNode for x [List element] | semmle.label | ControlFlowNode for x [List element] |
+| test.py:206:10:206:13 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| test.py:210:9:210:51 | ControlFlowNode for ListComp [List element] | semmle.label | ControlFlowNode for ListComp [List element] |
+| test.py:210:10:210:10 | ControlFlowNode for a [List element] | semmle.label | ControlFlowNode for a [List element] |
+| test.py:210:10:210:13 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
+| test.py:210:20:210:21 | IterableElement | semmle.label | IterableElement |
+| test.py:210:20:210:21 | SSA variable a [List element] | semmle.label | SSA variable a [List element] |
+| test.py:210:20:210:24 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:210:20:210:24 | IterableSequence [Tuple element at index 0] | semmle.label | IterableSequence [Tuple element at index 0] |
+| test.py:210:30:210:50 | ControlFlowNode for List [List element, Tuple element at index 0] | semmle.label | ControlFlowNode for List [List element, Tuple element at index 0] |
+| test.py:210:32:210:37 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
+| test.py:210:32:210:48 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
+| test.py:211:10:211:10 | ControlFlowNode for x [List element] | semmle.label | ControlFlowNode for x [List element] |
+| test.py:211:10:211:13 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| test.py:349:10:349:21 | ControlFlowNode for Subscript | semmle.label | ControlFlowNode for Subscript |
| test.py:349:11:349:16 | ControlFlowNode for SOURCE | semmle.label | ControlFlowNode for SOURCE |
| test.py:349:11:349:17 | ControlFlowNode for Tuple [Tuple element at index 0] | semmle.label | ControlFlowNode for Tuple [Tuple element at index 0] |
@@ -722,6 +764,8 @@ nodes
| test.py:184:10:184:13 | ControlFlowNode for Subscript | test.py:183:23:183:28 | ControlFlowNode for SOURCE | test.py:184:10:184:13 | ControlFlowNode for Subscript | Flow found |
| test.py:189:10:189:13 | ControlFlowNode for Subscript | test.py:188:25:188:30 | ControlFlowNode for SOURCE | test.py:189:10:189:13 | ControlFlowNode for Subscript | Flow found |
| test.py:200:10:200:13 | ControlFlowNode for Subscript | test.py:199:34:199:39 | ControlFlowNode for SOURCE | test.py:200:10:200:13 | ControlFlowNode for Subscript | Flow found |
+| test.py:206:10:206:13 | ControlFlowNode for Subscript | test.py:205:28:205:33 | ControlFlowNode for SOURCE | test.py:206:10:206:13 | ControlFlowNode for Subscript | Flow found |
+| test.py:211:10:211:13 | ControlFlowNode for Subscript | test.py:210:32:210:37 | ControlFlowNode for SOURCE | test.py:211:10:211:13 | ControlFlowNode for Subscript | Flow found |
| test.py:349:10:349:21 | ControlFlowNode for Subscript | test.py:349:11:349:16 | ControlFlowNode for SOURCE | test.py:349:10:349:21 | ControlFlowNode for Subscript | Flow found |
| test.py:353:10:353:20 | ControlFlowNode for Subscript | test.py:353:11:353:16 | ControlFlowNode for SOURCE | test.py:353:10:353:20 | ControlFlowNode for Subscript | Flow found |
| test.py:357:10:357:27 | ControlFlowNode for Subscript | test.py:357:16:357:21 | ControlFlowNode for SOURCE | test.py:357:10:357:27 | ControlFlowNode for Subscript | Flow found |
diff --git a/python/ql/test/experimental/dataflow/coverage/test.py b/python/ql/test/experimental/dataflow/coverage/test.py
index 0da8b40b274..2fdf9a9984d 100644
--- a/python/ql/test/experimental/dataflow/coverage/test.py
+++ b/python/ql/test/experimental/dataflow/coverage/test.py
@@ -203,12 +203,12 @@ def test_nested_comprehension_paren():
# Iterable unpacking in comprehensions
def test_unpacking_comprehension():
x = [a for (a, b) in [(SOURCE, NONSOURCE)]]
- SINK(x[0]) #$ MISSING:flow="SOURCE, l:-1 -> x[0]"
+ SINK(x[0]) #$ flow="SOURCE, l:-1 -> x[0]"
def test_star_unpacking_comprehension():
x = [a[0] for (*a, b) in [(SOURCE, NONSOURCE)]]
- SINK(x[0]) #$ MISSING:flow="SOURCE, l:-1 -> x[0]"
+ SINK(x[0]) #$ flow="SOURCE, l:-1 -> x[0]"
# 6.2.8. Generator expressions
From ebfb1faf779f45a063da91a8db21bd711bf83aaf Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 22:26:46 +0100
Subject: [PATCH 099/429] Python: Autoformat
---
python/ql/src/semmle/python/ApiGraphs.qll | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 54ae9f86875..6928d83477a 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -196,11 +196,11 @@ module API {
/** Gets the root node. */
Root root() { any() }
- /**
- * Gets a node corresponding to an import of module `m`.
+ /**
+ * Gets a node corresponding to an import of module `m`.
*
* Note: You should only use this predicate for top level modules. If you want nodes corresponding to a submodule,
- * you should use `.getMember` on the parent module. For example, for nodes corresponding to the module `foo.bar`,
+ * you should use `.getMember` on the parent module. For example, for nodes corresponding to the module `foo.bar`,
* use `moduleImport("foo").getMember("bar")`.
*/
Node moduleImport(string m) { result = Impl::MkModuleImport(m) }
From ba98b08001886288414f96511d5d284dc9c4c0b6 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 22:31:33 +0100
Subject: [PATCH 100/429] Python: Further elaboration of `use/3`
---
python/ql/src/semmle/python/ApiGraphs.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/python/ql/src/semmle/python/ApiGraphs.qll b/python/ql/src/semmle/python/ApiGraphs.qll
index 6928d83477a..49f1ee1303c 100644
--- a/python/ql/src/semmle/python/ApiGraphs.qll
+++ b/python/ql/src/semmle/python/ApiGraphs.qll
@@ -290,7 +290,7 @@ module API {
exists(DataFlow::LocalSourceNode src, DataFlow::LocalSourceNode pred |
// First, we find a predecessor of the node `ref` that we want to determine. The predecessor
// is any node that is a type-tracked use of a data flow node (`src`), which is itself a
- // reference to the API node `base`.
+ // reference to the API node `base`. Thus, `pred` and `src` both represent uses of `base`.
//
// Once we have identified the predecessor, we define its relation to the successor `ref` as
// well as the label on the edge from `pred` to `ref`. This label describes the nature of
From 5974af661e1c8d11aa92c5fb36ee41e10f616202 Mon Sep 17 00:00:00 2001
From: Taus Brock-Nannestad
Date: Wed, 3 Feb 2021 22:43:21 +0100
Subject: [PATCH 101/429] Python: Update test file
Makes the `a.b.c.d` test more sensible.
Also adds a test that shows a case where we're currently _not_ getting
the right flow.
---
.../experimental/dataflow/ApiGraphs/test.py | 39 +++++++++++++++++--
1 file changed, 36 insertions(+), 3 deletions(-)
diff --git a/python/ql/test/experimental/dataflow/ApiGraphs/test.py b/python/ql/test/experimental/dataflow/ApiGraphs/test.py
index c361e3a169f..8137066ad6f 100644
--- a/python/ql/test/experimental/dataflow/ApiGraphs/test.py
+++ b/python/ql/test/experimental/dataflow/ApiGraphs/test.py
@@ -22,13 +22,46 @@ abc = ab.c #$ use=moduleImport("a").getMember("b").getMember("c")
abcd = abc.d #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d")
-x5 = abcd() #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d").getReturn()
+x5 = abcd.method() #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d").getMember("method").getReturn()
-y5 = x5.method() #$ use=moduleImport("a").getMember("b").getMember("c").getMember("d").getReturn().getMember("method").getReturn()
+from a6 import m6 #$ use=moduleImport("a6").getMember("m6")
+
+x6 = m6().foo().bar() #$ use=moduleImport("a6").getMember("m6").getReturn().getMember("foo").getReturn().getMember("bar").getReturn()
# Relative imports. These are ignored
from .foo import bar
-from ..foobar import baz
\ No newline at end of file
+from ..foobar import baz
+
+
+# Use of imports across scopes
+
+def use_m4():
+ x = m4.blah4 #$ use=moduleImport("a4").getMember("b4").getMember("c4").getMember("blah4")
+
+def local_import_use():
+ from foo import bar #$ use=moduleImport("foo").getMember("bar")
+
+ x = bar() #$ use=moduleImport("foo").getMember("bar").getReturn()
+
+from eggs import ham as spam #$ use=moduleImport("eggs").getMember("ham")
+
+def bbb():
+ f = spam #$ use=moduleImport("eggs").getMember("ham")
+
+from danger import SOURCE #$ use=moduleImport("danger").getMember("SOURCE")
+
+foo = SOURCE #$ use=moduleImport("danger").getMember("SOURCE")
+
+def change_foo():
+ global foo
+ foo = SOURCE #$ use=moduleImport("danger").getMember("SOURCE")
+
+def f():
+ global foo
+ sink(foo) #$ use=moduleImport("danger").getMember("SOURCE")
+ foo = NONSOURCE
+ change_foo()
+ sink(foo) #$ MISSING: use=moduleImport("danger").getMember("SOURCE")
From 5e1e27c2b6b3429623b66531d4fe0b090e70638a Mon Sep 17 00:00:00 2001
From: "Raul Garcia (MSFT)"
Date: Wed, 3 Feb 2021 15:12:31 -0800
Subject: [PATCH 102/429] Adding queries related to the Solorigate campaign
---
.../backdoor/DangerousNativeFunctionCall.ql | 53 ++++++
.../DangerousNativeFunctionCall.qlhelp | 8 +
.../backdoor/PotentialTimeBomb.ql | 153 ++++++++++++++++
.../backdoor/ProcessNameToHashTaintFlow.ql | 57 ++++++
.../ProcessNameToHashTaintFlow.qlhelp | 9 +
.../campaign/Solorigate-Readme.md | 121 +++++++++++++
.../ModifedFnvFunctionDetection.qhelp | 12 ++
.../Solorigate/ModifedFnvFunctionDetection.ql | 32 ++++
.../NumberOfKnownCommandsAboveThreshold.qhelp | 12 ++
.../NumberOfKnownCommandsAboveThreshold.ql | 20 +++
.../NumberOfKnownHashesAboveThreshold.qhelp | 13 ++
.../NumberOfKnownHashesAboveThreshold.ql | 23 +++
.../NumberOfKnownLiteralsAboveThreshold.qhelp | 13 ++
.../NumberOfKnownLiteralsAboveThreshold.ql | 23 +++
...mberOfKnownMethodNamesAboveThreshold.qhelp | 13 ++
.../NumberOfKnownMethodNamesAboveThreshold.ql | 23 +++
.../campaign/Solorigate/Solorigate.qhelp | 13 ++
.../campaign/Solorigate/Solorigate.qll | 163 ++++++++++++++++++
.../SwallowEverythingExceptionHandler.qhelp | 12 ++
.../SwallowEverythingExceptionHandler.ql | 30 ++++
.../Cryptography/NonCryptographicHashes.qll | 108 ++++++++++++
21 files changed, 911 insertions(+)
create mode 100644 csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql
create mode 100644 csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qlhelp
create mode 100644 csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql
create mode 100644 csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql
create mode 100644 csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qlhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate-Readme.md
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.qhelp
create mode 100644 csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.ql
create mode 100644 csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql
new file mode 100644
index 00000000000..cbc42c41ddb
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql
@@ -0,0 +1,53 @@
+/**
+ * @name Potential dangerous use of native functions
+ * @description Please review code for possible malicious intent or unsafe handling.
+ * @kind problem
+ * @problem.severity warning
+ * @precision low
+ * @id cs/backdoor/dangerous-native-functions
+ * @tags security
+ * solorigate
+ */
+
+import csharp
+import semmle.code.csharp.frameworks.system.runtime.InteropServices
+
+predicate isDangerousMethod(Method m) {
+ m.getName() = "OpenProcessToken" or
+ m.getName() = "OpenThreadToken" or
+ m.getName() = "DuplicateToken" or
+ m.getName() = "DuplicateTokenEx" or
+ m.getName().matches("LogonUser%") or
+ m.getName().matches("WNetAddConnection%") or
+ m.getName() = "DeviceIoControl" or
+ m.getName().matches ("LoadLibrary%") or
+ m.getName() = "GetProcAddress" or
+ m.getName().matches ("CreateProcess%") or
+ m.getName().matches ("InitiateSystemShutdown%") or
+ m.getName() = "GetCurrentProcess" or
+ m.getName() = "GetCurrentProcessToken" or
+ m.getName() = "GetCurrentThreadToken" or
+ m.getName() = "GetCurrentThreadEffectiveToken" or
+ m.getName() = "OpenThreadToken" or
+ m.getName() = "SetTokenInformation" or
+ m.getName().matches ("LookupPrivilegeValue%") or
+ m.getName() = "AdjustTokenPrivileges" or
+ m.getName() = "SetProcessPrivilege" or
+ m.getName() = "ImpersonateLoggedOnUser" or
+ m.getName().matches ("Add%Ace%")
+}
+
+predicate isExternMethod(Method externMethod) {
+ externMethod.isExtern()
+ or
+ externMethod.getAnAttribute().getType() instanceof
+ SystemRuntimeInteropServicesDllImportAttributeClass
+ or
+ externMethod.getDeclaringType().getAnAttribute().getType() instanceof
+ SystemRuntimeInteropServicesComImportAttributeClass
+}
+
+from MethodCall mc
+where isExternMethod(mc.getTarget())
+and isDangerousMethod(mc.getTarget())
+select mc, "Call to an external method $@", mc, mc.toString()
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qlhelp b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qlhelp
new file mode 100644
index 00000000000..8d1aff62988
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qlhelp
@@ -0,0 +1,8 @@
+
+
+
+
This query finds native calls to external functions that are often used in creating backdoors or are generally attributed to unsafe code practices.
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql b/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql
new file mode 100644
index 00000000000..367ec6955e2
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql
@@ -0,0 +1,153 @@
+/**
+ * @name Potential Timebomb
+ * @description Flow from a file last modification date (very likely implant installation time) and an offset to condition statement (the trigger)
+ * @kind problem
+ * @precision Low
+ * @problem.severity warning
+ * @id cs/backdoor/potential-time-bomb
+ * @tags security
+ * solorigate
+ */
+
+import csharp
+import DataFlow
+
+/**
+ * Class that will help to find the source for the trigger file-modification date.
+ *
+ * May be extended as new patterns for similar time bombs are found.
+ */
+class GetLastWriteTimeMethod extends Method {
+ GetLastWriteTimeMethod() {
+ this.getQualifiedName() in [ "System.IO.File.GetLastWriteTime", "System.IO.File.GetFileCreationTime", "System.IO.File.GetCreationTimeUtc", "System.IO.File.GetLastAccessTimeUtc" ]
+ }
+}
+
+/**
+ * Abstracts `System.DateTime` structure
+ */
+class DateTimeStruct extends Struct {
+ DateTimeStruct() {
+ this.getQualifiedName() = "System.DateTime"
+ }
+
+ /**
+ * holds if the Callable is used for DateTime arithmetic operations
+ */
+ Callable getATimeSpanArtithmeticCallable() {
+ ( result = this.getAnOperator() or result = this.getAMethod()) and
+ ( result.getName() in ["Add", "AddDays", "AddHours", "AddMilliseconds", "AddMinutes", "AddMonths", "AddSeconds", "AddTicks", "AddYears", "+", "-"] )
+ }
+
+ /**
+ * Holds if the Callable is used for DateTime comparision
+ */
+ Callable getAComparisonCallable() {
+ ( result = this.getAnOperator() or result = this.getAMethod()) and
+ ( result.getName() in ["Compare", "CompareTo", "Equals", "==", "!=", "<", ">", "<=", ">="] )
+ }
+}
+
+/**
+ * Dataflow configuration to find flow from a GetLastWriteTime source to a DateTime arithmetic operation
+ */
+private class FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable extends TaintTracking::Configuration {
+ FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable() {
+ this = "FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable"
+ }
+
+ override
+ predicate isSource(DataFlow::Node source) {
+ exists( Call call, GetLastWriteTimeMethod m |
+ m.getACall() = call and
+ source.asExpr() = call
+ )
+ }
+
+ override
+ predicate isSink(DataFlow::Node sink) {
+ exists( Call call, DateTimeStruct dateTime |
+ call.getAChild*() = sink.asExpr() and
+ call = dateTime.getATimeSpanArtithmeticCallable().getACall()
+ )
+ }
+}
+
+/**
+ * Dataflow configuration to find flow from a DateTime arithmetic operation to a DateTime comparison operation
+ */
+private class FlowsFromTimeSpanArithmeticToTimeComparisonCallable extends TaintTracking::Configuration {
+ FlowsFromTimeSpanArithmeticToTimeComparisonCallable() {
+ this = "FlowsFromTimeSpanArithmeticToTimeComparisonCallable"
+ }
+
+ override
+ predicate isSource(DataFlow::Node source) {
+ exists( DateTimeStruct dateTime, Call call |
+ source.asExpr() = call |
+ call = dateTime.getATimeSpanArtithmeticCallable().getACall()
+ )
+ }
+
+ override
+ predicate isSink(DataFlow::Node sink) {
+ exists( Call call, DateTimeStruct dateTime |
+ call.getAnArgument().getAChild*() = sink.asExpr() and
+ call = dateTime.getAComparisonCallable().getACall()
+ )
+
+ }
+}
+
+/**
+ * Dataflow configuration to find flow from a DateTime comparison operation to a Selection Statement (such as an If)
+ */
+private class FlowsFromTimeComparisonCallableToSelectionStatementCondition extends TaintTracking::Configuration {
+ FlowsFromTimeComparisonCallableToSelectionStatementCondition() {
+ this = "FlowsFromTimeComparisonCallableToSelectionStatementCondition"
+ }
+
+ override
+ predicate isSource(DataFlow::Node source) {
+ exists( DateTimeStruct dateTime, Call call |
+ source.asExpr() = call |
+ call = dateTime.getAComparisonCallable().getACall()
+ )
+ }
+
+ override
+ predicate isSink(DataFlow::Node sink) {
+ exists( SelectionStmt sel |
+ sel.getCondition().getAChild*() = sink.asExpr()
+ )
+
+ }
+}
+
+/**
+ * Holds if the last file modification date from the call to getLastWriteTimeMethodCall will be used in a DateTime arithmetic operation timeArithmeticCall,
+ * which is then used for a DateTime comparison timeComparisonCall and the result flows to a Selection statement which is likely a TimeBomb trigger
+ */
+predicate isPotentialTimeBomb( Call getLastWriteTimeMethodCall, Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement ) {
+ exists( FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable config1, Node sink, DateTimeStruct dateTime,
+ FlowsFromTimeSpanArithmeticToTimeComparisonCallable config2, Node sink2,
+ FlowsFromTimeComparisonCallableToSelectionStatementCondition config3, Node sink3 |
+ config1.hasFlow(exprNode(getLastWriteTimeMethodCall), sink) and
+ timeArithmeticCall = dateTime.getATimeSpanArtithmeticCallable().getACall() and
+ timeArithmeticCall.getAChild*() = sink.asExpr() and
+ config2.hasFlow(exprNode(timeArithmeticCall), sink2) and
+ timeComparisonCall = dateTime.getAComparisonCallable().getACall() and
+ timeComparisonCall.getAnArgument().getAChild*() = sink2.asExpr() and
+ config3.hasFlow(exprNode(timeComparisonCall), sink3) and
+ selStatement.getCondition().getAChild*() = sink3.asExpr()
+ )
+}
+
+from Call getLastWriteTimeMethodCall, Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement
+where isPotentialTimeBomb( getLastWriteTimeMethodCall, timeArithmeticCall, timeComparisonCall, selStatement )
+select selStatement, "Possible TimeBomb logic triggered by $@ that takes into account $@ from the $@ as part of the potential trigger."
+ , timeComparisonCall, timeComparisonCall.toString()
+ , timeArithmeticCall, "an offset"
+ ,getLastWriteTimeMethodCall, "last modification time of a file"
+
+
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql
new file mode 100644
index 00000000000..63085f75ddc
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql
@@ -0,0 +1,57 @@
+/**
+ * @name ProcessName to hash function flow
+ * @description Flow from a function retrieving process name to a hash function
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @problem.severity warning
+ * @precision medium
+ * @id cs/backdoor/process-name-to-hash-function
+ */
+
+import csharp
+import DataFlow
+import microsoft.code.csharp.Cryptography.NonCryptographicHashes
+
+class DataFlowFromMethodToHash extends TaintTracking::Configuration {
+ DataFlowFromMethodToHash() {
+ this = "DataFlowFromMethodNameToHashFunction"
+ }
+ /**
+ * Holds if `source` is a relevant data flow source.
+ */
+ override predicate isSource(Node source)
+ {
+ isSuspiciousPropertyName(source.asExpr())
+ }
+
+ /**
+ * Holds if `sink` is a relevant data flow sink.
+ */
+ override predicate isSink(Node sink)
+ {
+ isGetHash(sink.asExpr())
+ }
+}
+
+predicate isGetHash(Expr arg) {
+ exists (MethodCall mc |
+ (mc.getTarget().getName().matches ("%Hash%") or
+ mc.getTarget().getName().regexpMatch("Md[4-5]|Sha[1-9]{1,3}")
+ )
+ and
+ mc.getAnArgument() = arg) or
+ exists( Callable callable, Parameter param, Call call, int i |
+ isCallableAPotentialNonCryptographicHashFunction( callable, param ) and
+ callable.getParameter(i) = param and
+ call = callable.getACall() and
+ call.getArgument(i) = arg)
+}
+
+predicate isSuspiciousPropertyName(PropertyRead pr) {
+ pr.getTarget().getQualifiedName() = "System.Diagnostics.Process.ProcessName"
+}
+
+from Node src, Node sink, DataFlowFromMethodToHash conf
+where conf.hasFlow(src, sink)
+select src, "The hash is calculated on the process name $@, may be related to a backdoor. Please review the code for possible malicious intent.", sink, "here"
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qlhelp b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qlhelp
new file mode 100644
index 00000000000..39d21b32b72
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qlhelp
@@ -0,0 +1,9 @@
+
+
+
+
This query detects code flow from PorcessName property on the Process class into a hash function.
+
Such flow is often used in code backdoors to detect runnig processes and compare them to an obfuscated list of antivirus processes to aviod detection.
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate-Readme.md b/csharp/ql/src/experimental/Security Features/campaign/Solorigate-Readme.md
new file mode 100644
index 00000000000..471fd9ed6f8
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate-Readme.md
@@ -0,0 +1,121 @@
+# Working with Solorigate queries
+
+These queries hunt for Indicators of Compromise (IoCs) associated with the malicious implant code that was inserted into the SolarWinds Orion product.
+
+This ReadMe walks through what each query does and limitations of the approaches taken, suggestions for modifications, and general advice on using CodeQL to author backdoor hunting queries. There are two approaches taken with the queries; the first is to look for syntactic characteristics used in the malicious implant, things like names and particular literals. The second approach looks for semantic patterns – particular functionality and flow associated with the implant.
+
+When editing this queries for open sourcing, we tried to find the right balance between detection capability and false positive rate, mindful that different organizations have differing resources to review the findings.
+
+## Syntactic queries
+
+These queries are targeting specific names/values and other syntactic traits present in the Solorigate IoCs. They tend to either target syntax that is easy for the malicious actor to alter between operations or syntax that is going to be fairly coincidental, so are either very precise but very fragile, or imprecise but more durable. However, they are very fast to execute, and very easy to modify, so can be easily repurposed to do first pass analysis in future campaigns to hunt for code IoCs. Several of them use a list syntax introduced in CodeQL 1.26, so require that version of newer to compile and execute.
+
+### cs/Solorigate/number-of-known-commands-in-enum-above-threshold
+
+**Implemented in:** NumberOfKnownCommandsAboveThreshold.ql
+
+This query looks for enumerations that look like they are defining Command and Control functionality, based on the enumeration in Solorigate. Enumerations are a convenient way of defining and referencing this functionality, so are likely to be used in other implants. This query does allow some fuzziness to how many commands are defined, by default looking for any enumeration with at least 10 of the 18 commands but is brittle to changes in the names of the commands.
+
+The specific list of commands it looks for within the enumeration is defined in Solorigate.qll in countSolorigateCommandInEnum. There are several “clusters” of commands – for example, commands to interact with the file system, with the registry, etc. If implementing an API for file system or registry interactions it is possible to coincidentally include similar command names, but it is unlikely a single enumeration would coincidentally contain commands to interact with the file system, assembly, handle process execution, etc. If you know your code performs a subset of those functions, deleting those specific commands from countSolorigateCommandInEnum and lowering the threshold of the query should reduce false positives, though does increase the chance of false negatives.
+
+This query could be modified to reduce false negatives by decreasing the threshold and changing the name comparisons of the enumeration values to fuzzy matches, but doing so will produce higher false positives. Fuzzy matching is also more computationally costly, so will increase the execution time.
+
+### cs/Solorigate/number-of-known-hashes-above-threshold
+
+**Implemented in:** NumberofKnownHashesAboveThreshold.ql
+
+This query looks for the presence of 5 or more of the XOR’ed hashes Solorigate derived from the names of running processes they wanted to avoid(various anti-malware, etc.). The Solorigate implant would first hash the process name with FNV-1A, and then XOR it with the magic number 6605813339339102567, and then compared to a list of these derived hashes to see if any of the processes the implant wanted to evade were currently running. Likely the reason for the XOR with a magic number is so that comparison values can be unique per implant, so it is very unlikely they were reused elsewhere. However, these values are incredibly unlikely to appear coincidentally in source, so if this query does have findings there is a high probability the findings are malicious.
+
+The derived hash literals are defined in Solorigate.qll in “solorigateSuspiciousHashes”. If a similar campaign happens in the future this query can easily be repurposed by swapping the current hash values with the values for that campaign. Like now, doing so probably won’t trigger findings as the malicious actor would likely make the values unique per implant, but this is a very quick query to both author/tweak and to execute, so the cost of checking “just in case” is quite low.
+
+“cs/Solorigate/modified-fnv-function-detection” and “cs/backdoor/process-name-to-hash-function” attempt to detect the actual process name hashing technique rather than the particular values, so will be durable to use of different magic numbers.
+
+### cs/Solorigate/number-of-known-literals-above-threshold
+
+**Implemented In:** NumberOfKnownLiteralsAboveThreshold.ql
+
+This query looks for literals present in the implant, however since many of the literals would appear coincidentally in a great deal of code (common IP addresses, host names, etc.), this query does not fire unless at least 30 of the literals are all present. Many of the literals would need to be reused in other implants if similar functionality were desired (though they could be hidden with a reversable obfuscation technique).
+
+The query is ensuring that these are semantic literals, so will not incorrectly count usages in comments/method names/variable names/etc. – this shows that even for a relatively simple detection CodeQL provides advantages over more simplistic text parsing. As previously mentioned, many of these literals are incredibly common, so the likelihood that a finding represents malicious modification is low. Its recommended to triage the other queries first, as triaging the results of this query is more labor intensive and results are less likely to represent malicious activity. Reducing the threshold value in NumberOfKnownLiteralsAboveThreshold.ql will increase the chance that it catches other implants that used a subset of the literals, but will also significantly increase false positives. Alternatively, if the first run produces a large number of results, moving the threshold value up helps reduce the findings to only code that more completely mirrors the literals used in Solorigate, while increasing the possibility potential false negatives.
+
+The list of literals is defined in Solorigate.qll in solorigateSuspiciousLiterals. When run against specific code bases the result set may be more manageable by removing methods and literals known to be legitimately used in that code base, though trimming the list too extensively will increase the chances of false negatives. This query can easily be repurposed for future campaigns by replacing the literals here with the literals that were features of IoCs in those campaigns. If the number of IoC literals is greater in future campaigns, moving the threshold value up from 30 would likely increase detection accuracy without significantly increasing false negative rates. On the flip side, if the number of literals is significantly less, the threshold value will likely need to be decreased from 30.
+
+### cs/solorigate/number-of-known-method-names-above-threshold
+
+**Implemented In:** NumberOfKnownMethodNamesAboveThreshold.ql
+
+This query looks for method names present within the implant, but as with the literals, the method names would appear coincidentally in many code bases as they were intended to blend in. Because the individual method names are highly likely to be coincidentally used, this query only fires if 50 or more are present. This query is ensuring that these are used as method names, so will not incorrectly count usage in literals/comments/variable names/etc. While these are common method names, it would be uncommon (but still possible – we had plenty of false positives) for many of them to all be defined within the same project. Excluding methods that are common overrides in class declarations (ex. ToString) will lower the false positive rate, but with the threshold of 50 we found the result set manageable without that adjustment.
+
+The list of literals is defined in Solorigate.qll in solorigateSuspiciousMethodNames. While the previous queries looking for the command Enumeration, Derived process hashes, and literals may provide some value if ported to cover other languages, the specific method list for this query does not, as the methods are particular for C#. However, if an additional implant targeting another language is discovered in the future, the basic approach used in this query is re-usable even if the method names are not.
+
+## Semantic Queries
+
+The semantic queries are not looking for any specific names or terms unique to the IoCs, but rather techniques or patterns used for the implant’s functionality. Some can be evaded by implementing the functionality via different semantics, but a malicious agent would need to specifically take pains to do so. These queries may detect unrelated backdoors, and can also be ported to cover other programming languages to detect the same techniques in those languages.
+
+### cs/Solorigate/modified-fnv-function-detection
+
+**Implemented in:** ModifiedFnvFunctionDetection.ql
+
+The Solorigate implant tries to evade various security detection software by comparing hashes of the process names against an embedded list of values. However, to make the embedded list unique per implant, the hashes were then XOR’ed with a magic value. The implant embedded a version of the FNV-1A hash function for this purpose, so this query looks for use of an “FNV-like” implementation with XOR.
+
+The logic that determines what is “FNV-like” is in NonCryptographicHashes.qll, in the predicate “maybeUsedInFNVFunction”. This logic is not looking for specific known implementations of an FNV function, but rather method code that looks like it is trying to implement one of the FNV functions. This should avoid evasions based purely on the name of the function, but deliberate attempts to obfuscate the implementation of the function may be able to evade this analysis.
+
+Separately, and unrelated to Solorigate, this predicate (as well as the one looking for “Elfhash-like”) can be combined with control flow analysis to ensure that these hash functions are not being used in a cryptographic context where their use would be insecure.
+
+### cs/backdoor/process-name-to-hash-function
+
+**Implemented in:** ProcessNameToHashTaintFlow.ql
+
+This query takes a different approach than the previous two – rather than looking for use of FNV-like methods used specifically in conjunction with an XOR, it instead looks for flow from System.Diagnostics.Process.ProcessName to something that “looks like” a hash function. It determines what looks like a hash function by looking for methods whose names contain “Hash” or MD4/5 or some variation of SHA, or whose implementation is either “FNV-Like” or “Elf-Like” (see NonCryptographicHashes.qll for the logic that determines FNV or Elf-like – looking at the implementation is durable to simple renaming of the methods). This approach should catch a variety of process name hashing schemes, though it should be noted that there are legitimate reasons to hash a process name. The flow analysis is interprocedural, so can detect attempts to obfuscate the hashing of process names by distributing the steps across the source. As the definition of a hash function is intentionally a bit fuzzy this query has a medium precision (i.e. it may falsely catch code that is not attempting to hash the process name), and as there are legitimate reasons to hash a process name the chance that an accurate finding represents a malicious implant is moderate to low depending on the code base.
+
+To add additional hash detections logic to cast a wider net, modify isGetHash in ProcessNameToHashTaintFlow.ql.
+
+### cs/backdoor/potential-time-bomb
+
+**Implemented in:** PotentialTimeBomb.ql
+
+This query looks for a the Solorigate Implant's time delay functionality. The Backdoor would not initially activate until 12-14 days *after* the Orion update with the implanted code was installed. It did this by checking the last file write time, randomly adding between 288 and 336 hours to it, and comparing that against the current system time before it initially called back to the C2 servers controlling the campaign. This query detects that technique by looking for flow from GetLastWriteTime to an arithmetic operation, and from there to a comparison against the current time. This query will coincidentally flag any software that invokes an action a certain time after installation - for example, prompting the user to register or activate the product after a certain period has elapsed.
+
+The accuracy of this query could be improved to reduce those coincidental findings by checking if there is then flow to a remote source (which is the case in the Solorigate implant) or to shell or process execution as they are often features of time bombed malicious code, however the additional flow steps do increase the execution time and increase the chance of false negatives. This query as is produced a manageable set of false positives when run across our thousands of databases, so we suggest first running it as is, and only making those modifications if the practice appears to be common in your code bases.
+
+To make this query more generic, it could be modified to check for other ways of persisting an initial data on the system - reading a registry key, file contents, system reboot time, etc. However, each additional data source will increase false positives.
+
+### cs/Solorigate/swallow-everything-exception
+
+**Implemented in:** SwallowEverythingExceptionHandler.ql
+
+The Solorigate Implant wraps the implanted code in a
+
+ try {
+ //stuff
+ }
+ catch (Exception) {}
+
+to ensure that runtime exceptions didn’t tip anyone off. This query looks for all catches of generic exceptions, with empty exception blocks. The detection is high precision – it will accurately find all empty generic exception handlers. However, it is unfortunately a common enough bad programming practice that there are plenty of writeups in existence explaining that it is a bad programming practice, so the likelihood of the findings being a malicious implant are low. It’s recommended that they be reviewed to ensure this bad practice isn’t hiding latent (or not so latent) bugs in the code.
+
+It can be evaded by including any statements in the catch block, but the existing cs/catch-of-all-exceptions query can be used to catch that evasion. That query’s findings will be a superset of this query, as it will catch many legitimate situations where the exception was anticipated and the catch block can take action that would make swallowing the exception and continuing on relatively risk free. By looking at empty catch statements, this query looks for the exact pattern in the Solorigate implant and will generate fewer findings to review.
+
+### cs/backdoor/dangerous-native-functions
+
+**Implemented in:** DangerousNativeFunctionCalls.ql
+
+This query looks for several native Windows API that might be present in backdoor functionality, including several used in Solorigate. It is also more broadly useful in looking for a technique that can be used to evade queries looking for malicious execution of processes by calling the native platform libraries instead of the .Net Class libraries. We are including it to act as inspiration for other query developers to think about how they can query not just for backdoor patterns but also how they can hunt for attempts to then evade those queries.
+
+While the results of this query are useful on their own, both to cast a wide net to find this evasion technique, and to review why the developers chose the more error prone PInvoke implementation in benign findings, this query was kept simple specifically to use as an example. It can easily be combined with Data Flow or Taint Flow by adding a call to the isExternMethod predicate in DangerousNativeFunctionCalls to an overridden isSink implementation. With this relatively simple addition, flow from untrusted sources like web requests into the potentially dangerous API can easily be detected, and at the very least represent a potential security vulnerability that should be reviewed, but may also represent a C2 implementation attempting to evade searches for the standard Class Library functions.
+
+## Build CodeQL Databases from the production Build Servers/Pipelines
+
+The malicious modifications of SolarWinds’ code took place on the build server (more details from [CrowdStrike](https://www.crowdstrike.com/blog/sunspot-malware-technical-analysis/)), so CodeQL databases used for analysis should ideally be built from the same build servers. CodeQL utilizes the same Roslyn Compiler as MSBuild, so absent the malicious actor specifically building checks for the presence of CodeQL, it is likely the injection technique utilized by the malicious actor would replicate when CodeQL was run. Alternatively, if building CodeQL databases in an independent environment, other techniques can be used to validate that the code that was compiled into the final binaries is the same as the source code in the source repository.
+
+> ### FYI
+>
+> While the step-by-step details are outside the scope of this ReadMe, if the build environment is configured to perform deterministic builds (not all compilers support this, nor default to this if they do), the binaries produced by the build environment can be compared to binaries produced from the same source compiled in a distinct environment. Additionally, some compilers can be configured to emit a manifest of the source code hashes for the source files that were compiled (MSBuild puts them in the PDB files emitted during compilation), which can be compared to source hashes created in an independent environment. If the comparison of either the deterministically built binaries or source hashes do not match, that is a clear indicator that further investigation is warranted. These two techniques can be automated to provide ongoing validation of the build output.
+>
+>If comparing source hashes, it is strongly recommended that SHA256 rather than weaker hashes be used. This can be configured in the following Microsoft compilers by using the specified compiler flags below:
+>
+> * cl.exe /ZH:SHA_256
+> * ml.exe /ZH:SHA_256
+> * ml64.exe /ZH:SHA_256
+> * armasm.exe -gh:SHA_256
+> * armasm64.exe -gh:SHA_256
+> * csc.exe /checksumalgorithm:SHA256
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.qhelp
new file mode 100644
index 00000000000..d3abdc85f28
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.qhelp
@@ -0,0 +1,12 @@
+
+
+
+
The malicious code included hash values calculated using a standard FNV-1A 64-bit hash with an additional XOR by 6605813339339102567 after computing the FNV-1A.
+
This query detects FNV-like hash calculations where there is an additional xor (with any static value) after the hash calculation loop.
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql
new file mode 100644
index 00000000000..e8e2f42dfe4
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql
@@ -0,0 +1,32 @@
+/**
+ * @name Detects a modified FNV function
+ * @description Possible indication of Solorigate. Detects a modified FNV1 function, where there is an additional xor using a literal after the regular FNV hash
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @precision high
+ * @id cs/solorigate/modified-fnv-function-detection
+ * @problem.severity error
+ */
+
+import csharp
+import Solorigate
+import microsoft.code.csharp.Cryptography.NonCryptographicHashes
+
+from Variable v, Literal l, LoopStmt loop, Expr additional_xor
+where maybeUsedInFNVFunction( v, _, _, loop)
+ and (
+ exists( BitwiseXorExpr xor2 |
+ xor2.getAnOperand() = l and additional_xor = xor2 |
+ loop.getAControlFlowNode().getASuccessor*() = xor2.getAControlFlowNode()
+ and xor2.getAnOperand() = v.getAnAccess()
+ ) or exists( AssignXorExpr xor2 |
+ xor2.getAnOperand() = l and additional_xor = xor2 |
+ loop.getAControlFlowNode().getASuccessor*() = xor2.getAControlFlowNode()
+ and xor2.getAnOperand() = v.getAnAccess()
+ )
+ )
+ select l, "The variable $@ seems to be used as part of a FNV-like hash calculation, that is modified by an additional $@ expression using literal $@."
+ , v, v.toString()
+ , additional_xor, "xor"
+ , l, l.toString()
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.qhelp
new file mode 100644
index 00000000000..0db1732805d
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.qhelp
@@ -0,0 +1,12 @@
+
+
+
+
This query detects if there is an enum that includes various of the values used for the Solorigate commands.
+
Please notice that by themselves the names of these enum constants are not malign.
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql
new file mode 100644
index 00000000000..154d115a39f
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql
@@ -0,0 +1,20 @@
+/**
+ * @name Number of Solorigate-related command names in enum is above the threshold
+ * @description The enum contains several values that look similar to command and control command names, which may indicate that the code may have been tampered by an external agent.
+ * It is recommended to review the code and verify that there is no unexpected code in this project.
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @problem.severity warning
+ * @precision medium
+ * @id cs/solorigate/number-of-known-commands-in-enum-above-threshold
+ */
+
+import csharp
+import Solorigate
+
+from Enum e, int total
+where total = countSolorigateCommandInEnum(e)
+ and total > 10
+select e, "The enum $@ may be related to Solorigate. It matches " + total + " of the values used for commands in the enum."
+ , e, e.getName()
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.qhelp
new file mode 100644
index 00000000000..a5fca70869b
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.qhelp
@@ -0,0 +1,13 @@
+
+
+
+
This query detects literals that look like Solorigate Hash values used in the Solorigate code.
+
This query detects when the code includes at least 5 of the Hash literals used in the Solorigate tampered code
+
Please notice that by themselves these literals are not malign, but several of the values together would be less likely to be coincidental.
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql
new file mode 100644
index 00000000000..6d4f7818a9e
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Number of Solorigate-related Hashes as literals is above the threshold
+ * @description The total number of Solorigate-related hash literals found in the code is above a threshold, which may indicate that the code may have been tampered by an external agent.
+ * It is recommended to review the code and verify that there is no unexpected code in this project, however it is highly unlikely the hash values would be present coincideentally
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @problem.severity warning
+ * @precision medium
+ * @id cs/solorigate/number-of-known-hashes-above-threshold
+ */
+
+import csharp
+import Solorigate
+
+
+from Literal l, int total, int threshold
+where total = countSolorigateSuspiciousHash()
+ and threshold = 5 // out of ~200 known literals
+ and isSolorigateHash(l)
+ and total > threshold
+select l, "The Hash literal $@ may be related to the Solorigate campaign. Total count = " + total + " is above the threshold " + threshold + "."
+ , l, l.getValue()
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.qhelp
new file mode 100644
index 00000000000..5a275eeadba
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.qhelp
@@ -0,0 +1,13 @@
+
+
+
+
This query detects literals used in the Solorigate code.
+
This query detects when the code includes at least 30 of the literals used in the Solorigate tampered code
+
Please notice that by themselves these literals are not malign.
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql
new file mode 100644
index 00000000000..d736e6fbf42
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Number of Solorigate-related literals is above the threshold
+ * @description The total number of Solorigate-related literals found in the code is above a threshold, which may indicate that the code may have been tampered by an external agent.
+ * It is recommended to review the code and verify that there is no unexpected code in this project.
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @problem.severity warning
+ * @precision medium
+ * @id cs/solorigate/number-of-known-literals-above-threshold
+ */
+
+import csharp
+import Solorigate
+
+
+from Literal l, int total, int threshold
+where total = countSolorigateSuspiciousLiterals()
+ and threshold = 30 // out of ~150 known literals
+ and isSolorigateLiteral(l)
+ and total > threshold
+select l, "The literal $@ may be related to the Solorigate campaign. Total count = " + total + " is above the threshold " + threshold + "."
+ , l, l.getValue()
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.qhelp
new file mode 100644
index 00000000000..9216fa9e2e5
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.qhelp
@@ -0,0 +1,13 @@
+
+
+
+
This query detects method names used in the Solorigate code.
+
This query detects when the code includes at least 50 of the mthods used in the Solorigate tampered code
+
Please notice that by themselves these method names are not malign.
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql
new file mode 100644
index 00000000000..f38a2f2dfb8
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql
@@ -0,0 +1,23 @@
+/**
+ * @name Number of Solorigate-related method names is above the threshold
+ * @description The total number of Solorigate-related method names found in the code is above a threshold, which may indicate that the code may have been tampered by an external agent.
+ * It is recommended to review the code and verify that there is no unexpected code in this project.
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @problem.severity warning
+ * @precision medium
+ * @id cs/solorigate/number-of-known-method-names-above-threshold
+ */
+
+import csharp
+import Solorigate
+
+
+from Method m, int total, int threshold
+where total = countSolorigateSuspiciousMethodNames()
+ and threshold = 50 // out of ~ 100 known names
+ and isSolorigateSuspiciousMethodName(m)
+ and total > threshold
+select m, "The method $@ may be related to Solorigate. Total count = " + total + " is above the threshold " + threshold + "."
+ , m, m.getName()
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qhelp
new file mode 100644
index 00000000000..798ae65ba8b
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qhelp
@@ -0,0 +1,13 @@
+
+
+
+
The nation-state supply chain attack on SolarWinds known as Solorigate or SunBurst gave nation-state actors access to some victims' networks.
+
The purpose of these rules is to identify poetntially tampered code that requires further analysis
+
+
+
Any findings from these rules are only intended to indicate suspicious code that shares similarities with known portions of code used for this attack, but no certainty that the code is related or part of any attack.
+
For more information, please visit https://aka.ms/solorigate.
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll
new file mode 100644
index 00000000000..77cd4c0fe2e
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll
@@ -0,0 +1,163 @@
+/* *
+ * Provides reusable predicates related to Solorigate
+ *
+ */
+
+import csharp
+
+/*
+ * Returns a list of Literals representing process hashes. These are unlikely to be recycled between campaigns, so not expected to have hits, but if present
+ * are almost certainly an indicator of compromise
+ * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ * and https://github.com/fireeye/sunburst_countermeasures/blob/main/fnv1a_xor_hashes.txt
+ */
+private string solorigateSuspiciousHashes() {
+ result = [
+ "10063651499895178962", "10235971842993272939", "10296494671777307979", "10336842116636872171", "10374841591685794123", "10393903804869831898", "10463926208560207521",
+ "10484659978517092504", "10501212300031893463", "10545868833523019926", "10657751674541025650", "106672141413120087", "10734127004244879770", "10829648878147112121",
+ "1099511628211", "11073283311104541690", "1109067043404435916", "11109294216876344399", "11266044540366291518", "11385275378891906608", "11771945869106552231", "11801746708619571308",
+ "11818825521849580123", "11913842725949116895", "12027963942392743532", "12094027092655598256", "12343334044036541897", "12445177985737237804", "12445232961318634374",
+ "12574535824074203265", "12679195163651834776", "12709986806548166638", "12718416789200275332", "12785322942775634499", "12790084614253405985", "12969190449276002545",
+ "13014156621614176974", "13029357933491444455", "13135068273077306806", "13260224381505715848", "13316211011159594063", "13464308873961738403", "13544031715334011032",
+ "13581776705111912829", "13599785766252827703", "13611051401579634621", "13611814135072561278", "13655261125244647696", "1367627386496056834", "1368907909245890092", "13693525876560827283",
+ "13783346438774742614", "13799353263187722717", "13825071784440082496", "13852439084267373191", "13876356431472225791", "14055243717250701608", "14079676299181301772", "14095938998438966337",
+ "14111374107076822891", "14193859431895170587", "14226582801651130532", "14243671177281069512", "14256853800858727521", "14480775929210717493", "14482658293117931546",
+ "14513577387099045298", "14630721578341374856", "14695981039346656037", "14710585101020280896", "1475579823244607677", "14868920869169964081", "14968320160131875803", "14971809093655817917",
+ "15039834196857999838", "15092207615430402812", "15114163911481793350", "15194901817027173566", "15267980678929160412", "15457732070353984570", "15514036435533858158",
+ "15535773470978271326", "15587050164583443069", "155978580751494388", "15695338751700748390", "15997665423159927228", "16066522799090129502", "16066651430762394116", "16112751343173365533",
+ "16130138450758310172", "1614465773938842903", "16292685861617888592", "16335643316870329598", "16423314183614230717", "16570804352575357627", "1682585410644922036", "16858955978146406642",
+ "16990567851129491937", "17017923349298346219", "17097380490166623672", "17109238199226571972", "17204844226884380288", "17291806236368054941", "17351543633914244545",
+ "17439059603042731363", "17574002783607647274", "17624147599670377042", "17633734304611248415", "17683972236092287897", "17849680105131524334", "17939405613729073960", "17956969551821596225",
+ "17978774977754553159", "17984632978012874803", "17997967489723066537", "18147627057830191163", "18150909006539876521", "18159703063075866524", "18246404330670877335",
+ "18294908219222222902", "18392881921099771407", "18446744073709551613", "191060519014405309", "2032008861530788751",
+ "2128122064571842954", "2147483647", "2147745794", "2380224015317016190", "2478231962306073784", "2532538262737333146",
+ "2589926981877829912", "2597124982561782591", "2600364143812063535", "2717025511528702475", "2734787258623754862", "27407921587843457", "2760663353550280147",
+ "2797129108883749491", "2810460305047003196", "292198192373389586", "2934149816356927366", "3045986759481489935", "3178468437029279937", "3200333496547938354", "3320026265773918739",
+ "3320767229281015341", "3341747963119755850", "3407972863931386250", "3413052607651207697", "3413886037471417852", "3421197789791424393", "3421213182954201407", "3425260965299690882",
+ "3538022140597504361", "3575761800716667678", "3588624367609827560", "3626142665768487764", "3642525650883269872", "3656637464651387014", "3660705254426876796", "3769837838875367802",
+ "3778500091710709090", "3796405623695665524", "3869935012404164040", "3890769468012566366", "3890794756780010537", "397780960855462669", "4030236413975199654", "4088976323439621041",
+ "4454255944391929578", "4501656691368064027", "4578480846255629462", "4821863173800309721", "4931721628717906635", "506634811745884560", "5132256620104998637",
+ "5183687599225757871", "521157249538507889", "5219431737322569038", "541172992193764396", "5415426428750045503", "5449730069165757263", "5587557070429522647", "5614586596107908838",
+ "576626207276463000", "5942282052525294911", "5945487981219695001", "5984963105389676759", "607197993339007484",
+ "6088115528707848728", "6116246686670134098", "6180361713414290679", "6195833633417633900", "6274014997237900919", "640589622539783622", "6461429591783621719", "6491986958834001955",
+ "6508141243778577344", "6605813339339102567", "682250828679635420", "6827032273910657891", "6943102301517884811", "700598796416086955", "7080175711202577138",
+ "7175363135479931834", "7315838824213522000", "7412338704062093516", "7516148236133302073", "7574774749059321801", "7701683279824397773", "7775177810774851294", "7810436520414958497",
+ "7878537243757499832", "79089792725215063", "7982848972385914508", "8052533790968282297", "8129411991672431889", "8146185202538899243", "835151375515278827", "8381292265993977266",
+ "8408095252303317471", "8473756179280619170", "8478833628889826985", "8612208440357175863", "8697424601205169055", "8698326794961817906", "8709004393777297355", "8727477769544302060",
+ "8760312338504300643", "8799118153397725683", "8873858923435176895", "8994091295115840290", "9007106680104765185", "9061219083560670602", "9149947745824492274", "917638920165491138", "9234894663364701749",
+ "9333057603143916814", "9384605490088500348", "9531326785919727076", "9555688264681862794", "9559632696372799208", "9903758755917170407"
+ ]
+
+}
+
+/*
+ * Holds if the literal is one that matches a literal found in Solorigate code.
+ *
+ * NOTE: Some of the values have been commented out as they are commonly found elsewhere.
+ */
+predicate isSolorigateHash(Literal l){
+ l.getValue() = solorigateSuspiciousHashes()
+}
+
+/*
+ * Returns the total number of Solorigate-related literales found in the project
+ */
+int countSolorigateSuspiciousHash(){
+ result = count(string s | exists( Literal l | s = l.getValue() and s = solorigateSuspiciousHashes()))
+}
+
+/*
+ * Returns a list of Literals used by Solorigate
+ *
+ * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ * and https://github.com/fireeye/sunburst_countermeasures/blob/main/fnv1a_xor_hashes.txt
+ */
+private string solorigateSuspiciousLiterals() {
+ result = [ "(?i)([^a-z]|^)(test)([^a-z]|$)", "(?i)(solarwinds)", "[{0,5}] {1,-16} {2}\t{3,5} {4}\\{5}\n", "[{0,5}] {1}\n", "[E] {0} {1} {2}",
+ "\"\\{[0-9a-f-]{36}\\}\"|\"[0-9a-f]{32}\"|\"[0-9a-f]{16}\"", ".CortexPlugin", ".Orion", "\"EventName\":\"EventManager\",", "\"EventType\":\"Orion\",",
+ "\\OrionImprovement\\SolarWinds.OrionImprovement.exe", "0123456789abcdefghijklmnopqrstuvwxyz-_.",
+ "\"sessionId\":\"{0}\",", "\"steps\":[", "\"Succeeded\":true,", "\"Timestamp\":\"\\/Date({0})\\/\",", "\"userId\":\"{0}\",", "{0} {1} HTTP/{2}\n",
+ "10140", "144.86.226.0", "154.118.140.0", "172.16.0.0", "18.130.0.0", "184.72.0.0", "192.168.0.0", "199.201.117.0", "20.140.0.0", "20100", "20220", "217.163.7.0", "224.0.0.0",
+ "240.0.0.0", "255.240.0.0", "255.254.0.0", "255.255.248.0", "3.0.0.382", "41.84.159.0", "43140", "4320", "43260", "524287", "583da945-62af-10e8-4902-a8f205c72b2e", "65280",
+ "71.152.53.0", "74.114.24.0", "8.18.144.0", "87.238.80.0", "96.31.172.0", "983040", "99.79.0.0",
+ "Administrator", "advapi32.dll", "Apollo", "appsync-api", "avsvmcloud.com", "api.solarwinds.com", "-root", "-cert", "-universal_ca", "-ca", "-primary_ca", "-timestamp", "-global", "-secureca", "CloudMonitoring",
+ "MACAddress", "DHCPEnabled", "DHCPServer", "DNSHostName", "DNSDomainSuffixSearchOrder", "DNSServerSearchOrder", "IPAddress", "IPSubnet", "DefaultIPGateway", "OSArchitecture", "InstallDate", "Organization", "RegisteredUser",
+ "fc00::", "fe00::", "fec0::", "ffc0::", "ff00::",
+ "HKCC", "HKCR", "HKCU", "HKDD", "HKEY_CLASSES_ROOT", "HKEY_CURRENT_CONFIG", "HKEY_CURRENT_USER", "HKEY_DYN_DATA", "HKEY_LOCAL_MACHINE", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
+ "HKEY_PERFOMANCE_DATA", "HKEY_USERS", "HKLM", "HKPD", "HKU", "If-None-Match", "Microsoft-CryptoAPI/", "Nodes", "Volumes", "Interfaces", "Components",
+ "opensans", "Organization", "OSArchitecture", "ParentProcessID", "PathName", "ReportWatcherPostpone", "ReportWatcherRetry", "S-1-5-", "SeRestorePrivilege", "SeShutdownPrivilege", "SeTakeOwnershipPrivilege",
+ "SolarWinds", "SolarWindsOrionImprovementClient/", "SourceCodePro", "SourceHanSans", "SourceHanSerif", "SourceSerifPro", "Start", "swip/Events", "swip/upd/", "swip/Upload.ashx", "SYSTEM",
+ "SYSTEM\\CurrentControlSet\\services", "us-east-1", "us-east-2", "us-west-2",
+ "fonts/woff/{0}-{1}-{2}{3}.woff2", "fonts/woff/{0}-{1}-{2}-webfont{3}.woff2", "ph2eifo3n5utg1j8d94qrvbmk0sal76c", "pki/crl/{0}{1}{2}.crl", "rq3gsalt6u1iyfzop572d49bnx8cvmkewhj",
+ "Select * From Win32_NetworkAdapterConfiguration where IPEnabled=true", "Select * From Win32_OperatingSystem", "Select * From Win32_Process", "Select * From Win32_SystemDriver", "Select * From Win32_UserAccount"]
+
+}
+
+/*
+ * Holds if the literal is one that matches a literal found in Solorigate code.
+ *
+ * NOTE: Some of the values have been commented out as they are commonly found elsewhere.
+ */
+predicate isSolorigateLiteral(Literal l){
+ l.getValue() = solorigateSuspiciousLiterals()
+}
+
+/*
+ * Returns the total number of Solorigate-related literales found in the project
+ */
+int countSolorigateSuspiciousLiterals(){
+ result = count(string s | exists( Literal l | s = l.getValue() and s = solorigateSuspiciousLiterals()))
+}
+
+/*
+ * Returns a list of method names used by Solorigate
+ *
+ * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ */
+private string solorigateSuspiciousMethodNames() {
+ result = [ "Abort", "AddFileExecutionEngine", "AddRegistryExecutionEngine", "AdjustTokenPrivileges", "Base64Decode", "Base64Encode", "ByteArrayToHexString", "CheckServerConnection", "Close", "CloseHandle", "CollectSystemDescription",
+ "Compress", "CreateSecureString", "CreateString", "CreateUploadRequest", "CreateUploadRequestImpl", "Decompress", "DecryptShort", "Deflate", "DelayMin", "DelayMs", "DeleteFile", "DeleteRegistryValue", "DeleteValue",
+ "ExecuteEngine", "FileExists", "GetAddresses", "GetAddressFamily", "GetArgumentIndex", "GetBaseUri", "GetBaseUriImpl", "GetCache", "GetCurrentProcess", "GetCurrentString", "GetDescriptionId", "GetFileHash", "GetFileSystemEntries",
+ "GetHash", "GetHive", "GetIntArray", "GetIPHostEntry", "GetManagementObjectProperty", "GetNetworkAdapterConfiguration", "GetNewOwnerName", "GetNextString", "GetNextStringEx", "GetOrCreateUserID", "GetOrionImprovementCustomerId",
+ "GetOSVersion", "GetPreviousString", "GetProcessByDescription", "GetRegistrySubKeyAndValueNames", "GetStatus", "GetStringHash", "GetSubKeyAndValueNames", "GetUserAgent", "GetValue", "GetWebProxy", "HexStringToByteArray", "Inflate",
+ "Initialize", "InitiateSystemShutdownExW", "IsNullOrInvalidName", "IsSynchronized", "KillTask", "LookupPrivilegeValueW", "OpenProcessToken", "ParseServiceResponse", "Quote", "ReadConfig", "ReadDeviceInfo", "ReadRegistryValue",
+ "ReadReportStatus", "ReadServiceStatus", "RebootComputer", "RunTask", "SearchAssemblies", "SearchConfigurations", "SearchServices", "SetAutomaticMode", "SetKeyOwner", "SetKeyOwnerWithPrivileges", "SetKeyPermissions", "SetManualMode",
+ "SetProcessPrivilege", "SetRegistryValue", "SetTime", "SetValue", "SplitString", "ToString", "TrackEvent", "TrackProcesses", "Unquote", "Unzip", "Update", "UpdateBuffer", "UpdateNotification", "UploadSystemDescription", "Valid",
+ "WriteConfig", "WriteFile", "WriteReportStatus", "WriteServiceStatus", "Zip"]
+}
+
+/*
+ * Holds if the method is one that matches a method found in Solorigate code.
+ *
+ * NOTE: Pretty much all of these method names are common.
+ */
+predicate isSolorigateSuspiciousMethodName(Method m) {
+ m.fromSource() and
+ (
+ m.getName() = solorigateSuspiciousMethodNames()
+
+ )
+}
+
+/*
+ * Returns the total number of Solorigate-related method names found in the project
+ */
+int countSolorigateSuspiciousMethodNames(){
+ result = count(string s | exists(Method m | s=m.getName() and s = solorigateSuspiciousMethodNames()))
+}
+
+
+/*
+ * Returns the total number of Solorigate-related commands in the given enum
+ *
+ * This command list is described at https://www.fireeye.com/blog/products-and-services/2020/12/global-intrusion-campaign-leverages-software-supply-chain-compromise.html
+ * and the enum names are based from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ */
+int countSolorigateCommandInEnum(Enum e) {
+ result = count(string s, EnumConstant ec |
+ e.getAnEnumConstant() = ec and
+ s = ec.getName() and
+ s in [ "Idle", "Exit", "SetTime", "CollectSystemDescription", "UploadSystemDescription", "RunTask", "GetProcessByDescription", "KillTask",
+ "GetFileSystemEntries", "WriteFile", "FileExists", "DeleteFile", "GetFileHash", "ReadRegistryValue", "SetRegistryValue",
+ "DeleteRegistryValue", "GetRegistrySubKeyAndValueNames", "Reboot", "None" ] )
+}
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.qhelp b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.qhelp
new file mode 100644
index 00000000000..83fa8f8c58c
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.qhelp
@@ -0,0 +1,12 @@
+
+
+
+
The malicious code was wrapped in generic exception catch blocks that lacked any statements
+
This query detects all generic exception empty catch blocks, but it is strongly suggested that the results for cs/catch-of-all-exceptions also be reviewed in the event that a malicious swallow everything exception handler was not empty
+
+
+
+
+
\ No newline at end of file
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.ql
new file mode 100644
index 00000000000..f481956883c
--- /dev/null
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/SwallowEverythingExceptionHandler.ql
@@ -0,0 +1,30 @@
+/**
+ * @name Looks for use of a Swallow Everything empty catch block
+ * @description Possible indication of Solorigate. The maliciously inserted source was wrapped in a Swallow Everything catch to hide any runtime exceptions
+ * @kind problem
+ * @tags security
+ * solorigate
+ * @precision high
+ * @id cs/solorigate/swallow-everything-exception
+ * @problem.severity error
+ */
+
+import csharp
+import Solorigate
+import semmle.code.csharp.frameworks.System
+
+class GenericCatchClause extends CatchClause {
+ GenericCatchClause() {
+ this instanceof GeneralCatchClause
+ or
+ this =
+ any(SpecificCatchClause scc |
+ scc.getCaughtExceptionType() instanceof SystemExceptionClass and
+ not scc.hasFilterClause()
+ )
+ }
+}
+
+from GenericCatchClause gcc
+where gcc.getBlock().getNumberOfStmts() = 0
+select gcc, "Empty Swallow Everything Exception."
diff --git a/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll b/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll
new file mode 100644
index 00000000000..4f611bac84b
--- /dev/null
+++ b/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll
@@ -0,0 +1,108 @@
+/* *
+ * Predicates that help detect potential non-cryptographic hash functions
+ *
+ * By themselves, non-cryptographic functions are common and not dangerous
+ * These predicates are intended for helping detect non-cryptographic hashes that may be used
+ * in a context that is not appropriate, or for detecting modified hash functions
+ */
+
+import csharp
+private import DataFlow
+private import semmle.code.csharp.dataflow.TaintTracking2
+
+predicate maybeANonCryptogrphicHash( Callable callable, Variable v, Expr xor, Expr mul, LoopStmt loop ) {
+ callable = loop.getEnclosingCallable() and
+ (
+ maybeUsedInFNVFunction( v, xor, mul, loop) or
+ maybeUsedInElfHashFunction( v, xor, mul, loop)
+ )
+}
+
+/**
+ * Holds if the arguments are used in a way that resembles a FNV hash function
+ * where there is a loop statement `loop` where the variable `v` is used in an xor `xor` expression
+ * followed by a multiplication `mul` expression.
+ */
+predicate maybeUsedInFNVFunction( Variable v, Expr xor, Expr mul, LoopStmt loop) {
+ exists( Expr e1, Expr e2 |
+ exists( Operation axore, Operation amule |
+ xor = axore and mul = amule |
+ e1.getAChild*() = v.getAnAccess() and
+ e2.getAChild*() = v.getAnAccess() and
+ e1 = axore.getAnOperand() and
+ e2 = amule.getAnOperand() and
+ axore.getAControlFlowNode().getASuccessor*() = amule.getAControlFlowNode() and
+ (axore instanceof AssignXorExpr or axore instanceof BitwiseXorExpr) and
+ (amule instanceof AssignMulExpr or amule instanceof MulExpr)
+ )
+ )
+ and loop.getAChild*() = mul.getEnclosingStmt()
+ and loop.getAChild*() = xor.getEnclosingStmt()
+}
+
+/**
+ * Holds if the arguments are used in a way that resembles an Elf-Hash hash function
+ * where there is a loop statement `loop` where the variable `v` is used in an xor `xor` expression
+ * followed by an addition `add` expression.
+ */
+predicate maybeUsedInElfHashFunction(Variable v, Expr xorExpr, Expr addExpr, LoopStmt loop) {
+ exists( Expr e1, Operation add, Expr e2, AssignExpr addAssign, Operation xor, AssignExpr xorAssign, Operation notOp, AssignExpr notAssign |
+ xorExpr = xor and
+ addExpr = add and
+ ( add instanceof AddExpr or add instanceof AssignAddExpr ) and
+ e1.getAChild*() = add.getAnOperand() and
+ e1 instanceof BinaryBitwiseOperation and
+ e2 = e1.(BinaryBitwiseOperation).getLeftOperand() and
+ v = addAssign.getTargetVariable() and
+ addAssign.getAChild*() = add and
+ ( xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr ) and
+ addAssign.getAControlFlowNode().getASuccessor*() = xor.getAControlFlowNode() and
+ xorAssign.getAChild*() = xor and
+ v = xorAssign.getTargetVariable() and
+ (notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseOperation ) and
+ xor.getAControlFlowNode().getASuccessor*() = notOp.getAControlFlowNode() and
+ notAssign.getAChild*() = notOp and
+ v = notAssign.getTargetVariable() and
+ loop.getAChild*() = add.getEnclosingStmt() and
+ loop.getAChild*() = xor.getEnclosingStmt()
+ )
+}
+
+/**
+ * Any dataflow from any source to any sink, used internally
+ */
+private class AnyDataFlow extends TaintTracking2::Configuration {
+ AnyDataFlow() {
+ this = "DataFlowFromDataGatheringMethodToVariable"
+ }
+
+ override predicate isSource(Node source) {
+ any()
+ }
+
+ override predicate isSink(Node sink)
+ {
+ any()
+ }
+}
+
+/**
+ * Holds if the Callable is a function that behaves like a non-cryptographic hash
+ * where the parameter `param` is likely the message to hash
+ */
+predicate isCallableAPotentialNonCryptographicHashFunction( Callable callable, Parameter param) {
+ exists( Variable v, Expr op1, Expr op2, LoopStmt loop |
+ maybeANonCryptogrphicHash(callable, v, op1, op2, loop)
+ and callable.getAParameter() = param
+ and (
+ param.getAnAccess() = op1.(Operation).getAnOperand().getAChild*() or
+ param.getAnAccess() = op2.(Operation).getAnOperand().getAChild*() or
+ exists( AnyDataFlow config, Node source, Node sink |
+ ( sink.asExpr() = op1.(Operation).getAChild*() or
+ sink.asExpr() = op2.(Operation).getAChild*()) and
+ source.asExpr() = param.getAnAccess() and
+ config.hasFlow(source, sink)
+ )
+ )
+ )
+}
From 86a2aa97ec4053740182347f19408032b900b62c Mon Sep 17 00:00:00 2001
From: "Raul Garcia (MSFT)"
Date: Wed, 3 Feb 2021 15:48:16 -0800
Subject: [PATCH 103/429] Fixing incorrect file extension & adding suite
---
csharp/ql/src/codeql-suites/solorigate.qls | 4 ++++
...eFunctionCall.qlhelp => DangerousNativeFunctionCall.qhelp} | 0
...oHashTaintFlow.qlhelp => ProcessNameToHashTaintFlow.qhelp} | 0
3 files changed, 4 insertions(+)
create mode 100644 csharp/ql/src/codeql-suites/solorigate.qls
rename csharp/ql/src/experimental/Security Features/backdoor/{DangerousNativeFunctionCall.qlhelp => DangerousNativeFunctionCall.qhelp} (100%)
rename csharp/ql/src/experimental/Security Features/backdoor/{ProcessNameToHashTaintFlow.qlhelp => ProcessNameToHashTaintFlow.qhelp} (100%)
diff --git a/csharp/ql/src/codeql-suites/solorigate.qls b/csharp/ql/src/codeql-suites/solorigate.qls
new file mode 100644
index 00000000000..f6e575bb700
--- /dev/null
+++ b/csharp/ql/src/codeql-suites/solorigate.qls
@@ -0,0 +1,4 @@
+- description: Queries related to Solorigate
+- qlpack: codeql-csharp
+- include:
+ tags contain: solorigate
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qlhelp b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qhelp
similarity index 100%
rename from csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qlhelp
rename to csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.qhelp
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qlhelp b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qhelp
similarity index 100%
rename from csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qlhelp
rename to csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.qhelp
From 53ab787efc28a07949ad08cbcef203023fd8ad3b Mon Sep 17 00:00:00 2001
From: "Raul Garcia (MSFT)"
Date: Wed, 3 Feb 2021 15:54:47 -0800
Subject: [PATCH 104/429] Fixed format
---
.../backdoor/DangerousNativeFunctionCall.ql | 23 +-
.../backdoor/PotentialTimeBomb.ql | 157 +++++----
.../backdoor/ProcessNameToHashTaintFlow.ql | 44 ++-
.../Solorigate/ModifedFnvFunctionDetection.ql | 32 +-
.../NumberOfKnownCommandsAboveThreshold.ql | 12 +-
.../NumberOfKnownHashesAboveThreshold.ql | 17 +-
.../NumberOfKnownLiteralsAboveThreshold.ql | 17 +-
.../NumberOfKnownMethodNamesAboveThreshold.ql | 17 +-
.../campaign/Solorigate/Solorigate.qll | 331 +++++++++++-------
.../Cryptography/NonCryptographicHashes.qll | 141 ++++----
10 files changed, 436 insertions(+), 355 deletions(-)
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql
index cbc42c41ddb..cc8f0bda367 100644
--- a/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql
+++ b/csharp/ql/src/experimental/Security Features/backdoor/DangerousNativeFunctionCall.ql
@@ -20,34 +20,35 @@ predicate isDangerousMethod(Method m) {
m.getName().matches("LogonUser%") or
m.getName().matches("WNetAddConnection%") or
m.getName() = "DeviceIoControl" or
- m.getName().matches ("LoadLibrary%") or
+ m.getName().matches("LoadLibrary%") or
m.getName() = "GetProcAddress" or
- m.getName().matches ("CreateProcess%") or
- m.getName().matches ("InitiateSystemShutdown%") or
+ m.getName().matches("CreateProcess%") or
+ m.getName().matches("InitiateSystemShutdown%") or
m.getName() = "GetCurrentProcess" or
m.getName() = "GetCurrentProcessToken" or
m.getName() = "GetCurrentThreadToken" or
m.getName() = "GetCurrentThreadEffectiveToken" or
m.getName() = "OpenThreadToken" or
m.getName() = "SetTokenInformation" or
- m.getName().matches ("LookupPrivilegeValue%") or
+ m.getName().matches("LookupPrivilegeValue%") or
m.getName() = "AdjustTokenPrivileges" or
m.getName() = "SetProcessPrivilege" or
m.getName() = "ImpersonateLoggedOnUser" or
- m.getName().matches ("Add%Ace%")
+ m.getName().matches("Add%Ace%")
}
predicate isExternMethod(Method externMethod) {
externMethod.isExtern()
or
externMethod.getAnAttribute().getType() instanceof
- SystemRuntimeInteropServicesDllImportAttributeClass
+ SystemRuntimeInteropServicesDllImportAttributeClass
or
externMethod.getDeclaringType().getAnAttribute().getType() instanceof
- SystemRuntimeInteropServicesComImportAttributeClass
+ SystemRuntimeInteropServicesComImportAttributeClass
}
-
+
from MethodCall mc
-where isExternMethod(mc.getTarget())
-and isDangerousMethod(mc.getTarget())
-select mc, "Call to an external method $@", mc, mc.toString()
\ No newline at end of file
+where
+ isExternMethod(mc.getTarget()) and
+ isDangerousMethod(mc.getTarget())
+select mc, "Call to an external method $@", mc, mc.toString()
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql b/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql
index 367ec6955e2..3c83c0145f0 100644
--- a/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql
+++ b/csharp/ql/src/experimental/Security Features/backdoor/PotentialTimeBomb.ql
@@ -14,140 +14,139 @@ import DataFlow
/**
* Class that will help to find the source for the trigger file-modification date.
- *
+ *
* May be extended as new patterns for similar time bombs are found.
*/
class GetLastWriteTimeMethod extends Method {
- GetLastWriteTimeMethod() {
- this.getQualifiedName() in [ "System.IO.File.GetLastWriteTime", "System.IO.File.GetFileCreationTime", "System.IO.File.GetCreationTimeUtc", "System.IO.File.GetLastAccessTimeUtc" ]
- }
+ GetLastWriteTimeMethod() {
+ this.getQualifiedName() in [
+ "System.IO.File.GetLastWriteTime", "System.IO.File.GetFileCreationTime",
+ "System.IO.File.GetCreationTimeUtc", "System.IO.File.GetLastAccessTimeUtc"
+ ]
+ }
}
/**
* Abstracts `System.DateTime` structure
*/
class DateTimeStruct extends Struct {
- DateTimeStruct() {
- this.getQualifiedName() = "System.DateTime"
- }
-
- /**
- * holds if the Callable is used for DateTime arithmetic operations
- */
- Callable getATimeSpanArtithmeticCallable() {
- ( result = this.getAnOperator() or result = this.getAMethod()) and
- ( result.getName() in ["Add", "AddDays", "AddHours", "AddMilliseconds", "AddMinutes", "AddMonths", "AddSeconds", "AddTicks", "AddYears", "+", "-"] )
- }
+ DateTimeStruct() { this.getQualifiedName() = "System.DateTime" }
- /**
- * Holds if the Callable is used for DateTime comparision
- */
- Callable getAComparisonCallable() {
- ( result = this.getAnOperator() or result = this.getAMethod()) and
- ( result.getName() in ["Compare", "CompareTo", "Equals", "==", "!=", "<", ">", "<=", ">="] )
- }
+ /**
+ * holds if the Callable is used for DateTime arithmetic operations
+ */
+ Callable getATimeSpanArtithmeticCallable() {
+ (result = this.getAnOperator() or result = this.getAMethod()) and
+ result.getName() in [
+ "Add", "AddDays", "AddHours", "AddMilliseconds", "AddMinutes", "AddMonths", "AddSeconds",
+ "AddTicks", "AddYears", "+", "-"
+ ]
+ }
+
+ /**
+ * Holds if the Callable is used for DateTime comparision
+ */
+ Callable getAComparisonCallable() {
+ (result = this.getAnOperator() or result = this.getAMethod()) and
+ result.getName() in ["Compare", "CompareTo", "Equals", "==", "!=", "<", ">", "<=", ">="]
+ }
}
/**
- * Dataflow configuration to find flow from a GetLastWriteTime source to a DateTime arithmetic operation
+ * Dataflow configuration to find flow from a GetLastWriteTime source to a DateTime arithmetic operation
*/
private class FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable extends TaintTracking::Configuration {
FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable() {
this = "FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable"
}
- override
- predicate isSource(DataFlow::Node source) {
- exists( Call call, GetLastWriteTimeMethod m |
- m.getACall() = call and
- source.asExpr() = call
+ override predicate isSource(DataFlow::Node source) {
+ exists(Call call, GetLastWriteTimeMethod m |
+ m.getACall() = call and
+ source.asExpr() = call
)
}
- override
- predicate isSink(DataFlow::Node sink) {
- exists( Call call, DateTimeStruct dateTime |
- call.getAChild*() = sink.asExpr() and
- call = dateTime.getATimeSpanArtithmeticCallable().getACall()
+ override predicate isSink(DataFlow::Node sink) {
+ exists(Call call, DateTimeStruct dateTime |
+ call.getAChild*() = sink.asExpr() and
+ call = dateTime.getATimeSpanArtithmeticCallable().getACall()
)
}
}
/**
- * Dataflow configuration to find flow from a DateTime arithmetic operation to a DateTime comparison operation
+ * Dataflow configuration to find flow from a DateTime arithmetic operation to a DateTime comparison operation
*/
private class FlowsFromTimeSpanArithmeticToTimeComparisonCallable extends TaintTracking::Configuration {
FlowsFromTimeSpanArithmeticToTimeComparisonCallable() {
this = "FlowsFromTimeSpanArithmeticToTimeComparisonCallable"
}
- override
- predicate isSource(DataFlow::Node source) {
- exists( DateTimeStruct dateTime, Call call |
- source.asExpr() = call |
- call = dateTime.getATimeSpanArtithmeticCallable().getACall()
+ override predicate isSource(DataFlow::Node source) {
+ exists(DateTimeStruct dateTime, Call call | source.asExpr() = call |
+ call = dateTime.getATimeSpanArtithmeticCallable().getACall()
)
}
- override
- predicate isSink(DataFlow::Node sink) {
- exists( Call call, DateTimeStruct dateTime |
- call.getAnArgument().getAChild*() = sink.asExpr() and
- call = dateTime.getAComparisonCallable().getACall()
+ override predicate isSink(DataFlow::Node sink) {
+ exists(Call call, DateTimeStruct dateTime |
+ call.getAnArgument().getAChild*() = sink.asExpr() and
+ call = dateTime.getAComparisonCallable().getACall()
)
-
}
}
/**
- * Dataflow configuration to find flow from a DateTime comparison operation to a Selection Statement (such as an If)
+ * Dataflow configuration to find flow from a DateTime comparison operation to a Selection Statement (such as an If)
*/
private class FlowsFromTimeComparisonCallableToSelectionStatementCondition extends TaintTracking::Configuration {
FlowsFromTimeComparisonCallableToSelectionStatementCondition() {
this = "FlowsFromTimeComparisonCallableToSelectionStatementCondition"
}
- override
- predicate isSource(DataFlow::Node source) {
- exists( DateTimeStruct dateTime, Call call |
- source.asExpr() = call |
- call = dateTime.getAComparisonCallable().getACall()
+ override predicate isSource(DataFlow::Node source) {
+ exists(DateTimeStruct dateTime, Call call | source.asExpr() = call |
+ call = dateTime.getAComparisonCallable().getACall()
)
}
- override
- predicate isSink(DataFlow::Node sink) {
- exists( SelectionStmt sel |
- sel.getCondition().getAChild*() = sink.asExpr()
- )
-
+ override predicate isSink(DataFlow::Node sink) {
+ exists(SelectionStmt sel | sel.getCondition().getAChild*() = sink.asExpr())
}
}
/**
* Holds if the last file modification date from the call to getLastWriteTimeMethodCall will be used in a DateTime arithmetic operation timeArithmeticCall,
- * which is then used for a DateTime comparison timeComparisonCall and the result flows to a Selection statement which is likely a TimeBomb trigger
+ * which is then used for a DateTime comparison timeComparisonCall and the result flows to a Selection statement which is likely a TimeBomb trigger
*/
-predicate isPotentialTimeBomb( Call getLastWriteTimeMethodCall, Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement ) {
- exists( FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable config1, Node sink, DateTimeStruct dateTime,
- FlowsFromTimeSpanArithmeticToTimeComparisonCallable config2, Node sink2,
- FlowsFromTimeComparisonCallableToSelectionStatementCondition config3, Node sink3 |
- config1.hasFlow(exprNode(getLastWriteTimeMethodCall), sink) and
- timeArithmeticCall = dateTime.getATimeSpanArtithmeticCallable().getACall() and
- timeArithmeticCall.getAChild*() = sink.asExpr() and
- config2.hasFlow(exprNode(timeArithmeticCall), sink2) and
- timeComparisonCall = dateTime.getAComparisonCallable().getACall() and
- timeComparisonCall.getAnArgument().getAChild*() = sink2.asExpr() and
- config3.hasFlow(exprNode(timeComparisonCall), sink3) and
- selStatement.getCondition().getAChild*() = sink3.asExpr()
- )
+predicate isPotentialTimeBomb(
+ Call getLastWriteTimeMethodCall, Call timeArithmeticCall, Call timeComparisonCall,
+ SelectionStmt selStatement
+) {
+ exists(
+ FlowsFromGetLastWriteTimeConfigToTimeSpanArithmeticCallable config1, Node sink,
+ DateTimeStruct dateTime, FlowsFromTimeSpanArithmeticToTimeComparisonCallable config2,
+ Node sink2, FlowsFromTimeComparisonCallableToSelectionStatementCondition config3, Node sink3
+ |
+ config1.hasFlow(exprNode(getLastWriteTimeMethodCall), sink) and
+ timeArithmeticCall = dateTime.getATimeSpanArtithmeticCallable().getACall() and
+ timeArithmeticCall.getAChild*() = sink.asExpr() and
+ config2.hasFlow(exprNode(timeArithmeticCall), sink2) and
+ timeComparisonCall = dateTime.getAComparisonCallable().getACall() and
+ timeComparisonCall.getAnArgument().getAChild*() = sink2.asExpr() and
+ config3.hasFlow(exprNode(timeComparisonCall), sink3) and
+ selStatement.getCondition().getAChild*() = sink3.asExpr()
+ )
}
-from Call getLastWriteTimeMethodCall, Call timeArithmeticCall, Call timeComparisonCall, SelectionStmt selStatement
-where isPotentialTimeBomb( getLastWriteTimeMethodCall, timeArithmeticCall, timeComparisonCall, selStatement )
-select selStatement, "Possible TimeBomb logic triggered by $@ that takes into account $@ from the $@ as part of the potential trigger."
- , timeComparisonCall, timeComparisonCall.toString()
- , timeArithmeticCall, "an offset"
- ,getLastWriteTimeMethodCall, "last modification time of a file"
-
-
+from
+ Call getLastWriteTimeMethodCall, Call timeArithmeticCall, Call timeComparisonCall,
+ SelectionStmt selStatement
+where
+ isPotentialTimeBomb(getLastWriteTimeMethodCall, timeArithmeticCall, timeComparisonCall,
+ selStatement)
+select selStatement,
+ "Possible TimeBomb logic triggered by $@ that takes into account $@ from the $@ as part of the potential trigger.",
+ timeComparisonCall, timeComparisonCall.toString(), timeArithmeticCall, "an offset",
+ getLastWriteTimeMethodCall, "last modification time of a file"
diff --git a/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql
index 63085f75ddc..4b1c657eff3 100644
--- a/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql
+++ b/csharp/ql/src/experimental/Security Features/backdoor/ProcessNameToHashTaintFlow.ql
@@ -14,44 +14,42 @@ import DataFlow
import microsoft.code.csharp.Cryptography.NonCryptographicHashes
class DataFlowFromMethodToHash extends TaintTracking::Configuration {
- DataFlowFromMethodToHash() {
- this = "DataFlowFromMethodNameToHashFunction"
- }
+ DataFlowFromMethodToHash() { this = "DataFlowFromMethodNameToHashFunction" }
+
/**
* Holds if `source` is a relevant data flow source.
*/
- override predicate isSource(Node source)
- {
- isSuspiciousPropertyName(source.asExpr())
- }
+ override predicate isSource(Node source) { isSuspiciousPropertyName(source.asExpr()) }
/**
* Holds if `sink` is a relevant data flow sink.
*/
- override predicate isSink(Node sink)
- {
- isGetHash(sink.asExpr())
- }
+ override predicate isSink(Node sink) { isGetHash(sink.asExpr()) }
}
predicate isGetHash(Expr arg) {
- exists (MethodCall mc |
- (mc.getTarget().getName().matches ("%Hash%") or
- mc.getTarget().getName().regexpMatch("Md[4-5]|Sha[1-9]{1,3}")
+ exists(MethodCall mc |
+ (
+ mc.getTarget().getName().matches("%Hash%") or
+ mc.getTarget().getName().regexpMatch("Md[4-5]|Sha[1-9]{1,3}")
+ ) and
+ mc.getAnArgument() = arg
+ )
+ or
+ exists(Callable callable, Parameter param, Call call, int i |
+ isCallableAPotentialNonCryptographicHashFunction(callable, param) and
+ callable.getParameter(i) = param and
+ call = callable.getACall() and
+ call.getArgument(i) = arg
)
- and
- mc.getAnArgument() = arg) or
- exists( Callable callable, Parameter param, Call call, int i |
- isCallableAPotentialNonCryptographicHashFunction( callable, param ) and
- callable.getParameter(i) = param and
- call = callable.getACall() and
- call.getArgument(i) = arg)
}
predicate isSuspiciousPropertyName(PropertyRead pr) {
- pr.getTarget().getQualifiedName() = "System.Diagnostics.Process.ProcessName"
+ pr.getTarget().getQualifiedName() = "System.Diagnostics.Process.ProcessName"
}
from Node src, Node sink, DataFlowFromMethodToHash conf
where conf.hasFlow(src, sink)
-select src, "The hash is calculated on the process name $@, may be related to a backdoor. Please review the code for possible malicious intent.", sink, "here"
\ No newline at end of file
+select src,
+ "The hash is calculated on the process name $@, may be related to a backdoor. Please review the code for possible malicious intent.",
+ sink, "here"
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql
index e8e2f42dfe4..2e94ea4ad4e 100644
--- a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/ModifedFnvFunctionDetection.ql
@@ -14,19 +14,19 @@ import Solorigate
import microsoft.code.csharp.Cryptography.NonCryptographicHashes
from Variable v, Literal l, LoopStmt loop, Expr additional_xor
-where maybeUsedInFNVFunction( v, _, _, loop)
- and (
- exists( BitwiseXorExpr xor2 |
- xor2.getAnOperand() = l and additional_xor = xor2 |
- loop.getAControlFlowNode().getASuccessor*() = xor2.getAControlFlowNode()
- and xor2.getAnOperand() = v.getAnAccess()
- ) or exists( AssignXorExpr xor2 |
- xor2.getAnOperand() = l and additional_xor = xor2 |
- loop.getAControlFlowNode().getASuccessor*() = xor2.getAControlFlowNode()
- and xor2.getAnOperand() = v.getAnAccess()
- )
- )
- select l, "The variable $@ seems to be used as part of a FNV-like hash calculation, that is modified by an additional $@ expression using literal $@."
- , v, v.toString()
- , additional_xor, "xor"
- , l, l.toString()
+where
+ maybeUsedInFNVFunction(v, _, _, loop) and
+ (
+ exists(BitwiseXorExpr xor2 | xor2.getAnOperand() = l and additional_xor = xor2 |
+ loop.getAControlFlowNode().getASuccessor*() = xor2.getAControlFlowNode() and
+ xor2.getAnOperand() = v.getAnAccess()
+ )
+ or
+ exists(AssignXorExpr xor2 | xor2.getAnOperand() = l and additional_xor = xor2 |
+ loop.getAControlFlowNode().getASuccessor*() = xor2.getAControlFlowNode() and
+ xor2.getAnOperand() = v.getAnAccess()
+ )
+ )
+select l,
+ "The variable $@ seems to be used as part of a FNV-like hash calculation, that is modified by an additional $@ expression using literal $@.",
+ v, v.toString(), additional_xor, "xor", l, l.toString()
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql
index 154d115a39f..f60d4232004 100644
--- a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownCommandsAboveThreshold.ql
@@ -1,5 +1,5 @@
/**
- * @name Number of Solorigate-related command names in enum is above the threshold
+ * @name Number of Solorigate-related command names in enum is above the threshold
* @description The enum contains several values that look similar to command and control command names, which may indicate that the code may have been tampered by an external agent.
* It is recommended to review the code and verify that there is no unexpected code in this project.
* @kind problem
@@ -14,7 +14,9 @@ import csharp
import Solorigate
from Enum e, int total
-where total = countSolorigateCommandInEnum(e)
- and total > 10
-select e, "The enum $@ may be related to Solorigate. It matches " + total + " of the values used for commands in the enum."
- , e, e.getName()
\ No newline at end of file
+where
+ total = countSolorigateCommandInEnum(e) and
+ total > 10
+select e,
+ "The enum $@ may be related to Solorigate. It matches " + total +
+ " of the values used for commands in the enum.", e, e.getName()
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql
index 6d4f7818a9e..830a9b48c2a 100644
--- a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownHashesAboveThreshold.ql
@@ -1,5 +1,5 @@
/**
- * @name Number of Solorigate-related Hashes as literals is above the threshold
+ * @name Number of Solorigate-related Hashes as literals is above the threshold
* @description The total number of Solorigate-related hash literals found in the code is above a threshold, which may indicate that the code may have been tampered by an external agent.
* It is recommended to review the code and verify that there is no unexpected code in this project, however it is highly unlikely the hash values would be present coincideentally
* @kind problem
@@ -13,11 +13,12 @@
import csharp
import Solorigate
-
from Literal l, int total, int threshold
-where total = countSolorigateSuspiciousHash()
- and threshold = 5 // out of ~200 known literals
- and isSolorigateHash(l)
- and total > threshold
-select l, "The Hash literal $@ may be related to the Solorigate campaign. Total count = " + total + " is above the threshold " + threshold + "."
- , l, l.getValue()
\ No newline at end of file
+where
+ total = countSolorigateSuspiciousHash() and
+ threshold = 5 and // out of ~200 known literals
+ isSolorigateHash(l) and
+ total > threshold
+select l,
+ "The Hash literal $@ may be related to the Solorigate campaign. Total count = " + total +
+ " is above the threshold " + threshold + ".", l, l.getValue()
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql
index d736e6fbf42..a280fefddfa 100644
--- a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownLiteralsAboveThreshold.ql
@@ -1,5 +1,5 @@
/**
- * @name Number of Solorigate-related literals is above the threshold
+ * @name Number of Solorigate-related literals is above the threshold
* @description The total number of Solorigate-related literals found in the code is above a threshold, which may indicate that the code may have been tampered by an external agent.
* It is recommended to review the code and verify that there is no unexpected code in this project.
* @kind problem
@@ -13,11 +13,12 @@
import csharp
import Solorigate
-
from Literal l, int total, int threshold
-where total = countSolorigateSuspiciousLiterals()
- and threshold = 30 // out of ~150 known literals
- and isSolorigateLiteral(l)
- and total > threshold
-select l, "The literal $@ may be related to the Solorigate campaign. Total count = " + total + " is above the threshold " + threshold + "."
- , l, l.getValue()
\ No newline at end of file
+where
+ total = countSolorigateSuspiciousLiterals() and
+ threshold = 30 and // out of ~150 known literals
+ isSolorigateLiteral(l) and
+ total > threshold
+select l,
+ "The literal $@ may be related to the Solorigate campaign. Total count = " + total +
+ " is above the threshold " + threshold + ".", l, l.getValue()
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql
index f38a2f2dfb8..277d8c745bd 100644
--- a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/NumberOfKnownMethodNamesAboveThreshold.ql
@@ -1,5 +1,5 @@
/**
- * @name Number of Solorigate-related method names is above the threshold
+ * @name Number of Solorigate-related method names is above the threshold
* @description The total number of Solorigate-related method names found in the code is above a threshold, which may indicate that the code may have been tampered by an external agent.
* It is recommended to review the code and verify that there is no unexpected code in this project.
* @kind problem
@@ -13,11 +13,12 @@
import csharp
import Solorigate
-
from Method m, int total, int threshold
-where total = countSolorigateSuspiciousMethodNames()
- and threshold = 50 // out of ~ 100 known names
- and isSolorigateSuspiciousMethodName(m)
- and total > threshold
-select m, "The method $@ may be related to Solorigate. Total count = " + total + " is above the threshold " + threshold + "."
- , m, m.getName()
\ No newline at end of file
+where
+ total = countSolorigateSuspiciousMethodNames() and
+ threshold = 50 and // out of ~ 100 known names
+ isSolorigateSuspiciousMethodName(m) and
+ total > threshold
+select m,
+ "The method $@ may be related to Solorigate. Total count = " + total + " is above the threshold " +
+ threshold + ".", m, m.getName()
diff --git a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll
index 77cd4c0fe2e..15444930fad 100644
--- a/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll
+++ b/csharp/ql/src/experimental/Security Features/campaign/Solorigate/Solorigate.qll
@@ -1,6 +1,5 @@
-/* *
+/*
* Provides reusable predicates related to Solorigate
- *
*/
import csharp
@@ -8,156 +7,236 @@ import csharp
/*
* Returns a list of Literals representing process hashes. These are unlikely to be recycled between campaigns, so not expected to have hits, but if present
* are almost certainly an indicator of compromise
- * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
* and https://github.com/fireeye/sunburst_countermeasures/blob/main/fnv1a_xor_hashes.txt
*/
+
private string solorigateSuspiciousHashes() {
- result = [
- "10063651499895178962", "10235971842993272939", "10296494671777307979", "10336842116636872171", "10374841591685794123", "10393903804869831898", "10463926208560207521",
- "10484659978517092504", "10501212300031893463", "10545868833523019926", "10657751674541025650", "106672141413120087", "10734127004244879770", "10829648878147112121",
- "1099511628211", "11073283311104541690", "1109067043404435916", "11109294216876344399", "11266044540366291518", "11385275378891906608", "11771945869106552231", "11801746708619571308",
- "11818825521849580123", "11913842725949116895", "12027963942392743532", "12094027092655598256", "12343334044036541897", "12445177985737237804", "12445232961318634374",
- "12574535824074203265", "12679195163651834776", "12709986806548166638", "12718416789200275332", "12785322942775634499", "12790084614253405985", "12969190449276002545",
- "13014156621614176974", "13029357933491444455", "13135068273077306806", "13260224381505715848", "13316211011159594063", "13464308873961738403", "13544031715334011032",
- "13581776705111912829", "13599785766252827703", "13611051401579634621", "13611814135072561278", "13655261125244647696", "1367627386496056834", "1368907909245890092", "13693525876560827283",
- "13783346438774742614", "13799353263187722717", "13825071784440082496", "13852439084267373191", "13876356431472225791", "14055243717250701608", "14079676299181301772", "14095938998438966337",
- "14111374107076822891", "14193859431895170587", "14226582801651130532", "14243671177281069512", "14256853800858727521", "14480775929210717493", "14482658293117931546",
- "14513577387099045298", "14630721578341374856", "14695981039346656037", "14710585101020280896", "1475579823244607677", "14868920869169964081", "14968320160131875803", "14971809093655817917",
- "15039834196857999838", "15092207615430402812", "15114163911481793350", "15194901817027173566", "15267980678929160412", "15457732070353984570", "15514036435533858158",
- "15535773470978271326", "15587050164583443069", "155978580751494388", "15695338751700748390", "15997665423159927228", "16066522799090129502", "16066651430762394116", "16112751343173365533",
- "16130138450758310172", "1614465773938842903", "16292685861617888592", "16335643316870329598", "16423314183614230717", "16570804352575357627", "1682585410644922036", "16858955978146406642",
- "16990567851129491937", "17017923349298346219", "17097380490166623672", "17109238199226571972", "17204844226884380288", "17291806236368054941", "17351543633914244545",
- "17439059603042731363", "17574002783607647274", "17624147599670377042", "17633734304611248415", "17683972236092287897", "17849680105131524334", "17939405613729073960", "17956969551821596225",
- "17978774977754553159", "17984632978012874803", "17997967489723066537", "18147627057830191163", "18150909006539876521", "18159703063075866524", "18246404330670877335",
- "18294908219222222902", "18392881921099771407", "18446744073709551613", "191060519014405309", "2032008861530788751",
- "2128122064571842954", "2147483647", "2147745794", "2380224015317016190", "2478231962306073784", "2532538262737333146",
- "2589926981877829912", "2597124982561782591", "2600364143812063535", "2717025511528702475", "2734787258623754862", "27407921587843457", "2760663353550280147",
- "2797129108883749491", "2810460305047003196", "292198192373389586", "2934149816356927366", "3045986759481489935", "3178468437029279937", "3200333496547938354", "3320026265773918739",
- "3320767229281015341", "3341747963119755850", "3407972863931386250", "3413052607651207697", "3413886037471417852", "3421197789791424393", "3421213182954201407", "3425260965299690882",
- "3538022140597504361", "3575761800716667678", "3588624367609827560", "3626142665768487764", "3642525650883269872", "3656637464651387014", "3660705254426876796", "3769837838875367802",
- "3778500091710709090", "3796405623695665524", "3869935012404164040", "3890769468012566366", "3890794756780010537", "397780960855462669", "4030236413975199654", "4088976323439621041",
- "4454255944391929578", "4501656691368064027", "4578480846255629462", "4821863173800309721", "4931721628717906635", "506634811745884560", "5132256620104998637",
- "5183687599225757871", "521157249538507889", "5219431737322569038", "541172992193764396", "5415426428750045503", "5449730069165757263", "5587557070429522647", "5614586596107908838",
- "576626207276463000", "5942282052525294911", "5945487981219695001", "5984963105389676759", "607197993339007484",
- "6088115528707848728", "6116246686670134098", "6180361713414290679", "6195833633417633900", "6274014997237900919", "640589622539783622", "6461429591783621719", "6491986958834001955",
- "6508141243778577344", "6605813339339102567", "682250828679635420", "6827032273910657891", "6943102301517884811", "700598796416086955", "7080175711202577138",
- "7175363135479931834", "7315838824213522000", "7412338704062093516", "7516148236133302073", "7574774749059321801", "7701683279824397773", "7775177810774851294", "7810436520414958497",
- "7878537243757499832", "79089792725215063", "7982848972385914508", "8052533790968282297", "8129411991672431889", "8146185202538899243", "835151375515278827", "8381292265993977266",
- "8408095252303317471", "8473756179280619170", "8478833628889826985", "8612208440357175863", "8697424601205169055", "8698326794961817906", "8709004393777297355", "8727477769544302060",
- "8760312338504300643", "8799118153397725683", "8873858923435176895", "8994091295115840290", "9007106680104765185", "9061219083560670602", "9149947745824492274", "917638920165491138", "9234894663364701749",
- "9333057603143916814", "9384605490088500348", "9531326785919727076", "9555688264681862794", "9559632696372799208", "9903758755917170407"
- ]
-
-}
-
-/*
- * Holds if the literal is one that matches a literal found in Solorigate code.
- *
- * NOTE: Some of the values have been commented out as they are commonly found elsewhere.
- */
-predicate isSolorigateHash(Literal l){
- l.getValue() = solorigateSuspiciousHashes()
-}
-
-/*
- * Returns the total number of Solorigate-related literales found in the project
- */
-int countSolorigateSuspiciousHash(){
- result = count(string s | exists( Literal l | s = l.getValue() and s = solorigateSuspiciousHashes()))
+ result =
+ [
+ "10063651499895178962", "10235971842993272939", "10296494671777307979",
+ "10336842116636872171", "10374841591685794123", "10393903804869831898",
+ "10463926208560207521", "10484659978517092504", "10501212300031893463",
+ "10545868833523019926", "10657751674541025650", "106672141413120087", "10734127004244879770",
+ "10829648878147112121", "1099511628211", "11073283311104541690", "1109067043404435916",
+ "11109294216876344399", "11266044540366291518", "11385275378891906608",
+ "11771945869106552231", "11801746708619571308", "11818825521849580123",
+ "11913842725949116895", "12027963942392743532", "12094027092655598256",
+ "12343334044036541897", "12445177985737237804", "12445232961318634374",
+ "12574535824074203265", "12679195163651834776", "12709986806548166638",
+ "12718416789200275332", "12785322942775634499", "12790084614253405985",
+ "12969190449276002545", "13014156621614176974", "13029357933491444455",
+ "13135068273077306806", "13260224381505715848", "13316211011159594063",
+ "13464308873961738403", "13544031715334011032", "13581776705111912829",
+ "13599785766252827703", "13611051401579634621", "13611814135072561278",
+ "13655261125244647696", "1367627386496056834", "1368907909245890092", "13693525876560827283",
+ "13783346438774742614", "13799353263187722717", "13825071784440082496",
+ "13852439084267373191", "13876356431472225791", "14055243717250701608",
+ "14079676299181301772", "14095938998438966337", "14111374107076822891",
+ "14193859431895170587", "14226582801651130532", "14243671177281069512",
+ "14256853800858727521", "14480775929210717493", "14482658293117931546",
+ "14513577387099045298", "14630721578341374856", "14695981039346656037",
+ "14710585101020280896", "1475579823244607677", "14868920869169964081", "14968320160131875803",
+ "14971809093655817917", "15039834196857999838", "15092207615430402812",
+ "15114163911481793350", "15194901817027173566", "15267980678929160412",
+ "15457732070353984570", "15514036435533858158", "15535773470978271326",
+ "15587050164583443069", "155978580751494388", "15695338751700748390", "15997665423159927228",
+ "16066522799090129502", "16066651430762394116", "16112751343173365533",
+ "16130138450758310172", "1614465773938842903", "16292685861617888592", "16335643316870329598",
+ "16423314183614230717", "16570804352575357627", "1682585410644922036", "16858955978146406642",
+ "16990567851129491937", "17017923349298346219", "17097380490166623672",
+ "17109238199226571972", "17204844226884380288", "17291806236368054941",
+ "17351543633914244545", "17439059603042731363", "17574002783607647274",
+ "17624147599670377042", "17633734304611248415", "17683972236092287897",
+ "17849680105131524334", "17939405613729073960", "17956969551821596225",
+ "17978774977754553159", "17984632978012874803", "17997967489723066537",
+ "18147627057830191163", "18150909006539876521", "18159703063075866524",
+ "18246404330670877335", "18294908219222222902", "18392881921099771407",
+ "18446744073709551613", "191060519014405309", "2032008861530788751", "2128122064571842954",
+ "2147483647", "2147745794", "2380224015317016190", "2478231962306073784",
+ "2532538262737333146", "2589926981877829912", "2597124982561782591", "2600364143812063535",
+ "2717025511528702475", "2734787258623754862", "27407921587843457", "2760663353550280147",
+ "2797129108883749491", "2810460305047003196", "292198192373389586", "2934149816356927366",
+ "3045986759481489935", "3178468437029279937", "3200333496547938354", "3320026265773918739",
+ "3320767229281015341", "3341747963119755850", "3407972863931386250", "3413052607651207697",
+ "3413886037471417852", "3421197789791424393", "3421213182954201407", "3425260965299690882",
+ "3538022140597504361", "3575761800716667678", "3588624367609827560", "3626142665768487764",
+ "3642525650883269872", "3656637464651387014", "3660705254426876796", "3769837838875367802",
+ "3778500091710709090", "3796405623695665524", "3869935012404164040", "3890769468012566366",
+ "3890794756780010537", "397780960855462669", "4030236413975199654", "4088976323439621041",
+ "4454255944391929578", "4501656691368064027", "4578480846255629462", "4821863173800309721",
+ "4931721628717906635", "506634811745884560", "5132256620104998637", "5183687599225757871",
+ "521157249538507889", "5219431737322569038", "541172992193764396", "5415426428750045503",
+ "5449730069165757263", "5587557070429522647", "5614586596107908838", "576626207276463000",
+ "5942282052525294911", "5945487981219695001", "5984963105389676759", "607197993339007484",
+ "6088115528707848728", "6116246686670134098", "6180361713414290679", "6195833633417633900",
+ "6274014997237900919", "640589622539783622", "6461429591783621719", "6491986958834001955",
+ "6508141243778577344", "6605813339339102567", "682250828679635420", "6827032273910657891",
+ "6943102301517884811", "700598796416086955", "7080175711202577138", "7175363135479931834",
+ "7315838824213522000", "7412338704062093516", "7516148236133302073", "7574774749059321801",
+ "7701683279824397773", "7775177810774851294", "7810436520414958497", "7878537243757499832",
+ "79089792725215063", "7982848972385914508", "8052533790968282297", "8129411991672431889",
+ "8146185202538899243", "835151375515278827", "8381292265993977266", "8408095252303317471",
+ "8473756179280619170", "8478833628889826985", "8612208440357175863", "8697424601205169055",
+ "8698326794961817906", "8709004393777297355", "8727477769544302060", "8760312338504300643",
+ "8799118153397725683", "8873858923435176895", "8994091295115840290", "9007106680104765185",
+ "9061219083560670602", "9149947745824492274", "917638920165491138", "9234894663364701749",
+ "9333057603143916814", "9384605490088500348", "9531326785919727076", "9555688264681862794",
+ "9559632696372799208", "9903758755917170407"
+ ]
}
/*
- * Returns a list of Literals used by Solorigate
- *
- * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ * Holds if the literal is one that matches a literal found in Solorigate code.
+ *
+ * NOTE: Some of the values have been commented out as they are commonly found elsewhere.
+ */
+
+predicate isSolorigateHash(Literal l) { l.getValue() = solorigateSuspiciousHashes() }
+
+/*
+ * Returns the total number of Solorigate-related literales found in the project
+ */
+
+int countSolorigateSuspiciousHash() {
+ result =
+ count(string s | exists(Literal l | s = l.getValue() and s = solorigateSuspiciousHashes()))
+}
+
+/*
+ * Returns a list of Literals used by Solorigate
+ *
+ * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
* and https://github.com/fireeye/sunburst_countermeasures/blob/main/fnv1a_xor_hashes.txt
*/
+
private string solorigateSuspiciousLiterals() {
- result = [ "(?i)([^a-z]|^)(test)([^a-z]|$)", "(?i)(solarwinds)", "[{0,5}] {1,-16} {2}\t{3,5} {4}\\{5}\n", "[{0,5}] {1}\n", "[E] {0} {1} {2}",
- "\"\\{[0-9a-f-]{36}\\}\"|\"[0-9a-f]{32}\"|\"[0-9a-f]{16}\"", ".CortexPlugin", ".Orion", "\"EventName\":\"EventManager\",", "\"EventType\":\"Orion\",",
- "\\OrionImprovement\\SolarWinds.OrionImprovement.exe", "0123456789abcdefghijklmnopqrstuvwxyz-_.",
- "\"sessionId\":\"{0}\",", "\"steps\":[", "\"Succeeded\":true,", "\"Timestamp\":\"\\/Date({0})\\/\",", "\"userId\":\"{0}\",", "{0} {1} HTTP/{2}\n",
- "10140", "144.86.226.0", "154.118.140.0", "172.16.0.0", "18.130.0.0", "184.72.0.0", "192.168.0.0", "199.201.117.0", "20.140.0.0", "20100", "20220", "217.163.7.0", "224.0.0.0",
- "240.0.0.0", "255.240.0.0", "255.254.0.0", "255.255.248.0", "3.0.0.382", "41.84.159.0", "43140", "4320", "43260", "524287", "583da945-62af-10e8-4902-a8f205c72b2e", "65280",
- "71.152.53.0", "74.114.24.0", "8.18.144.0", "87.238.80.0", "96.31.172.0", "983040", "99.79.0.0",
- "Administrator", "advapi32.dll", "Apollo", "appsync-api", "avsvmcloud.com", "api.solarwinds.com", "-root", "-cert", "-universal_ca", "-ca", "-primary_ca", "-timestamp", "-global", "-secureca", "CloudMonitoring",
- "MACAddress", "DHCPEnabled", "DHCPServer", "DNSHostName", "DNSDomainSuffixSearchOrder", "DNSServerSearchOrder", "IPAddress", "IPSubnet", "DefaultIPGateway", "OSArchitecture", "InstallDate", "Organization", "RegisteredUser",
- "fc00::", "fe00::", "fec0::", "ffc0::", "ff00::",
- "HKCC", "HKCR", "HKCU", "HKDD", "HKEY_CLASSES_ROOT", "HKEY_CURRENT_CONFIG", "HKEY_CURRENT_USER", "HKEY_DYN_DATA", "HKEY_LOCAL_MACHINE", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
- "HKEY_PERFOMANCE_DATA", "HKEY_USERS", "HKLM", "HKPD", "HKU", "If-None-Match", "Microsoft-CryptoAPI/", "Nodes", "Volumes", "Interfaces", "Components",
- "opensans", "Organization", "OSArchitecture", "ParentProcessID", "PathName", "ReportWatcherPostpone", "ReportWatcherRetry", "S-1-5-", "SeRestorePrivilege", "SeShutdownPrivilege", "SeTakeOwnershipPrivilege",
- "SolarWinds", "SolarWindsOrionImprovementClient/", "SourceCodePro", "SourceHanSans", "SourceHanSerif", "SourceSerifPro", "Start", "swip/Events", "swip/upd/", "swip/Upload.ashx", "SYSTEM",
- "SYSTEM\\CurrentControlSet\\services", "us-east-1", "us-east-2", "us-west-2",
- "fonts/woff/{0}-{1}-{2}{3}.woff2", "fonts/woff/{0}-{1}-{2}-webfont{3}.woff2", "ph2eifo3n5utg1j8d94qrvbmk0sal76c", "pki/crl/{0}{1}{2}.crl", "rq3gsalt6u1iyfzop572d49bnx8cvmkewhj",
- "Select * From Win32_NetworkAdapterConfiguration where IPEnabled=true", "Select * From Win32_OperatingSystem", "Select * From Win32_Process", "Select * From Win32_SystemDriver", "Select * From Win32_UserAccount"]
-
-}
-
-/*
- * Holds if the literal is one that matches a literal found in Solorigate code.
- *
- * NOTE: Some of the values have been commented out as they are commonly found elsewhere.
- */
-predicate isSolorigateLiteral(Literal l){
- l.getValue() = solorigateSuspiciousLiterals()
-}
-
-/*
- * Returns the total number of Solorigate-related literales found in the project
- */
-int countSolorigateSuspiciousLiterals(){
- result = count(string s | exists( Literal l | s = l.getValue() and s = solorigateSuspiciousLiterals()))
+ result =
+ [
+ "(?i)([^a-z]|^)(test)([^a-z]|$)", "(?i)(solarwinds)", "[{0,5}] {1,-16} {2}\t{3,5} {4}\\{5}\n",
+ "[{0,5}] {1}\n", "[E] {0} {1} {2}",
+ "\"\\{[0-9a-f-]{36}\\}\"|\"[0-9a-f]{32}\"|\"[0-9a-f]{16}\"", ".CortexPlugin", ".Orion",
+ "\"EventName\":\"EventManager\",", "\"EventType\":\"Orion\",",
+ "\\OrionImprovement\\SolarWinds.OrionImprovement.exe",
+ "0123456789abcdefghijklmnopqrstuvwxyz-_.", "\"sessionId\":\"{0}\",", "\"steps\":[",
+ "\"Succeeded\":true,", "\"Timestamp\":\"\\/Date({0})\\/\",", "\"userId\":\"{0}\",",
+ "{0} {1} HTTP/{2}\n", "10140", "144.86.226.0", "154.118.140.0", "172.16.0.0", "18.130.0.0",
+ "184.72.0.0", "192.168.0.0", "199.201.117.0", "20.140.0.0", "20100", "20220", "217.163.7.0",
+ "224.0.0.0", "240.0.0.0", "255.240.0.0", "255.254.0.0", "255.255.248.0", "3.0.0.382",
+ "41.84.159.0", "43140", "4320", "43260", "524287", "583da945-62af-10e8-4902-a8f205c72b2e",
+ "65280", "71.152.53.0", "74.114.24.0", "8.18.144.0", "87.238.80.0", "96.31.172.0", "983040",
+ "99.79.0.0", "Administrator", "advapi32.dll", "Apollo", "appsync-api", "avsvmcloud.com",
+ "api.solarwinds.com", "-root", "-cert", "-universal_ca", "-ca", "-primary_ca", "-timestamp",
+ "-global", "-secureca", "CloudMonitoring", "MACAddress", "DHCPEnabled", "DHCPServer",
+ "DNSHostName", "DNSDomainSuffixSearchOrder", "DNSServerSearchOrder", "IPAddress", "IPSubnet",
+ "DefaultIPGateway", "OSArchitecture", "InstallDate", "Organization", "RegisteredUser",
+ "fc00::", "fe00::", "fec0::", "ffc0::", "ff00::", "HKCC", "HKCR", "HKCU", "HKDD",
+ "HKEY_CLASSES_ROOT", "HKEY_CURRENT_CONFIG", "HKEY_CURRENT_USER", "HKEY_DYN_DATA",
+ "HKEY_LOCAL_MACHINE", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography",
+ "HKEY_PERFOMANCE_DATA", "HKEY_USERS", "HKLM", "HKPD", "HKU", "If-None-Match",
+ "Microsoft-CryptoAPI/", "Nodes", "Volumes", "Interfaces", "Components", "opensans",
+ "Organization", "OSArchitecture", "ParentProcessID", "PathName", "ReportWatcherPostpone",
+ "ReportWatcherRetry", "S-1-5-", "SeRestorePrivilege", "SeShutdownPrivilege",
+ "SeTakeOwnershipPrivilege", "SolarWinds", "SolarWindsOrionImprovementClient/",
+ "SourceCodePro", "SourceHanSans", "SourceHanSerif", "SourceSerifPro", "Start", "swip/Events",
+ "swip/upd/", "swip/Upload.ashx", "SYSTEM", "SYSTEM\\CurrentControlSet\\services", "us-east-1",
+ "us-east-2", "us-west-2", "fonts/woff/{0}-{1}-{2}{3}.woff2",
+ "fonts/woff/{0}-{1}-{2}-webfont{3}.woff2", "ph2eifo3n5utg1j8d94qrvbmk0sal76c",
+ "pki/crl/{0}{1}{2}.crl", "rq3gsalt6u1iyfzop572d49bnx8cvmkewhj",
+ "Select * From Win32_NetworkAdapterConfiguration where IPEnabled=true",
+ "Select * From Win32_OperatingSystem", "Select * From Win32_Process",
+ "Select * From Win32_SystemDriver", "Select * From Win32_UserAccount"
+ ]
}
/*
- * Returns a list of method names used by Solorigate
- *
- * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ * Holds if the literal is one that matches a literal found in Solorigate code.
+ *
+ * NOTE: Some of the values have been commented out as they are commonly found elsewhere.
*/
+
+predicate isSolorigateLiteral(Literal l) { l.getValue() = solorigateSuspiciousLiterals() }
+
+/*
+ * Returns the total number of Solorigate-related literales found in the project
+ */
+
+int countSolorigateSuspiciousLiterals() {
+ result =
+ count(string s | exists(Literal l | s = l.getValue() and s = solorigateSuspiciousLiterals()))
+}
+
+/*
+ * Returns a list of method names used by Solorigate
+ *
+ * This data was extracted from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ */
+
private string solorigateSuspiciousMethodNames() {
- result = [ "Abort", "AddFileExecutionEngine", "AddRegistryExecutionEngine", "AdjustTokenPrivileges", "Base64Decode", "Base64Encode", "ByteArrayToHexString", "CheckServerConnection", "Close", "CloseHandle", "CollectSystemDescription",
- "Compress", "CreateSecureString", "CreateString", "CreateUploadRequest", "CreateUploadRequestImpl", "Decompress", "DecryptShort", "Deflate", "DelayMin", "DelayMs", "DeleteFile", "DeleteRegistryValue", "DeleteValue",
- "ExecuteEngine", "FileExists", "GetAddresses", "GetAddressFamily", "GetArgumentIndex", "GetBaseUri", "GetBaseUriImpl", "GetCache", "GetCurrentProcess", "GetCurrentString", "GetDescriptionId", "GetFileHash", "GetFileSystemEntries",
- "GetHash", "GetHive", "GetIntArray", "GetIPHostEntry", "GetManagementObjectProperty", "GetNetworkAdapterConfiguration", "GetNewOwnerName", "GetNextString", "GetNextStringEx", "GetOrCreateUserID", "GetOrionImprovementCustomerId",
- "GetOSVersion", "GetPreviousString", "GetProcessByDescription", "GetRegistrySubKeyAndValueNames", "GetStatus", "GetStringHash", "GetSubKeyAndValueNames", "GetUserAgent", "GetValue", "GetWebProxy", "HexStringToByteArray", "Inflate",
- "Initialize", "InitiateSystemShutdownExW", "IsNullOrInvalidName", "IsSynchronized", "KillTask", "LookupPrivilegeValueW", "OpenProcessToken", "ParseServiceResponse", "Quote", "ReadConfig", "ReadDeviceInfo", "ReadRegistryValue",
- "ReadReportStatus", "ReadServiceStatus", "RebootComputer", "RunTask", "SearchAssemblies", "SearchConfigurations", "SearchServices", "SetAutomaticMode", "SetKeyOwner", "SetKeyOwnerWithPrivileges", "SetKeyPermissions", "SetManualMode",
- "SetProcessPrivilege", "SetRegistryValue", "SetTime", "SetValue", "SplitString", "ToString", "TrackEvent", "TrackProcesses", "Unquote", "Unzip", "Update", "UpdateBuffer", "UpdateNotification", "UploadSystemDescription", "Valid",
- "WriteConfig", "WriteFile", "WriteReportStatus", "WriteServiceStatus", "Zip"]
+ result =
+ [
+ "Abort", "AddFileExecutionEngine", "AddRegistryExecutionEngine", "AdjustTokenPrivileges",
+ "Base64Decode", "Base64Encode", "ByteArrayToHexString", "CheckServerConnection", "Close",
+ "CloseHandle", "CollectSystemDescription", "Compress", "CreateSecureString", "CreateString",
+ "CreateUploadRequest", "CreateUploadRequestImpl", "Decompress", "DecryptShort", "Deflate",
+ "DelayMin", "DelayMs", "DeleteFile", "DeleteRegistryValue", "DeleteValue", "ExecuteEngine",
+ "FileExists", "GetAddresses", "GetAddressFamily", "GetArgumentIndex", "GetBaseUri",
+ "GetBaseUriImpl", "GetCache", "GetCurrentProcess", "GetCurrentString", "GetDescriptionId",
+ "GetFileHash", "GetFileSystemEntries", "GetHash", "GetHive", "GetIntArray", "GetIPHostEntry",
+ "GetManagementObjectProperty", "GetNetworkAdapterConfiguration", "GetNewOwnerName",
+ "GetNextString", "GetNextStringEx", "GetOrCreateUserID", "GetOrionImprovementCustomerId",
+ "GetOSVersion", "GetPreviousString", "GetProcessByDescription",
+ "GetRegistrySubKeyAndValueNames", "GetStatus", "GetStringHash", "GetSubKeyAndValueNames",
+ "GetUserAgent", "GetValue", "GetWebProxy", "HexStringToByteArray", "Inflate", "Initialize",
+ "InitiateSystemShutdownExW", "IsNullOrInvalidName", "IsSynchronized", "KillTask",
+ "LookupPrivilegeValueW", "OpenProcessToken", "ParseServiceResponse", "Quote", "ReadConfig",
+ "ReadDeviceInfo", "ReadRegistryValue", "ReadReportStatus", "ReadServiceStatus",
+ "RebootComputer", "RunTask", "SearchAssemblies", "SearchConfigurations", "SearchServices",
+ "SetAutomaticMode", "SetKeyOwner", "SetKeyOwnerWithPrivileges", "SetKeyPermissions",
+ "SetManualMode", "SetProcessPrivilege", "SetRegistryValue", "SetTime", "SetValue",
+ "SplitString", "ToString", "TrackEvent", "TrackProcesses", "Unquote", "Unzip", "Update",
+ "UpdateBuffer", "UpdateNotification", "UploadSystemDescription", "Valid", "WriteConfig",
+ "WriteFile", "WriteReportStatus", "WriteServiceStatus", "Zip"
+ ]
}
-/*
+/*
* Holds if the method is one that matches a method found in Solorigate code.
- *
- * NOTE: Pretty much all of these method names are common.
+ *
+ * NOTE: Pretty much all of these method names are common.
*/
+
predicate isSolorigateSuspiciousMethodName(Method m) {
- m.fromSource() and
- (
- m.getName() = solorigateSuspiciousMethodNames()
-
- )
+ m.fromSource() and
+ m.getName() = solorigateSuspiciousMethodNames()
}
-/*
- * Returns the total number of Solorigate-related method names found in the project
+/*
+ * Returns the total number of Solorigate-related method names found in the project
*/
-int countSolorigateSuspiciousMethodNames(){
- result = count(string s | exists(Method m | s=m.getName() and s = solorigateSuspiciousMethodNames()))
+
+int countSolorigateSuspiciousMethodNames() {
+ result =
+ count(string s | exists(Method m | s = m.getName() and s = solorigateSuspiciousMethodNames()))
}
-
-/*
+/*
* Returns the total number of Solorigate-related commands in the given enum
- *
+ *
* This command list is described at https://www.fireeye.com/blog/products-and-services/2020/12/global-intrusion-campaign-leverages-software-supply-chain-compromise.html
- * and the enum names are based from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
+ * and the enum names are based from https://github.com/ITAYC0HEN/SUNBURST-Cracked/tree/a01f358965525bee34ad026acd9dfda3d488fdd8
*/
+
int countSolorigateCommandInEnum(Enum e) {
- result = count(string s, EnumConstant ec |
- e.getAnEnumConstant() = ec and
- s = ec.getName() and
- s in [ "Idle", "Exit", "SetTime", "CollectSystemDescription", "UploadSystemDescription", "RunTask", "GetProcessByDescription", "KillTask",
- "GetFileSystemEntries", "WriteFile", "FileExists", "DeleteFile", "GetFileHash", "ReadRegistryValue", "SetRegistryValue",
- "DeleteRegistryValue", "GetRegistrySubKeyAndValueNames", "Reboot", "None" ] )
+ result =
+ count(string s, EnumConstant ec |
+ e.getAnEnumConstant() = ec and
+ s = ec.getName() and
+ s in [
+ "Idle", "Exit", "SetTime", "CollectSystemDescription", "UploadSystemDescription",
+ "RunTask", "GetProcessByDescription", "KillTask", "GetFileSystemEntries", "WriteFile",
+ "FileExists", "DeleteFile", "GetFileHash", "ReadRegistryValue", "SetRegistryValue",
+ "DeleteRegistryValue", "GetRegistrySubKeyAndValueNames", "Reboot", "None"
+ ]
+ )
}
diff --git a/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll b/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll
index 4f611bac84b..ea4368af7f6 100644
--- a/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll
+++ b/csharp/ql/src/microsoft/code/csharp/Cryptography/NonCryptographicHashes.qll
@@ -1,6 +1,6 @@
-/* *
+/*
* Predicates that help detect potential non-cryptographic hash functions
- *
+ *
* By themselves, non-cryptographic functions are common and not dangerous
* These predicates are intended for helping detect non-cryptographic hashes that may be used
* in a context that is not appropriate, or for detecting modified hash functions
@@ -10,99 +10,98 @@ import csharp
private import DataFlow
private import semmle.code.csharp.dataflow.TaintTracking2
-predicate maybeANonCryptogrphicHash( Callable callable, Variable v, Expr xor, Expr mul, LoopStmt loop ) {
- callable = loop.getEnclosingCallable() and
- (
- maybeUsedInFNVFunction( v, xor, mul, loop) or
- maybeUsedInElfHashFunction( v, xor, mul, loop)
- )
+predicate maybeANonCryptogrphicHash(Callable callable, Variable v, Expr xor, Expr mul, LoopStmt loop) {
+ callable = loop.getEnclosingCallable() and
+ (
+ maybeUsedInFNVFunction(v, xor, mul, loop) or
+ maybeUsedInElfHashFunction(v, xor, mul, loop)
+ )
}
-/**
+/**
* Holds if the arguments are used in a way that resembles a FNV hash function
* where there is a loop statement `loop` where the variable `v` is used in an xor `xor` expression
* followed by a multiplication `mul` expression.
*/
-predicate maybeUsedInFNVFunction( Variable v, Expr xor, Expr mul, LoopStmt loop) {
- exists( Expr e1, Expr e2 |
- exists( Operation axore, Operation amule |
- xor = axore and mul = amule |
- e1.getAChild*() = v.getAnAccess() and
- e2.getAChild*() = v.getAnAccess() and
- e1 = axore.getAnOperand() and
- e2 = amule.getAnOperand() and
- axore.getAControlFlowNode().getASuccessor*() = amule.getAControlFlowNode() and
- (axore instanceof AssignXorExpr or axore instanceof BitwiseXorExpr) and
- (amule instanceof AssignMulExpr or amule instanceof MulExpr)
- )
- )
- and loop.getAChild*() = mul.getEnclosingStmt()
- and loop.getAChild*() = xor.getEnclosingStmt()
+predicate maybeUsedInFNVFunction(Variable v, Expr xor, Expr mul, LoopStmt loop) {
+ exists(Expr e1, Expr e2 |
+ exists(Operation axore, Operation amule | xor = axore and mul = amule |
+ e1.getAChild*() = v.getAnAccess() and
+ e2.getAChild*() = v.getAnAccess() and
+ e1 = axore.getAnOperand() and
+ e2 = amule.getAnOperand() and
+ axore.getAControlFlowNode().getASuccessor*() = amule.getAControlFlowNode() and
+ (axore instanceof AssignXorExpr or axore instanceof BitwiseXorExpr) and
+ (amule instanceof AssignMulExpr or amule instanceof MulExpr)
+ )
+ ) and
+ loop.getAChild*() = mul.getEnclosingStmt() and
+ loop.getAChild*() = xor.getEnclosingStmt()
}
-/**
+/**
* Holds if the arguments are used in a way that resembles an Elf-Hash hash function
* where there is a loop statement `loop` where the variable `v` is used in an xor `xor` expression
* followed by an addition `add` expression.
*/
predicate maybeUsedInElfHashFunction(Variable v, Expr xorExpr, Expr addExpr, LoopStmt loop) {
- exists( Expr e1, Operation add, Expr e2, AssignExpr addAssign, Operation xor, AssignExpr xorAssign, Operation notOp, AssignExpr notAssign |
- xorExpr = xor and
- addExpr = add and
- ( add instanceof AddExpr or add instanceof AssignAddExpr ) and
- e1.getAChild*() = add.getAnOperand() and
- e1 instanceof BinaryBitwiseOperation and
- e2 = e1.(BinaryBitwiseOperation).getLeftOperand() and
- v = addAssign.getTargetVariable() and
- addAssign.getAChild*() = add and
- ( xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr ) and
- addAssign.getAControlFlowNode().getASuccessor*() = xor.getAControlFlowNode() and
- xorAssign.getAChild*() = xor and
- v = xorAssign.getTargetVariable() and
- (notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseOperation ) and
- xor.getAControlFlowNode().getASuccessor*() = notOp.getAControlFlowNode() and
- notAssign.getAChild*() = notOp and
- v = notAssign.getTargetVariable() and
- loop.getAChild*() = add.getEnclosingStmt() and
- loop.getAChild*() = xor.getEnclosingStmt()
- )
+ exists(
+ Expr e1, Operation add, Expr e2, AssignExpr addAssign, Operation xor, AssignExpr xorAssign,
+ Operation notOp, AssignExpr notAssign
+ |
+ xorExpr = xor and
+ addExpr = add and
+ (add instanceof AddExpr or add instanceof AssignAddExpr) and
+ e1.getAChild*() = add.getAnOperand() and
+ e1 instanceof BinaryBitwiseOperation and
+ e2 = e1.(BinaryBitwiseOperation).getLeftOperand() and
+ v = addAssign.getTargetVariable() and
+ addAssign.getAChild*() = add and
+ (xor instanceof BitwiseXorExpr or xor instanceof AssignXorExpr) and
+ addAssign.getAControlFlowNode().getASuccessor*() = xor.getAControlFlowNode() and
+ xorAssign.getAChild*() = xor and
+ v = xorAssign.getTargetVariable() and
+ (notOp instanceof UnaryBitwiseOperation or notOp instanceof AssignBitwiseOperation) and
+ xor.getAControlFlowNode().getASuccessor*() = notOp.getAControlFlowNode() and
+ notAssign.getAChild*() = notOp and
+ v = notAssign.getTargetVariable() and
+ loop.getAChild*() = add.getEnclosingStmt() and
+ loop.getAChild*() = xor.getEnclosingStmt()
+ )
}
/**
* Any dataflow from any source to any sink, used internally
*/
private class AnyDataFlow extends TaintTracking2::Configuration {
- AnyDataFlow() {
- this = "DataFlowFromDataGatheringMethodToVariable"
- }
+ AnyDataFlow() { this = "DataFlowFromDataGatheringMethodToVariable" }
- override predicate isSource(Node source) {
- any()
- }
+ override predicate isSource(Node source) { any() }
- override predicate isSink(Node sink)
- {
- any()
- }
+ override predicate isSink(Node sink) { any() }
}
-/**
+/**
* Holds if the Callable is a function that behaves like a non-cryptographic hash
* where the parameter `param` is likely the message to hash
*/
-predicate isCallableAPotentialNonCryptographicHashFunction( Callable callable, Parameter param) {
- exists( Variable v, Expr op1, Expr op2, LoopStmt loop |
- maybeANonCryptogrphicHash(callable, v, op1, op2, loop)
- and callable.getAParameter() = param
- and (
- param.getAnAccess() = op1.(Operation).getAnOperand().getAChild*() or
- param.getAnAccess() = op2.(Operation).getAnOperand().getAChild*() or
- exists( AnyDataFlow config, Node source, Node sink |
- ( sink.asExpr() = op1.(Operation).getAChild*() or
- sink.asExpr() = op2.(Operation).getAChild*()) and
- source.asExpr() = param.getAnAccess() and
- config.hasFlow(source, sink)
- )
- )
- )
+predicate isCallableAPotentialNonCryptographicHashFunction(Callable callable, Parameter param) {
+ exists(Variable v, Expr op1, Expr op2, LoopStmt loop |
+ maybeANonCryptogrphicHash(callable, v, op1, op2, loop) and
+ callable.getAParameter() = param and
+ (
+ param.getAnAccess() = op1.(Operation).getAnOperand().getAChild*()
+ or
+ param.getAnAccess() = op2.(Operation).getAnOperand().getAChild*()
+ or
+ exists(AnyDataFlow config, Node source, Node sink |
+ (
+ sink.asExpr() = op1.(Operation).getAChild*() or
+ sink.asExpr() = op2.(Operation).getAChild*()
+ ) and
+ source.asExpr() = param.getAnAccess() and
+ config.hasFlow(source, sink)
+ )
+ )
+ )
}
From 3be229f097d1d41bf4154a09ecaecc48da4b80f7 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 11:09:30 +0100
Subject: [PATCH 105/429] C#: Separate visitors to dedicated files, rename and
reorganize comment extraction related classes
---
.../Semmle.Extraction.CSharp/Analyser.cs | 4 +-
.../Entities/CommentLine.cs | 93 +------------
.../Populators/Comments.cs | 33 -----
.../Populators/CompilationUnitVisitor.cs | 51 +++++++
.../Populators/TriviaPopulator.cs | 127 ++++++++++++++++++
...ilationUnit.cs => TypeContainerVisitor.cs} | 68 ----------
.../Populators/TypeOrNamespaceVisitor.cs | 24 ++++
7 files changed, 205 insertions(+), 195 deletions(-)
delete mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs
rename csharp/extractor/Semmle.Extraction.CSharp/Populators/{CompilationUnit.cs => TypeContainerVisitor.cs} (56%)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
index 1222403515f..b4076956c4f 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
@@ -374,9 +374,9 @@ namespace Semmle.Extraction.CSharp
if (!upToDate)
{
var cx = extractor.CreateContext(compilation.Clone(), trapWriter, new SourceScope(tree), AddAssemblyTrapPrefix);
- Populators.CompilationUnit.Extract(cx, tree.GetRoot());
+ CompilationUnitVisitor.Extract(cx, tree.GetRoot());
cx.PopulateAll();
- cx.ExtractComments(cx.CommentGenerator);
+ TriviaPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
index ba1770f20e5..f6348211695 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/CommentLine.cs
@@ -21,97 +21,6 @@ namespace Semmle.Extraction.CSharp.Entities
public string Text { get { return symbol.Item2; } }
public string RawText { get; private set; }
- public static void Extract(Context cx, SyntaxTrivia trivia)
- {
- switch (trivia.Kind())
- {
- case SyntaxKind.SingleLineDocumentationCommentTrivia:
- /*
- This is actually a multi-line comment consisting of /// lines.
- So split it up.
- */
-
- var text = trivia.ToFullString();
-
- var split = text.Split('\n');
- var currentLocation = trivia.GetLocation().SourceSpan.Start - 3;
-
- for (var line = 0; line < split.Length - 1; ++line)
- {
- var fullLine = split[line];
- var nextLineLocation = currentLocation + fullLine.Length + 1;
- fullLine = fullLine.TrimEnd('\r');
- var trimmedLine = fullLine;
-
- var leadingSpaces = trimmedLine.IndexOf('/');
- if (leadingSpaces != -1)
- {
- fullLine = fullLine.Substring(leadingSpaces);
- currentLocation += leadingSpaces;
- trimmedLine = trimmedLine.Substring(leadingSpaces + 3); // Remove leading spaces and the "///"
- trimmedLine = trimmedLine.Trim();
-
- var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
- var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
- var commentType = CommentLineType.XmlDoc;
- cx.CommentGenerator.AddComment(Create(cx, location, commentType, trimmedLine, fullLine));
- }
- else
- {
- cx.ModelError("Unexpected comment format");
- }
- currentLocation = nextLineLocation;
- }
- break;
-
- case SyntaxKind.SingleLineCommentTrivia:
- {
- var contents = trivia.ToString().Substring(2);
- var commentType = CommentLineType.Singleline;
- if (contents.Length > 0 && contents[0] == '/')
- {
- commentType = CommentLineType.XmlDoc;
- contents = contents.Substring(1); // An XML comment.
- }
- cx.CommentGenerator.AddComment(Create(cx, trivia.GetLocation(), commentType, contents.Trim(), trivia.ToFullString()));
- }
- break;
- case SyntaxKind.MultiLineDocumentationCommentTrivia:
- case SyntaxKind.MultiLineCommentTrivia:
- /* We receive a single SyntaxTrivia for a multiline block spanning several lines.
- So we split it into separate lines
- */
- text = trivia.ToFullString();
-
- split = text.Split('\n');
- currentLocation = trivia.GetLocation().SourceSpan.Start;
-
- for (var line = 0; line < split.Length; ++line)
- {
- var fullLine = split[line];
- var nextLineLocation = currentLocation + fullLine.Length + 1;
- fullLine = fullLine.TrimEnd('\r');
- var trimmedLine = fullLine;
- if (line == 0)
- trimmedLine = trimmedLine.Substring(2);
- if (line == split.Length - 1)
- trimmedLine = trimmedLine.Substring(0, trimmedLine.Length - 2);
- trimmedLine = trimmedLine.Trim();
-
- var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
- var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
- var commentType = line == 0 ? CommentLineType.Multiline : CommentLineType.MultilineContinuation;
- cx.CommentGenerator.AddComment(Create(cx, location, commentType, trimmedLine, fullLine));
- currentLocation = nextLineLocation;
- }
- break;
- // Strangely, these are reported as SingleLineCommentTrivia.
- case SyntaxKind.DocumentationCommentExteriorTrivia:
- cx.ModelError($"Unhandled comment type {trivia.Kind()} for {trivia}");
- break;
- }
- }
-
private Extraction.Entities.Location location;
public override void Populate(TextWriter trapFile)
@@ -131,7 +40,7 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.Write(";commentline");
}
- private static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
+ internal static CommentLine Create(Context cx, Microsoft.CodeAnalysis.Location loc, CommentLineType type, string text, string raw)
{
var init = (loc, type, text, raw);
return CommentLineFactory.Instance.CreateEntity(cx, init, init);
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs
deleted file mode 100644
index 8f6bb6f5206..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Comments.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using Semmle.Extraction.CommentProcessing;
-using System;
-
-namespace Semmle.Extraction.CSharp.Populators
-{
- ///
- /// Populators for comments.
- ///
- public static class Comments
- {
- public static void ExtractComments(this Context cx, ICommentGenerator gen)
- {
- cx.Try(null, null, () =>
- {
- gen.GenerateBindings((entity, duplicationGuardKey, block, binding) =>
- {
- var commentBlock = Entities.CommentBlock.Create(cx, block);
- Action a = () =>
- {
- commentBlock.BindTo(entity, binding);
- };
- // When the duplication guard key exists, it means that the entity is guarded against
- // trap duplication ().
- // We must therefore also guard comment construction.
- if (duplicationGuardKey != null)
- cx.WithDuplicationGuard(duplicationGuardKey, a);
- else
- a();
- });
- });
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
new file mode 100644
index 00000000000..a98816fc23e
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
@@ -0,0 +1,51 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Util.Logging;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
+ {
+ private CompilationUnitVisitor(Context cx)
+ : base(cx, cx.TrapWriter.Writer, null) { }
+
+ public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
+ {
+ // This information is not yet extracted.
+ cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Extraction.Entities.Location.Create(cx, node.GetLocation()), "", Severity.Info);
+ }
+
+ public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
+ {
+ foreach (var m in compilationUnit.ChildNodes())
+ {
+ cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
+ }
+
+ // Gather comments:
+ foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
+ {
+ TriviaPopulator.ExtractTrivia(cx, trivia);
+ }
+
+ foreach (var trivia in compilationUnit.GetLeadingTrivia())
+ {
+ TriviaPopulator.ExtractTrivia(cx, trivia);
+ }
+
+ foreach (var trivia in compilationUnit.GetTrailingTrivia())
+ {
+ TriviaPopulator.ExtractTrivia(cx, trivia);
+ }
+ }
+
+ public static void Extract(Context cx, SyntaxNode unit)
+ {
+ // Ensure that the file itself is populated in case the source file is totally empty
+ Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);
+
+ ((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs
new file mode 100644
index 00000000000..8aa99960a5b
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs
@@ -0,0 +1,127 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Semmle.Extraction.CommentProcessing;
+using Semmle.Extraction.CSharp.Entities;
+using System;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ ///
+ /// Populators for trivias.
+ ///
+ public static class TriviaPopulator
+ {
+ public static void ExtractCommentBlocks(Context cx, ICommentGenerator gen)
+ {
+ cx.Try(null, null, () =>
+ {
+ gen.GenerateBindings((entity, duplicationGuardKey, block, binding) =>
+ {
+ var commentBlock = Entities.CommentBlock.Create(cx, block);
+ Action a = () =>
+ {
+ commentBlock.BindTo(entity, binding);
+ };
+ // When the duplication guard key exists, it means that the entity is guarded against
+ // trap duplication ().
+ // We must therefore also guard comment construction.
+ if (duplicationGuardKey != null)
+ cx.WithDuplicationGuard(duplicationGuardKey, a);
+ else
+ a();
+ });
+ });
+ }
+
+ public static void ExtractTrivia(Context cx, SyntaxTrivia trivia)
+ {
+ switch (trivia.Kind())
+ {
+ case SyntaxKind.SingleLineDocumentationCommentTrivia:
+ /*
+ This is actually a multi-line comment consisting of /// lines.
+ So split it up.
+ */
+
+ var text = trivia.ToFullString();
+
+ var split = text.Split('\n');
+ var currentLocation = trivia.GetLocation().SourceSpan.Start - 3;
+
+ for (var line = 0; line < split.Length - 1; ++line)
+ {
+ var fullLine = split[line];
+ var nextLineLocation = currentLocation + fullLine.Length + 1;
+ fullLine = fullLine.TrimEnd('\r');
+ var trimmedLine = fullLine;
+
+ var leadingSpaces = trimmedLine.IndexOf('/');
+ if (leadingSpaces != -1)
+ {
+ fullLine = fullLine.Substring(leadingSpaces);
+ currentLocation += leadingSpaces;
+ trimmedLine = trimmedLine.Substring(leadingSpaces + 3); // Remove leading spaces and the "///"
+ trimmedLine = trimmedLine.Trim();
+
+ var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
+ var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
+ var commentType = CommentLineType.XmlDoc;
+ cx.CommentGenerator.AddComment(CommentLine.Create(cx, location, commentType, trimmedLine, fullLine));
+ }
+ else
+ {
+ cx.ModelError("Unexpected comment format");
+ }
+ currentLocation = nextLineLocation;
+ }
+ break;
+
+ case SyntaxKind.SingleLineCommentTrivia:
+ {
+ var contents = trivia.ToString().Substring(2);
+ var commentType = CommentLineType.Singleline;
+ if (contents.Length > 0 && contents[0] == '/')
+ {
+ commentType = CommentLineType.XmlDoc;
+ contents = contents.Substring(1); // An XML comment.
+ }
+ cx.CommentGenerator.AddComment(CommentLine.Create(cx, trivia.GetLocation(), commentType, contents.Trim(), trivia.ToFullString()));
+ }
+ break;
+ case SyntaxKind.MultiLineDocumentationCommentTrivia:
+ case SyntaxKind.MultiLineCommentTrivia:
+ /* We receive a single SyntaxTrivia for a multiline block spanning several lines.
+ So we split it into separate lines
+ */
+ text = trivia.ToFullString();
+
+ split = text.Split('\n');
+ currentLocation = trivia.GetLocation().SourceSpan.Start;
+
+ for (var line = 0; line < split.Length; ++line)
+ {
+ var fullLine = split[line];
+ var nextLineLocation = currentLocation + fullLine.Length + 1;
+ fullLine = fullLine.TrimEnd('\r');
+ var trimmedLine = fullLine;
+ if (line == 0)
+ trimmedLine = trimmedLine.Substring(2);
+ if (line == split.Length - 1)
+ trimmedLine = trimmedLine.Substring(0, trimmedLine.Length - 2);
+ trimmedLine = trimmedLine.Trim();
+
+ var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(currentLocation, currentLocation + fullLine.Length);
+ var location = Microsoft.CodeAnalysis.Location.Create(trivia.SyntaxTree, span);
+ var commentType = line == 0 ? CommentLineType.Multiline : CommentLineType.MultilineContinuation;
+ cx.CommentGenerator.AddComment(CommentLine.Create(cx, location, commentType, trimmedLine, fullLine));
+ currentLocation = nextLineLocation;
+ }
+ break;
+ // Strangely, these are reported as SingleLineCommentTrivia.
+ case SyntaxKind.DocumentationCommentExteriorTrivia:
+ cx.ModelError($"Unhandled comment type {trivia.Kind()} for {trivia}");
+ break;
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnit.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeContainerVisitor.cs
similarity index 56%
rename from csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnit.cs
rename to csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeContainerVisitor.cs
index 69814bf197a..a45ede39501 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnit.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeContainerVisitor.cs
@@ -1,10 +1,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.CSharp.Entities;
using Semmle.Extraction.Entities;
-using Semmle.Util;
-using Semmle.Util.Logging;
using System;
using System.Collections.Generic;
using System.IO;
@@ -82,69 +79,4 @@ namespace Semmle.Extraction.CSharp.Populators
}
}
}
-
- internal class TypeOrNamespaceVisitor : TypeContainerVisitor
- {
- public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
- : base(cx, trapFile, parent) { }
-
- public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
- {
- // Only deal with "using namespace" not "using X = Y"
- if (usingDirective.Alias == null)
- new UsingDirective(cx, usingDirective, (NamespaceDeclaration)parent);
- }
-
- public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
- {
- NamespaceDeclaration.Create(cx, node, (NamespaceDeclaration)parent);
- }
- }
-
- internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
- {
- public CompilationUnitVisitor(Context cx)
- : base(cx, cx.TrapWriter.Writer, null) { }
-
- public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
- {
- // This information is not yet extracted.
- cx.ExtractionError("Not implemented extern alias directive", node.ToFullString(), Extraction.Entities.Location.Create(cx, node.GetLocation()), "", Severity.Info);
- }
-
- public override void VisitCompilationUnit(CompilationUnitSyntax compilationUnit)
- {
- foreach (var m in compilationUnit.ChildNodes())
- {
- cx.Try(m, null, () => ((CSharpSyntaxNode)m).Accept(this));
- }
-
- // Gather comments:
- foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
- {
- CommentLine.Extract(cx, trivia);
- }
-
- foreach (var trivia in compilationUnit.GetLeadingTrivia())
- {
- CommentLine.Extract(cx, trivia);
- }
-
- foreach (var trivia in compilationUnit.GetTrailingTrivia())
- {
- CommentLine.Extract(cx, trivia);
- }
- }
- }
-
- public class CompilationUnit
- {
- public static void Extract(Context cx, SyntaxNode unit)
- {
- // Ensure that the file itself is populated in case the source file is totally empty
- Semmle.Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);
-
- ((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
- }
- }
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs
new file mode 100644
index 00000000000..aacdc5d2a4f
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/TypeOrNamespaceVisitor.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.CSharp.Entities;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class TypeOrNamespaceVisitor : TypeContainerVisitor
+ {
+ public TypeOrNamespaceVisitor(Context cx, TextWriter trapFile, IEntity parent)
+ : base(cx, trapFile, parent) { }
+
+ public override void VisitUsingDirective(UsingDirectiveSyntax usingDirective)
+ {
+ // Only deal with "using namespace" not "using X = Y"
+ if (usingDirective.Alias == null)
+ new UsingDirective(cx, usingDirective, (NamespaceDeclaration)parent);
+ }
+
+ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node)
+ {
+ NamespaceDeclaration.Create(cx, node, (NamespaceDeclaration)parent);
+ }
+ }
+}
From c3ef6841d093356a78c715c75d753f7496d376e3 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 11:13:21 +0100
Subject: [PATCH 106/429] Add tests for trivia types
---
.../comments/BindingAfter.expected | 1 +
.../library-tests/comments/Comments.expected | 3 +
.../library-tests/comments/Orphans.expected | 1 +
.../ql/test/library-tests/comments/trivia.cs | 78 +++++++++++++++++++
4 files changed, 83 insertions(+)
create mode 100644 csharp/ql/test/library-tests/comments/trivia.cs
diff --git a/csharp/ql/test/library-tests/comments/BindingAfter.expected b/csharp/ql/test/library-tests/comments/BindingAfter.expected
index 44c01d79239..6838f406708 100644
--- a/csharp/ql/test/library-tests/comments/BindingAfter.expected
+++ b/csharp/ql/test/library-tests/comments/BindingAfter.expected
@@ -55,3 +55,4 @@
| comments2.cs:118:5:118:21 | // ... | comments2.cs:119:11:119:25 | GenericClass<> | GenericClass<> |
| comments2.cs:124:5:124:16 | // ... | comments2.cs:125:9:125:20 | GenericFn | GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:128:9:128:17 | return ...; | x |
+| trivia.cs:1:1:3:15 | // ... | trivia.cs:14:7:14:9 | Tr1 | semmle-extractor-options: --standalone |
diff --git a/csharp/ql/test/library-tests/comments/Comments.expected b/csharp/ql/test/library-tests/comments/Comments.expected
index 388687e7857..80208664157 100644
--- a/csharp/ql/test/library-tests/comments/Comments.expected
+++ b/csharp/ql/test/library-tests/comments/Comments.expected
@@ -70,6 +70,9 @@ singlelineComment
| comments2.cs:124:5:124:16 | // ... | comments2.cs:124:5:124:16 | // ... | 1 | GenericFn | // GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:127:20:127:23 | // ... | 1 | x | // x |
| comments2.cs:132:1:132:21 | // ... | comments2.cs:132:1:132:21 | // ... | 1 | End of comment2.cs | // End of comment2.cs |
+| trivia.cs:1:1:3:15 | // ... | trivia.cs:1:1:1:42 | // ... | 3 | semmle-extractor-options: --standalone | // semmle-extractor-options: --standalone |
+| trivia.cs:1:1:3:15 | // ... | trivia.cs:2:1:2:21 | // ... | 3 | Start of trivia.cs | // Start of trivia.cs |
+| trivia.cs:1:1:3:15 | // ... | trivia.cs:3:1:3:15 | // ... | 3 | Unassociated | // Unassociated |
multilineComment
| comments1.cs:11:1:11:25 | /* ... */ | comments1.cs:11:1:11:25 | /* ... */ | 1 | A multiline comment | /* A multiline comment */ |
| comments1.cs:25:1:25:43 | /* ... */ | comments1.cs:25:1:25:15 | /* ... */ | 2 | This is a | /* This is a */ |
diff --git a/csharp/ql/test/library-tests/comments/Orphans.expected b/csharp/ql/test/library-tests/comments/Orphans.expected
index e57bc81990e..56e5af20cd2 100644
--- a/csharp/ql/test/library-tests/comments/Orphans.expected
+++ b/csharp/ql/test/library-tests/comments/Orphans.expected
@@ -14,3 +14,4 @@
| comments2.cs:5:27:5:41 | // ... |
| comments2.cs:8:1:8:15 | // ... |
| comments2.cs:132:1:132:21 | // ... |
+| trivia.cs:1:1:3:15 | // ... |
diff --git a/csharp/ql/test/library-tests/comments/trivia.cs b/csharp/ql/test/library-tests/comments/trivia.cs
new file mode 100644
index 00000000000..e22ddf77a63
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/trivia.cs
@@ -0,0 +1,78 @@
+// semmle-extractor-options: --standalone
+// Start of trivia.cs
+// Unassociated
+#define DEBUG
+
+#undef DEBUG
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+#pragma warning disable 414, CS3021
+#pragma checksum "file.cs" "{406EA660-64CF-4C82-B6F0-42D48172A799}" "ab007f1d23d9" // New checksum
+class Tr1
+{
+ static void M1()
+ {
+#line 200 "Special"
+ int i;
+ int j;
+#line default
+ char c;
+#pragma warning restore
+ float f;
+#line hidden // numbering not affected
+ string s;
+#line default
+ double d;
+ }
+}
+
+class Tr2
+{
+ static void M1()
+ {
+ #region fields
+ int i;
+ #region nested
+ int j;
+ #endregion
+ #endregion
+ }
+}
+
+class Tr3
+{
+ static void M1()
+ {
+#nullable disable// Sets the nullable annotation and warning contexts to disabled.
+#nullable enable// Sets the nullable annotation and warning contexts to enabled.
+#nullable restore// Restores the nullable annotation and warning contexts to project settings.
+#nullable disable annotations// Sets the nullable annotation context to disabled.
+#nullable enable annotations// Sets the nullable annotation context to enabled.
+#nullable restore annotations// Restores the nullable annotation context to project settings.
+#nullable disable warnings// Sets the nullable warning context to disabled.
+#nullable enable warnings// Sets the nullable warning context to enabled.
+#nullable restore warnings// Restores the nullable warning context to project settings.
+ }
+}
+
+class Tr4
+{
+ static void M1()
+ {
+#if DEBUG
+#warning DEBUG is defined
+ var i = 0;
+#if NESTED
+ i--;
+#endif
+#elif (NOTDEBUG == true) || !(TEST)
+#error NOTDEBUG is defined or TEST is not
+ var i = 1;
+#else
+ var i = 2;
+#endif
+ }
+}
\ No newline at end of file
From 046a37b8342fe45dd9376358a8ba89ce60fcb503 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 12:23:06 +0100
Subject: [PATCH 107/429] Simplify element access extraction
---
.../Entities/Expressions/ElementAccess.cs | 8 +--
.../Populators/Ast.cs | 63 -------------------
.../arguments/argumentType.expected | 12 ++++
3 files changed, 14 insertions(+), 69 deletions(-)
delete mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ElementAccess.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ElementAccess.cs
index afc4cd2cd93..59e573984fe 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ElementAccess.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/ElementAccess.cs
@@ -30,12 +30,8 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
}
else
{
- var child = -1;
- Create(cx, qualifier, this, child++);
- foreach (var a in argumentList.Arguments)
- {
- cx.Extract(a, this, child++);
- }
+ Create(cx, qualifier, this, -1);
+ PopulateArguments(trapFile, argumentList, 0);
var symbolInfo = cx.GetSymbolInfo(base.Syntax);
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs
deleted file mode 100644
index 866d436bda1..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/Ast.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.CSharp.Entities;
-
-namespace Semmle.Extraction.CSharp.Populators
-{
- internal class Ast : CSharpSyntaxVisitor
- {
- private readonly Context cx;
- private readonly IExpressionParentEntity parent;
- private readonly int child;
-
- public Ast(Context cx, IExpressionParentEntity parent, int child)
- {
- this.cx = cx;
- this.parent = parent;
- this.child = child;
- }
-
- public override void DefaultVisit(SyntaxNode node)
- {
- cx.ModelError(node, $"Unhandled syntax node {node.Kind()}");
- }
-
- public override void VisitArgumentList(ArgumentListSyntax node)
- {
- var c = 0;
- foreach (var m in node.Arguments)
- {
- cx.Extract(m, parent, c++);
- }
- }
-
- public override void VisitArgument(ArgumentSyntax node)
- {
- Expression.Create(cx, node.Expression, parent, child);
- }
- }
-
- public static class AstExtensions
- {
- public static void Extract(this Context cx, CSharpSyntaxNode node, IExpressionParentEntity parent, int child)
- {
- using (cx.StackGuard)
- {
- try
- {
- node.Accept(new Ast(cx, parent, child));
- }
- catch (System.Exception ex) // lgtm[cs/catch-of-all-exceptions]
- {
- cx.ModelError(node, $"Exception processing syntax node of type {node.Kind()}: {ex.Message}");
- }
- }
- }
-
- public static void Extract(this Context cx, SyntaxNode node, IEntity parent, int child)
- {
- cx.Extract(((CSharpSyntaxNode)node), parent, child);
- }
- }
-}
diff --git a/csharp/ql/test/library-tests/arguments/argumentType.expected b/csharp/ql/test/library-tests/arguments/argumentType.expected
index 39697e63729..e2cd7fc4a51 100644
--- a/csharp/ql/test/library-tests/arguments/argumentType.expected
+++ b/csharp/ql/test/library-tests/arguments/argumentType.expected
@@ -22,3 +22,15 @@
| arguments.cs:40:41:40:41 | 0 | 0 |
| arguments.cs:45:12:45:32 | array creation of type Object[] | 0 |
| arguments.cs:45:35:45:38 | null | 0 |
+| arguments.cs:55:21:55:21 | 1 | 0 |
+| arguments.cs:55:24:55:24 | 2 | 0 |
+| arguments.cs:56:21:56:21 | 3 | 0 |
+| arguments.cs:56:24:56:24 | 4 | 0 |
+| arguments.cs:59:14:59:14 | 8 | 0 |
+| arguments.cs:59:17:59:17 | 9 | 0 |
+| arguments.cs:60:14:60:15 | 10 | 0 |
+| arguments.cs:60:14:60:15 | 10 | 0 |
+| arguments.cs:60:18:60:19 | 11 | 0 |
+| arguments.cs:60:18:60:19 | 11 | 0 |
+| arguments.cs:62:21:62:22 | 15 | 0 |
+| arguments.cs:62:25:62:26 | 16 | 0 |
From 48d24b2264ab65d5877174251b3430d85ff5162d Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 13:40:33 +0100
Subject: [PATCH 108/429] Get line comments from trivia lines
---
.../Populators/CompilationUnitVisitor.cs | 2 +-
.../test/library-tests/comments/BindingAfter.expected | 2 ++
.../library-tests/comments/BindingBefore.expected | 1 +
.../library-tests/comments/BindingParent.expected | 10 ++++++++++
.../ql/test/library-tests/comments/Bindings.expected | 11 +++++++++++
.../ql/test/library-tests/comments/Comments.expected | 11 +++++++++++
6 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
index a98816fc23e..918366de186 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
@@ -24,7 +24,7 @@ namespace Semmle.Extraction.CSharp.Populators
}
// Gather comments:
- foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span))
+ foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span, descendIntoTrivia: true))
{
TriviaPopulator.ExtractTrivia(cx, trivia);
}
diff --git a/csharp/ql/test/library-tests/comments/BindingAfter.expected b/csharp/ql/test/library-tests/comments/BindingAfter.expected
index 6838f406708..a44d0360f73 100644
--- a/csharp/ql/test/library-tests/comments/BindingAfter.expected
+++ b/csharp/ql/test/library-tests/comments/BindingAfter.expected
@@ -56,3 +56,5 @@
| comments2.cs:124:5:124:16 | // ... | comments2.cs:125:9:125:20 | GenericFn | GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:128:9:128:17 | return ...; | x |
| trivia.cs:1:1:3:15 | // ... | trivia.cs:14:7:14:9 | Tr1 | semmle-extractor-options: --standalone |
+| trivia.cs:13:84:13:98 | // ... | trivia.cs:14:7:14:9 | Tr1 | New checksum |
+| trivia.cs:25:14:25:38 | // ... | trivia.cs:26:9:26:17 | ... ...; | numbering not affected |
diff --git a/csharp/ql/test/library-tests/comments/BindingBefore.expected b/csharp/ql/test/library-tests/comments/BindingBefore.expected
index 4d41ea94864..9cb36b9e7cc 100644
--- a/csharp/ql/test/library-tests/comments/BindingBefore.expected
+++ b/csharp/ql/test/library-tests/comments/BindingBefore.expected
@@ -48,3 +48,4 @@
| comments2.cs:124:5:124:16 | // ... | comments2.cs:119:11:119:25 | GenericClass<> | GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:127:9:127:18 | ... ...; | x |
| comments2.cs:132:1:132:21 | // ... | comments2.cs:11:7:11:8 | C2 | End of comment2.cs |
+| trivia.cs:25:14:25:38 | // ... | trivia.cs:24:9:24:16 | ... ...; | numbering not affected |
diff --git a/csharp/ql/test/library-tests/comments/BindingParent.expected b/csharp/ql/test/library-tests/comments/BindingParent.expected
index de8fed3666b..d9c8f24d48d 100644
--- a/csharp/ql/test/library-tests/comments/BindingParent.expected
+++ b/csharp/ql/test/library-tests/comments/BindingParent.expected
@@ -47,3 +47,13 @@
| comments2.cs:121:17:121:20 | // ... | comments2.cs:119:11:119:25 | GenericClass<> | f |
| comments2.cs:124:5:124:16 | // ... | comments2.cs:11:7:11:8 | C2 | GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:126:5:129:5 | {...} | x |
+| trivia.cs:25:14:25:38 | // ... | trivia.cs:17:5:29:5 | {...} | numbering not affected |
+| trivia.cs:49:18:49:82 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation and warning contexts to disabled. |
+| trivia.cs:50:17:50:80 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation and warning contexts to enabled. |
+| trivia.cs:51:18:51:94 | // ... | trivia.cs:48:5:58:5 | {...} | Restores the nullable annotation and warning contexts to project settings. |
+| trivia.cs:52:30:52:81 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation context to disabled. |
+| trivia.cs:53:29:53:79 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation context to enabled. |
+| trivia.cs:54:30:54:93 | // ... | trivia.cs:48:5:58:5 | {...} | Restores the nullable annotation context to project settings. |
+| trivia.cs:55:27:55:75 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable warning context to disabled. |
+| trivia.cs:56:26:56:73 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable warning context to enabled. |
+| trivia.cs:57:27:57:87 | // ... | trivia.cs:48:5:58:5 | {...} | Restores the nullable warning context to project settings. |
diff --git a/csharp/ql/test/library-tests/comments/Bindings.expected b/csharp/ql/test/library-tests/comments/Bindings.expected
index b241ed56b55..90889ddeda0 100644
--- a/csharp/ql/test/library-tests/comments/Bindings.expected
+++ b/csharp/ql/test/library-tests/comments/Bindings.expected
@@ -49,3 +49,14 @@
| comments2.cs:121:17:121:20 | // ... | comments2.cs:121:13:121:13 | f | f |
| comments2.cs:124:5:124:16 | // ... | comments2.cs:125:9:125:20 | GenericFn | GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:127:9:127:18 | ... ...; | x |
+| trivia.cs:13:84:13:98 | // ... | trivia.cs:14:7:14:9 | Tr1 | New checksum |
+| trivia.cs:25:14:25:38 | // ... | trivia.cs:17:5:29:5 | {...} | numbering not affected |
+| trivia.cs:49:18:49:82 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation and warning contexts to disabled. |
+| trivia.cs:50:17:50:80 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation and warning contexts to enabled. |
+| trivia.cs:51:18:51:94 | // ... | trivia.cs:48:5:58:5 | {...} | Restores the nullable annotation and warning contexts to project settings. |
+| trivia.cs:52:30:52:81 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation context to disabled. |
+| trivia.cs:53:29:53:79 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable annotation context to enabled. |
+| trivia.cs:54:30:54:93 | // ... | trivia.cs:48:5:58:5 | {...} | Restores the nullable annotation context to project settings. |
+| trivia.cs:55:27:55:75 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable warning context to disabled. |
+| trivia.cs:56:26:56:73 | // ... | trivia.cs:48:5:58:5 | {...} | Sets the nullable warning context to enabled. |
+| trivia.cs:57:27:57:87 | // ... | trivia.cs:48:5:58:5 | {...} | Restores the nullable warning context to project settings. |
diff --git a/csharp/ql/test/library-tests/comments/Comments.expected b/csharp/ql/test/library-tests/comments/Comments.expected
index 80208664157..176211192a6 100644
--- a/csharp/ql/test/library-tests/comments/Comments.expected
+++ b/csharp/ql/test/library-tests/comments/Comments.expected
@@ -73,6 +73,17 @@ singlelineComment
| trivia.cs:1:1:3:15 | // ... | trivia.cs:1:1:1:42 | // ... | 3 | semmle-extractor-options: --standalone | // semmle-extractor-options: --standalone |
| trivia.cs:1:1:3:15 | // ... | trivia.cs:2:1:2:21 | // ... | 3 | Start of trivia.cs | // Start of trivia.cs |
| trivia.cs:1:1:3:15 | // ... | trivia.cs:3:1:3:15 | // ... | 3 | Unassociated | // Unassociated |
+| trivia.cs:13:84:13:98 | // ... | trivia.cs:13:84:13:98 | // ... | 1 | New checksum | // New checksum |
+| trivia.cs:25:14:25:38 | // ... | trivia.cs:25:14:25:38 | // ... | 1 | numbering not affected | // numbering not affected |
+| trivia.cs:49:18:49:82 | // ... | trivia.cs:49:18:49:82 | // ... | 1 | Sets the nullable annotation and warning contexts to disabled. | // Sets the nullable annotation and warning contexts to disabled. |
+| trivia.cs:50:17:50:80 | // ... | trivia.cs:50:17:50:80 | // ... | 1 | Sets the nullable annotation and warning contexts to enabled. | // Sets the nullable annotation and warning contexts to enabled. |
+| trivia.cs:51:18:51:94 | // ... | trivia.cs:51:18:51:94 | // ... | 1 | Restores the nullable annotation and warning contexts to project settings. | // Restores the nullable annotation and warning contexts to project settings. |
+| trivia.cs:52:30:52:81 | // ... | trivia.cs:52:30:52:81 | // ... | 1 | Sets the nullable annotation context to disabled. | // Sets the nullable annotation context to disabled. |
+| trivia.cs:53:29:53:79 | // ... | trivia.cs:53:29:53:79 | // ... | 1 | Sets the nullable annotation context to enabled. | // Sets the nullable annotation context to enabled. |
+| trivia.cs:54:30:54:93 | // ... | trivia.cs:54:30:54:93 | // ... | 1 | Restores the nullable annotation context to project settings. | // Restores the nullable annotation context to project settings. |
+| trivia.cs:55:27:55:75 | // ... | trivia.cs:55:27:55:75 | // ... | 1 | Sets the nullable warning context to disabled. | // Sets the nullable warning context to disabled. |
+| trivia.cs:56:26:56:73 | // ... | trivia.cs:56:26:56:73 | // ... | 1 | Sets the nullable warning context to enabled. | // Sets the nullable warning context to enabled. |
+| trivia.cs:57:27:57:87 | // ... | trivia.cs:57:27:57:87 | // ... | 1 | Restores the nullable warning context to project settings. | // Restores the nullable warning context to project settings. |
multilineComment
| comments1.cs:11:1:11:25 | /* ... */ | comments1.cs:11:1:11:25 | /* ... */ | 1 | A multiline comment | /* A multiline comment */ |
| comments1.cs:25:1:25:43 | /* ... */ | comments1.cs:25:1:25:15 | /* ... */ | 2 | This is a | /* This is a */ |
From 40186db768584015b372fa37fe92ad0984272d1f Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 15:51:38 +0100
Subject: [PATCH 109/429] Rename CommentPopulator
---
.../Semmle.Extraction.CSharp/Analyser.cs | 9 +++++++--
.../{TriviaPopulator.cs => CommentPopulator.cs} | 6 +++---
.../Populators/CompilationUnitVisitor.cs | 16 ++++------------
3 files changed, 14 insertions(+), 17 deletions(-)
rename csharp/extractor/Semmle.Extraction.CSharp/Populators/{TriviaPopulator.cs => CommentPopulator.cs} (97%)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
index b4076956c4f..fb03a6eee92 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
@@ -374,9 +374,14 @@ namespace Semmle.Extraction.CSharp
if (!upToDate)
{
var cx = extractor.CreateContext(compilation.Clone(), trapWriter, new SourceScope(tree), AddAssemblyTrapPrefix);
- CompilationUnitVisitor.Extract(cx, tree.GetRoot());
+ // Ensure that the file itself is populated in case the source file is totally empty
+ var root = tree.GetRoot();
+ Extraction.Entities.File.Create(cx, root.SyntaxTree.FilePath);
+
+ var csNode = (CSharpSyntaxNode)root;
+ csNode.Accept(new CompilationUnitVisitor(cx));
cx.PopulateAll();
- TriviaPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
+ CommentPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CommentPopulator.cs
similarity index 97%
rename from csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs
rename to csharp/extractor/Semmle.Extraction.CSharp/Populators/CommentPopulator.cs
index 8aa99960a5b..0574a11db5f 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/TriviaPopulator.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CommentPopulator.cs
@@ -7,9 +7,9 @@ using System;
namespace Semmle.Extraction.CSharp.Populators
{
///
- /// Populators for trivias.
+ /// Populators for comments.
///
- public static class TriviaPopulator
+ public static class CommentPopulator
{
public static void ExtractCommentBlocks(Context cx, ICommentGenerator gen)
{
@@ -33,7 +33,7 @@ namespace Semmle.Extraction.CSharp.Populators
});
}
- public static void ExtractTrivia(Context cx, SyntaxTrivia trivia)
+ public static void ExtractComment(Context cx, SyntaxTrivia trivia)
{
switch (trivia.Kind())
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
index 918366de186..bebdee933cd 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/CompilationUnitVisitor.cs
@@ -7,7 +7,7 @@ namespace Semmle.Extraction.CSharp.Populators
{
internal class CompilationUnitVisitor : TypeOrNamespaceVisitor
{
- private CompilationUnitVisitor(Context cx)
+ public CompilationUnitVisitor(Context cx)
: base(cx, cx.TrapWriter.Writer, null) { }
public override void VisitExternAliasDirective(ExternAliasDirectiveSyntax node)
@@ -26,26 +26,18 @@ namespace Semmle.Extraction.CSharp.Populators
// Gather comments:
foreach (var trivia in compilationUnit.DescendantTrivia(compilationUnit.Span, descendIntoTrivia: true))
{
- TriviaPopulator.ExtractTrivia(cx, trivia);
+ CommentPopulator.ExtractComment(cx, trivia);
}
foreach (var trivia in compilationUnit.GetLeadingTrivia())
{
- TriviaPopulator.ExtractTrivia(cx, trivia);
+ CommentPopulator.ExtractComment(cx, trivia);
}
foreach (var trivia in compilationUnit.GetTrailingTrivia())
{
- TriviaPopulator.ExtractTrivia(cx, trivia);
+ CommentPopulator.ExtractComment(cx, trivia);
}
}
-
- public static void Extract(Context cx, SyntaxNode unit)
- {
- // Ensure that the file itself is populated in case the source file is totally empty
- Extraction.Entities.File.Create(cx, unit.SyntaxTree.FilePath);
-
- ((CSharpSyntaxNode)unit).Accept(new CompilationUnitVisitor(cx));
- }
}
}
From 8b9c6712d1ca9fceec88221eb7940e545574d7b4 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 15:52:12 +0100
Subject: [PATCH 110/429] Extract pragma warning directives
---
.../Semmle.Extraction.CSharp/Analyser.cs | 1 +
.../IPreprocessorDirective.cs | 4 ++
.../PragmaWarningDirective.cs | 43 +++++++++++++++++++
.../Populators/DirectiveVisitor.cs | 21 +++++++++
.../Semmle.Extraction.CSharp/Tuples.cs | 20 +++++++++
csharp/ql/src/csharp.qll | 1 +
.../src/semmle/code/csharp/Preprocessor.qll | 30 +++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 22 +++++++++-
.../comments/PragmaWarnings.expected | 7 +++
.../library-tests/comments/PragmaWarnings.ql | 9 ++++
10 files changed, 157 insertions(+), 1 deletion(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
create mode 100644 csharp/ql/src/semmle/code/csharp/Preprocessor.qll
create mode 100644 csharp/ql/test/library-tests/comments/PragmaWarnings.expected
create mode 100644 csharp/ql/test/library-tests/comments/PragmaWarnings.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
index fb03a6eee92..5aa4f452d92 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
@@ -380,6 +380,7 @@ namespace Semmle.Extraction.CSharp
var csNode = (CSharpSyntaxNode)root;
csNode.Accept(new CompilationUnitVisitor(cx));
+ csNode.Accept(new DirectiveVisitor(cx));
cx.PopulateAll();
CommentPopulator.ExtractCommentBlocks(cx, cx.CommentGenerator);
cx.PopulateAll();
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs
new file mode 100644
index 00000000000..89d2418258e
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs
@@ -0,0 +1,4 @@
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal interface IPreprocessorDirective { }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
new file mode 100644
index 00000000000..a96a49d76d3
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
@@ -0,0 +1,43 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.Entities;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class PragmaWarningDirective : FreshEntity, IPreprocessorDirective
+ {
+ private readonly PragmaWarningDirectiveTriviaSyntax trivia;
+
+ public PragmaWarningDirective(Context cx, PragmaWarningDirectiveTriviaSyntax trivia)
+ : base(cx)
+ {
+ this.trivia = trivia;
+ TryPopulate();
+ }
+
+ protected override void Populate(TextWriter trapFile)
+ {
+ trapFile.pragma_warnings(this, trivia.DisableOrRestoreKeyword.IsKind(SyntaxKind.DisableKeyword) ? 0 : 1);
+
+ var childIndex = 0;
+ foreach (var code in trivia.ErrorCodes)
+ {
+ trapFile.pragma_warning_error_codes(this, code.ToString(), childIndex++);
+ }
+
+ trapFile.preprocessor_directive_location(this, cx.Create(ReportingLocation));
+
+ if (!cx.Extractor.Standalone)
+ {
+ var assembly = Assembly.CreateOutputAssembly(cx);
+ trapFile.preprocessor_directive_assembly(this, assembly);
+ }
+ }
+
+ public sealed override Microsoft.CodeAnalysis.Location ReportingLocation => trivia.GetLocation();
+
+ public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
new file mode 100644
index 00000000000..a4092e54455
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -0,0 +1,21 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Semmle.Extraction.CSharp.Populators
+{
+ internal class DirectiveVisitor : CSharpSyntaxWalker
+ {
+ private readonly Context cx;
+
+ public DirectiveVisitor(Context cx) : base(SyntaxWalkerDepth.StructuredTrivia)
+ {
+ this.cx = cx;
+ }
+
+ public override void VisitPragmaWarningDirectiveTrivia(PragmaWarningDirectiveTriviaSyntax node)
+ {
+ new Entities.PragmaWarningDirective(cx, node);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 630c2848e3d..8a6e570ff53 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -590,5 +590,25 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("using_static_directives", @using, type);
}
+
+ internal static void preprocessor_directive_location(this TextWriter trapFile, IPreprocessorDirective directive, Location location)
+ {
+ trapFile.WriteTuple("preprocessor_directive_location", directive, location);
+ }
+
+ internal static void preprocessor_directive_assembly(this TextWriter trapFile, IPreprocessorDirective directive, Assembly assembly)
+ {
+ trapFile.WriteTuple("preprocessor_directive_assembly", directive, assembly);
+ }
+
+ internal static void pragma_warnings(this TextWriter trapFile, PragmaWarningDirective pragma, int kind)
+ {
+ trapFile.WriteTuple("pragma_warnings", pragma, kind);
+ }
+
+ internal static void pragma_warning_error_codes(this TextWriter trapFile, PragmaWarningDirective pragma, string errorCode, int child)
+ {
+ trapFile.WriteTuple("pragma_warning_error_codes", pragma, errorCode, child);
+ }
}
}
diff --git a/csharp/ql/src/csharp.qll b/csharp/ql/src/csharp.qll
index 2a44f6e864a..3c821c9a443 100644
--- a/csharp/ql/src/csharp.qll
+++ b/csharp/ql/src/csharp.qll
@@ -19,6 +19,7 @@ import semmle.code.csharp.Type
import semmle.code.csharp.Using
import semmle.code.csharp.Variable
import semmle.code.csharp.XML
+import semmle.code.csharp.Preprocessor
import semmle.code.csharp.exprs.Access
import semmle.code.csharp.exprs.ArithmeticOperation
import semmle.code.csharp.exprs.Assignment
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
new file mode 100644
index 00000000000..d432a0a3588
--- /dev/null
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -0,0 +1,30 @@
+/**
+ * Provides all preprocessor directive classes.
+ */
+
+import Element
+
+class PreprocessorDirective extends Element, @preprocessor_directive {
+ override Location getALocation() { preprocessor_directive_location(this, result) }
+}
+
+/**
+ * A `#pragma warning` directive.
+ */
+class PragmaWarningDirective extends PreprocessorDirective, @pragma_warning {
+ /** Holds if this is a `#pragma warning restore` directive. */
+ predicate restore() { pragma_warnings(this, 1) }
+
+ /** Holds if this is a `#pragma warning disable` directive. */
+ predicate disable() { pragma_warnings(this, 0) }
+
+ /** Holds if this directive specifies error codes. */
+ predicate hasErrorCodes() { exists(string s | pragma_warning_error_codes(this, s, _)) }
+
+ /** Gets a specified error code from this directive. */
+ string getAnErrorCode() { pragma_warning_error_codes(this, result, _) }
+
+ override string toString() { result = "#pragma warning ..." }
+
+ override string getAPrimaryQlClass() { result = "PragmaWarningDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index efcd69e086a..3812d7ce9db 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -217,7 +217,7 @@ tokens(
@element = @declaration | @stmt | @expr | @modifier | @attribute | @namespace_declaration
| @using_directive | @type_parameter_constraints | @external_element
- | @xmllocatable | @asp_element | @namespace;
+ | @xmllocatable | @asp_element | @namespace | @preprocessor_directive;
@declaration = @callable | @generic | @assignable | @namespace;
@@ -331,6 +331,26 @@ using_directive_location(
unique int id: @using_directive ref,
int loc: @location ref);
+@preprocessor_directive = @pragma_warning;
+
+pragma_warnings(
+ unique int id: @pragma_warning,
+ int kind: int ref /* 0 = disable, 1 = restore */);
+
+#keyset[id, index]
+pragma_warning_error_codes(
+ int id: @pragma_warning ref,
+ string errorCode: string ref,
+ int index: int ref);
+
+preprocessor_directive_location(
+ unique int id: @preprocessor_directive ref,
+ int loc: @location ref);
+
+preprocessor_directive_assembly(
+ unique int id: @preprocessor_directive ref,
+ int loc: @assembly ref);
+
/** TYPES **/
types(
diff --git a/csharp/ql/test/library-tests/comments/PragmaWarnings.expected b/csharp/ql/test/library-tests/comments/PragmaWarnings.expected
new file mode 100644
index 00000000000..ade1a86da1c
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/PragmaWarnings.expected
@@ -0,0 +1,7 @@
+disable
+| trivia.cs:12:1:12:35 | #pragma warning ... |
+restore
+| trivia.cs:23:1:23:23 | #pragma warning ... |
+errorCodes
+| trivia.cs:12:1:12:35 | #pragma warning ... | 414 |
+| trivia.cs:12:1:12:35 | #pragma warning ... | CS3021 |
diff --git a/csharp/ql/test/library-tests/comments/PragmaWarnings.ql b/csharp/ql/test/library-tests/comments/PragmaWarnings.ql
new file mode 100644
index 00000000000..15d793b6392
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/PragmaWarnings.ql
@@ -0,0 +1,9 @@
+import csharp
+
+query predicate disable(PragmaWarningDirective pragma) { pragma.disable() }
+
+query predicate restore(PragmaWarningDirective pragma) { pragma.restore() }
+
+query predicate errorCodes(PragmaWarningDirective pragma, string code) {
+ pragma.hasErrorCodes() and code = pragma.getAnErrorCode()
+}
From 94bf3467b72aa14cc6ca39ad99c3ba92a6c1cf1e Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 16:59:14 +0100
Subject: [PATCH 111/429] Extract pragma checksum directives
---
.../IPreprocessorDirective.cs | 4 --
.../PragmaChecksumDirective.cs | 18 +++++++++
.../PragmaWarningDirective.cs | 23 ++----------
.../PreprocessorDirective.cs | 37 +++++++++++++++++++
.../Populators/DirectiveVisitor.cs | 5 +++
.../Semmle.Extraction.CSharp/Tuples.cs | 14 ++++++-
.../src/semmle/code/csharp/Preprocessor.qll | 18 +++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 8 +++-
.../comments/PragmaChecksums.expected | 1 +
.../library-tests/comments/PragmaChecksums.ql | 4 ++
10 files changed, 105 insertions(+), 27 deletions(-)
delete mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/PragmaChecksums.expected
create mode 100644 csharp/ql/test/library-tests/comments/PragmaChecksums.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs
deleted file mode 100644
index 89d2418258e..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IPreprocessorDirective.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Semmle.Extraction.CSharp.Entities
-{
- internal interface IPreprocessorDirective { }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
new file mode 100644
index 00000000000..9de67176e54
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaChecksumDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class PragmaChecksumDirective : PreprocessorDirective
+ {
+ public PragmaChecksumDirective(Context cx, PragmaChecksumDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.pragma_checksums(this, trivia.File.ToString(), trivia.Guid.ToString(), trivia.Bytes.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
index a96a49d76d3..4502fa4a87a 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PragmaWarningDirective.cs
@@ -1,23 +1,18 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Semmle.Extraction.Entities;
using System.IO;
namespace Semmle.Extraction.CSharp.Entities
{
- internal class PragmaWarningDirective : FreshEntity, IPreprocessorDirective
+ internal class PragmaWarningDirective : PreprocessorDirective
{
- private readonly PragmaWarningDirectiveTriviaSyntax trivia;
-
public PragmaWarningDirective(Context cx, PragmaWarningDirectiveTriviaSyntax trivia)
- : base(cx)
+ : base(cx, trivia)
{
- this.trivia = trivia;
- TryPopulate();
}
- protected override void Populate(TextWriter trapFile)
+ protected override void PopulatePreprocessor(TextWriter trapFile)
{
trapFile.pragma_warnings(this, trivia.DisableOrRestoreKeyword.IsKind(SyntaxKind.DisableKeyword) ? 0 : 1);
@@ -26,18 +21,6 @@ namespace Semmle.Extraction.CSharp.Entities
{
trapFile.pragma_warning_error_codes(this, code.ToString(), childIndex++);
}
-
- trapFile.preprocessor_directive_location(this, cx.Create(ReportingLocation));
-
- if (!cx.Extractor.Standalone)
- {
- var assembly = Assembly.CreateOutputAssembly(cx);
- trapFile.preprocessor_directive_assembly(this, assembly);
- }
}
-
- public sealed override Microsoft.CodeAnalysis.Location ReportingLocation => trivia.GetLocation();
-
- public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
new file mode 100644
index 00000000000..e19ffe43549
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
@@ -0,0 +1,37 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.Entities;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal abstract class PreprocessorDirective : FreshEntity where TDirective : DirectiveTriviaSyntax
+ {
+ protected readonly TDirective trivia;
+
+ protected PreprocessorDirective(Context cx, TDirective trivia)
+ : base(cx)
+ {
+ this.trivia = trivia;
+ TryPopulate();
+ }
+
+ protected sealed override void Populate(TextWriter trapFile)
+ {
+ PopulatePreprocessor(trapFile);
+
+ trapFile.preprocessor_directive_location(this, cx.Create(ReportingLocation));
+
+ if (!cx.Extractor.Standalone)
+ {
+ var assembly = Assembly.CreateOutputAssembly(cx);
+ trapFile.preprocessor_directive_assembly(this, assembly);
+ }
+ }
+
+ protected abstract void PopulatePreprocessor(TextWriter trapFile);
+
+ public sealed override Microsoft.CodeAnalysis.Location ReportingLocation => trivia.GetLocation();
+
+ public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.OptionalLabel;
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index a4092e54455..c1d23ca5f0b 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -17,5 +17,10 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.PragmaWarningDirective(cx, node);
}
+
+ public override void VisitPragmaChecksumDirectiveTrivia(PragmaChecksumDirectiveTriviaSyntax node)
+ {
+ new Entities.PragmaChecksumDirective(cx, node);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 8a6e570ff53..55060767806 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -1,3 +1,4 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CommentProcessing;
using Semmle.Extraction.CSharp.Entities;
using Semmle.Extraction.Entities;
@@ -591,12 +592,16 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("using_static_directives", @using, type);
}
- internal static void preprocessor_directive_location(this TextWriter trapFile, IPreprocessorDirective directive, Location location)
+ internal static void preprocessor_directive_location(this TextWriter trapFile,
+ PreprocessorDirective directive, Location location)
+ where TDirective : DirectiveTriviaSyntax
{
trapFile.WriteTuple("preprocessor_directive_location", directive, location);
}
- internal static void preprocessor_directive_assembly(this TextWriter trapFile, IPreprocessorDirective directive, Assembly assembly)
+ internal static void preprocessor_directive_assembly(this TextWriter trapFile,
+ PreprocessorDirective directive, Assembly assembly)
+ where TDirective : DirectiveTriviaSyntax
{
trapFile.WriteTuple("preprocessor_directive_assembly", directive, assembly);
}
@@ -610,5 +615,10 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("pragma_warning_error_codes", pragma, errorCode, child);
}
+
+ internal static void pragma_checksums(this TextWriter trapFile, PragmaChecksumDirective pragma, string file, string guid, string bytes)
+ {
+ trapFile.WriteTuple("pragma_checksums", pragma, file, guid, bytes);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index d432a0a3588..46e806aface 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -28,3 +28,21 @@ class PragmaWarningDirective extends PreprocessorDirective, @pragma_warning {
override string getAPrimaryQlClass() { result = "PragmaWarningDirective" }
}
+
+/**
+ * A `#pragma checksum` directive.
+ */
+class PragmaChecksumDirective extends PreprocessorDirective, @pragma_checksum {
+ /** Gets the file name of this directive. */
+ string getFileName() { pragma_checksums(this, result, _, _) }
+
+ /** Gets the GUID of this directive. */
+ string getGuid() { pragma_checksums(this, _, result, _) }
+
+ /** Gets the checksum bytes of this directive. */
+ string getBytes() { pragma_checksums(this, _, _, result) }
+
+ override string toString() { result = "#pragma checksum ..." }
+
+ override string getAPrimaryQlClass() { result = "PragmaChecksumDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index 3812d7ce9db..5530f36c85e 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -331,7 +331,13 @@ using_directive_location(
unique int id: @using_directive ref,
int loc: @location ref);
-@preprocessor_directive = @pragma_warning;
+@preprocessor_directive = @pragma_warning | @pragma_checksum;
+
+pragma_checksums(
+ unique int id: @pragma_checksum,
+ string file: string ref,
+ string guid: string ref,
+ string bytes: string ref);
pragma_warnings(
unique int id: @pragma_warning,
diff --git a/csharp/ql/test/library-tests/comments/PragmaChecksums.expected b/csharp/ql/test/library-tests/comments/PragmaChecksums.expected
new file mode 100644
index 00000000000..185e834cbdf
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/PragmaChecksums.expected
@@ -0,0 +1 @@
+| trivia.cs:13:1:13:98 | #pragma checksum ... | "file.cs" | "{406EA660-64CF-4C82-B6F0-42D48172A799}" | "ab007f1d23d9" |
diff --git a/csharp/ql/test/library-tests/comments/PragmaChecksums.ql b/csharp/ql/test/library-tests/comments/PragmaChecksums.ql
new file mode 100644
index 00000000000..7d3f10ab3b5
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/PragmaChecksums.ql
@@ -0,0 +1,4 @@
+import csharp
+
+from PragmaChecksumDirective p
+select p, p.getFileName(), p.getGuid(), p.getBytes()
From 9b405144ffb9949f99a3df121e1c888beb16d0a1 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 17:15:14 +0100
Subject: [PATCH 112/429] Extract define directives
---
.../PreprocessorDirectives/DefineDirective.cs | 18 ++++++++++++++++++
.../Populators/DirectiveVisitor.cs | 5 +++++
.../Semmle.Extraction.CSharp/Tuples.cs | 5 +++++
.../ql/src/semmle/code/csharp/Preprocessor.qll | 12 ++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 6 +++++-
.../comments/DefineDirectives.expected | 1 +
.../library-tests/comments/DefineDirectives.ql | 4 ++++
7 files changed, 50 insertions(+), 1 deletion(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/DefineDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/DefineDirectives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs
new file mode 100644
index 00000000000..2ca967a4864
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/DefineDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class DefineDirective : PreprocessorDirective
+ {
+ public DefineDirective(Context cx, DefineDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_defines(this, trivia.Name.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index c1d23ca5f0b..69222935483 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -22,5 +22,10 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.PragmaChecksumDirective(cx, node);
}
+
+ public override void VisitDefineDirectiveTrivia(DefineDirectiveTriviaSyntax node)
+ {
+ new Entities.DefineDirective(cx, node);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 55060767806..9e14c8d32d2 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -620,5 +620,10 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("pragma_checksums", pragma, file, guid, bytes);
}
+
+ internal static void directive_defines(this TextWriter trapFile, DefineDirective directive, string name)
+ {
+ trapFile.WriteTuple("directive_defines", directive, name);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 46e806aface..a5281da2241 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -46,3 +46,15 @@ class PragmaChecksumDirective extends PreprocessorDirective, @pragma_checksum {
override string getAPrimaryQlClass() { result = "PragmaChecksumDirective" }
}
+
+/**
+ * An `#define` directive.
+ */
+class DefineDirective extends PreprocessorDirective, @directive_define {
+ /** Gets the name of the preprocessor symbol that is being set by this directive. */
+ string getName() { directive_defines(this, result) }
+
+ override string toString() { result = "#define ..." }
+
+ override string getAPrimaryQlClass() { result = "DefineDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index 5530f36c85e..05e53c0042d 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -331,7 +331,11 @@ using_directive_location(
unique int id: @using_directive ref,
int loc: @location ref);
-@preprocessor_directive = @pragma_warning | @pragma_checksum;
+@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define;
+
+directive_defines(
+ unique int id: @directive_define,
+ string name: string ref);
pragma_checksums(
unique int id: @pragma_checksum,
diff --git a/csharp/ql/test/library-tests/comments/DefineDirectives.expected b/csharp/ql/test/library-tests/comments/DefineDirectives.expected
new file mode 100644
index 00000000000..e58b3859d58
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/DefineDirectives.expected
@@ -0,0 +1 @@
+| trivia.cs:4:1:4:13 | #define ... | DEBUG |
diff --git a/csharp/ql/test/library-tests/comments/DefineDirectives.ql b/csharp/ql/test/library-tests/comments/DefineDirectives.ql
new file mode 100644
index 00000000000..a27b18f9fbe
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/DefineDirectives.ql
@@ -0,0 +1,4 @@
+import csharp
+
+from DefineDirective d
+select d, d.getName()
From 3740aba4a800b2a96d84ee90600902b31d85d969 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 19 Jan 2021 17:22:51 +0100
Subject: [PATCH 113/429] Extract undef directives
---
.../UndefineDirective.cs | 18 ++++++++++++++++++
.../Populators/DirectiveVisitor.cs | 5 +++++
.../Semmle.Extraction.CSharp/Tuples.cs | 5 +++++
.../ql/src/semmle/code/csharp/Preprocessor.qll | 12 ++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 6 +++++-
.../comments/UndefineDirectives.expected | 1 +
.../comments/UndefineDirectives.ql | 4 ++++
7 files changed, 50 insertions(+), 1 deletion(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/UndefineDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/UndefineDirectives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs
new file mode 100644
index 00000000000..d4b976d50c0
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/UndefineDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class UndefineDirective : PreprocessorDirective
+ {
+ public UndefineDirective(Context cx, UndefDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_undefines(this, trivia.Name.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index 69222935483..07fd08965e0 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -27,5 +27,10 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.DefineDirective(cx, node);
}
+
+ public override void VisitUndefDirectiveTrivia(UndefDirectiveTriviaSyntax node)
+ {
+ new Entities.UndefineDirective(cx, node);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 9e14c8d32d2..b55ef23b6f4 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -625,5 +625,10 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("directive_defines", directive, name);
}
+
+ internal static void directive_undefines(this TextWriter trapFile, UndefineDirective directive, string name)
+ {
+ trapFile.WriteTuple("directive_undefines", directive, name);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index a5281da2241..174349e57d4 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -58,3 +58,15 @@ class DefineDirective extends PreprocessorDirective, @directive_define {
override string getAPrimaryQlClass() { result = "DefineDirective" }
}
+
+/**
+ * An `#undef` directive.
+ */
+class UndefineDirective extends PreprocessorDirective, @directive_undefine {
+ /** Gets the name of the preprocessor symbol that is being unset by this directive. */
+ string getName() { directive_undefines(this, result) }
+
+ override string toString() { result = "#undef ..." }
+
+ override string getAPrimaryQlClass() { result = "UndefineDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index 05e53c0042d..a026ea3d661 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -331,7 +331,11 @@ using_directive_location(
unique int id: @using_directive ref,
int loc: @location ref);
-@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define;
+@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine;
+
+directive_undefines(
+ unique int id: @directive_undefine,
+ string name: string ref);
directive_defines(
unique int id: @directive_define,
diff --git a/csharp/ql/test/library-tests/comments/UndefineDirectives.expected b/csharp/ql/test/library-tests/comments/UndefineDirectives.expected
new file mode 100644
index 00000000000..016d2623f7d
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/UndefineDirectives.expected
@@ -0,0 +1 @@
+| trivia.cs:6:1:6:12 | #undef ... | DEBUG |
diff --git a/csharp/ql/test/library-tests/comments/UndefineDirectives.ql b/csharp/ql/test/library-tests/comments/UndefineDirectives.ql
new file mode 100644
index 00000000000..72e992f9373
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/UndefineDirectives.ql
@@ -0,0 +1,4 @@
+import csharp
+
+from UndefineDirective d
+select d, d.getName()
From 15c611e22f80aa4926158fc57ab6b0720e66d1cc Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 20 Jan 2021 08:55:41 +0100
Subject: [PATCH 114/429] Extract warning and error directives
---
.../PreprocessorDirectives/ErrorDirective.cs | 18 ++++++++++++++
.../WarningDirective.cs | 18 ++++++++++++++
.../Populators/DirectiveVisitor.cs | 10 ++++++++
.../Semmle.Extraction.CSharp/Tuples.cs | 10 ++++++++
.../src/semmle/code/csharp/Preprocessor.qll | 24 +++++++++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 11 ++++++++-
.../comments/ErrorDirectives.expected | 1 +
.../library-tests/comments/ErrorDirectives.ql | 4 ++++
.../comments/WarningDirectives.expected | 1 +
.../comments/WarningDirectives.ql | 4 ++++
10 files changed, 100 insertions(+), 1 deletion(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/ErrorDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/ErrorDirectives.ql
create mode 100644 csharp/ql/test/library-tests/comments/WarningDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/WarningDirectives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs
new file mode 100644
index 00000000000..2917d077839
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ErrorDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class ErrorDirective : PreprocessorDirective
+ {
+ public ErrorDirective(Context cx, ErrorDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_errors(this, trivia.EndOfDirectiveToken.LeadingTrivia.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs
new file mode 100644
index 00000000000..1511be8d28c
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/WarningDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class WarningDirective : PreprocessorDirective
+ {
+ public WarningDirective(Context cx, WarningDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_warnings(this, trivia.EndOfDirectiveToken.LeadingTrivia.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index 07fd08965e0..7bafdac71b4 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -32,5 +32,15 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.UndefineDirective(cx, node);
}
+
+ public override void VisitWarningDirectiveTrivia(WarningDirectiveTriviaSyntax node)
+ {
+ new Entities.WarningDirective(cx, node);
+ }
+
+ public override void VisitErrorDirectiveTrivia(ErrorDirectiveTriviaSyntax node)
+ {
+ new Entities.ErrorDirective(cx, node);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index b55ef23b6f4..bcdacb8c3c1 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -630,5 +630,15 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("directive_undefines", directive, name);
}
+
+ internal static void directive_warnings(this TextWriter trapFile, WarningDirective directive, string message)
+ {
+ trapFile.WriteTuple("directive_warnings", directive, message);
+ }
+
+ internal static void directive_errors(this TextWriter trapFile, ErrorDirective directive, string message)
+ {
+ trapFile.WriteTuple("directive_errors", directive, message);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 174349e57d4..39dce043013 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -70,3 +70,27 @@ class UndefineDirective extends PreprocessorDirective, @directive_undefine {
override string getAPrimaryQlClass() { result = "UndefineDirective" }
}
+
+/**
+ * A `#warning` directive.
+ */
+class WarningDirective extends PreprocessorDirective, @directive_warning {
+ /** Gets the text of the warning. */
+ string getMessage() { directive_warnings(this, result) }
+
+ override string toString() { result = "#warning ..." }
+
+ override string getAPrimaryQlClass() { result = "WarningDirective" }
+}
+
+/**
+ * An `#error` directive.
+ */
+class ErrorDirective extends PreprocessorDirective, @directive_error {
+ /** Gets the text of the error. */
+ string getMessage() { directive_errors(this, result) }
+
+ override string toString() { result = "#error ..." }
+
+ override string getAPrimaryQlClass() { result = "ErrorDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index a026ea3d661..b05630b6e9a 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -331,7 +331,16 @@ using_directive_location(
unique int id: @using_directive ref,
int loc: @location ref);
-@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine;
+@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning
+ | @directive_error;
+
+directive_warnings(
+ unique int id: @directive_warning,
+ string message: string ref);
+
+directive_errors(
+ unique int id: @directive_error,
+ string message: string ref);
directive_undefines(
unique int id: @directive_undefine,
diff --git a/csharp/ql/test/library-tests/comments/ErrorDirectives.expected b/csharp/ql/test/library-tests/comments/ErrorDirectives.expected
new file mode 100644
index 00000000000..656b501c904
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/ErrorDirectives.expected
@@ -0,0 +1 @@
+| trivia.cs:72:1:72:41 | #error ... | NOTDEBUG is defined or TEST is not |
diff --git a/csharp/ql/test/library-tests/comments/ErrorDirectives.ql b/csharp/ql/test/library-tests/comments/ErrorDirectives.ql
new file mode 100644
index 00000000000..dbfbb986a4c
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/ErrorDirectives.ql
@@ -0,0 +1,4 @@
+import csharp
+
+from ErrorDirective d
+select d, d.getMessage()
diff --git a/csharp/ql/test/library-tests/comments/WarningDirectives.expected b/csharp/ql/test/library-tests/comments/WarningDirectives.expected
new file mode 100644
index 00000000000..105119a8127
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/WarningDirectives.expected
@@ -0,0 +1 @@
+| trivia.cs:66:1:66:25 | #warning ... | DEBUG is defined |
diff --git a/csharp/ql/test/library-tests/comments/WarningDirectives.ql b/csharp/ql/test/library-tests/comments/WarningDirectives.ql
new file mode 100644
index 00000000000..0b1dedf1d77
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/WarningDirectives.ql
@@ -0,0 +1,4 @@
+import csharp
+
+from WarningDirective d
+select d, d.getMessage()
From 4bb8b6c9924cbe9767818acc7302cfb718999766 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 20 Jan 2021 09:34:18 +0100
Subject: [PATCH 115/429] Extract nullable directives
---
.../NullableDirective.cs | 35 ++++++++++++++
.../Populators/DirectiveVisitor.cs | 5 ++
.../Semmle.Extraction.CSharp/Tuples.cs | 5 ++
.../src/semmle/code/csharp/Preprocessor.qll | 46 +++++++++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 7 ++-
.../comments/NullableDirectives.expected | 21 +++++++++
.../comments/NullableDirectives.ql | 11 +++++
7 files changed, 129 insertions(+), 1 deletion(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/NullableDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/NullableDirectives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs
new file mode 100644
index 00000000000..e6bb4e79fed
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/NullableDirective.cs
@@ -0,0 +1,35 @@
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class NullableDirective : PreprocessorDirective
+ {
+ public NullableDirective(Context cx, NullableDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ var setting = trivia.SettingToken.Kind() switch
+ {
+ SyntaxKind.DisableKeyword => 0,
+ SyntaxKind.EnableKeyword => 1,
+ SyntaxKind.RestoreKeyword => 2,
+ _ => throw new InternalError(trivia, "Unhandled setting token kind")
+ };
+
+ var target = trivia.TargetToken.Kind() switch
+ {
+ SyntaxKind.None => 0,
+ SyntaxKind.AnnotationsKeyword => 1,
+ SyntaxKind.WarningsKeyword => 2,
+ _ => throw new InternalError(trivia, "Unhandled target token kind")
+ };
+
+ trapFile.directive_nullables(this, setting, target);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index 7bafdac71b4..d16b4ca041b 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -42,5 +42,10 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.ErrorDirective(cx, node);
}
+
+ public override void VisitNullableDirectiveTrivia(NullableDirectiveTriviaSyntax node)
+ {
+ new Entities.NullableDirective(cx, node);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index bcdacb8c3c1..4b834d42ae1 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -640,5 +640,10 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("directive_errors", directive, message);
}
+
+ internal static void directive_nullables(this TextWriter trapFile, NullableDirective directive, int setting, int target)
+ {
+ trapFile.WriteTuple("directive_nullables", directive, setting, target);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 39dce043013..8996756d069 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -94,3 +94,49 @@ class ErrorDirective extends PreprocessorDirective, @directive_error {
override string getAPrimaryQlClass() { result = "ErrorDirective" }
}
+
+/**
+ * A `#nullable` directive.
+ */
+class NullableDirective extends PreprocessorDirective, @directive_nullable {
+ /** Holds if this is a `#nullable disable` directive. */
+ predicate disable() { directive_nullables(this, 0, _) }
+
+ /** Holds if this is a `#nullable enable` directive. */
+ predicate enable() { directive_nullables(this, 1, _) }
+
+ /** Holds if this is a `#nullable restore` directive. */
+ predicate restore() { directive_nullables(this, 2, _) }
+
+ /** Holds if this directive targets all nullable contexts. */
+ predicate targetsAll() { directive_nullables(this, _, 0) }
+
+ /** Holds if this directive targets nullable annotation context. */
+ predicate targetsAnnotations() { directive_nullables(this, _, 1) }
+
+ /** Holds if this directive targets nullable warning context. */
+ predicate targetsWarnings() { directive_nullables(this, _, 2) }
+
+ /** Gets the succeeding `#nullable` directive in the file, if any. */
+ NullableDirective getSuccNullableDirective() {
+ result =
+ rank[1](NullableDirective next |
+ next.getFile() = this.getFile() and
+ next.getLocation().getStartLine() > this.getLocation().getStartLine()
+ |
+ next order by next.getLocation().getStartLine()
+ )
+ }
+
+ /** Holds if there is a succeeding `#nullable` directive in the file. */
+ predicate hasSuccNullableDirective() {
+ exists(NullableDirective other |
+ other.getFile() = this.getFile() and
+ other.getLocation().getStartLine() > this.getLocation().getStartLine()
+ )
+ }
+
+ override string toString() { result = "#nullable ..." }
+
+ override string getAPrimaryQlClass() { result = "NullableDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index b05630b6e9a..e815200408e 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -332,7 +332,12 @@ using_directive_location(
int loc: @location ref);
@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning
- | @directive_error;
+ | @directive_error | @directive_nullable;
+
+directive_nullables(
+ unique int id: @directive_nullable,
+ int setting: int ref, /* 0: disable, 1: enable, 2: restore */
+ int target: int ref); /* 0: none, 1: annotations, 2: warnings */
directive_warnings(
unique int id: @directive_warning,
diff --git a/csharp/ql/test/library-tests/comments/NullableDirectives.expected b/csharp/ql/test/library-tests/comments/NullableDirectives.expected
new file mode 100644
index 00000000000..bbf13afa63b
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/NullableDirectives.expected
@@ -0,0 +1,21 @@
+nullables
+| trivia.cs:49:1:49:82 | #nullable ... | 0 | 0 |
+| trivia.cs:50:1:50:80 | #nullable ... | 1 | 0 |
+| trivia.cs:51:1:51:94 | #nullable ... | 2 | 0 |
+| trivia.cs:52:1:52:81 | #nullable ... | 0 | 1 |
+| trivia.cs:53:1:53:79 | #nullable ... | 1 | 1 |
+| trivia.cs:54:1:54:93 | #nullable ... | 2 | 1 |
+| trivia.cs:55:1:55:75 | #nullable ... | 0 | 2 |
+| trivia.cs:56:1:56:73 | #nullable ... | 1 | 2 |
+| trivia.cs:57:1:57:87 | #nullable ... | 2 | 2 |
+succ
+| trivia.cs:49:1:49:82 | #nullable ... | trivia.cs:50:1:50:80 | #nullable ... |
+| trivia.cs:50:1:50:80 | #nullable ... | trivia.cs:51:1:51:94 | #nullable ... |
+| trivia.cs:51:1:51:94 | #nullable ... | trivia.cs:52:1:52:81 | #nullable ... |
+| trivia.cs:52:1:52:81 | #nullable ... | trivia.cs:53:1:53:79 | #nullable ... |
+| trivia.cs:53:1:53:79 | #nullable ... | trivia.cs:54:1:54:93 | #nullable ... |
+| trivia.cs:54:1:54:93 | #nullable ... | trivia.cs:55:1:55:75 | #nullable ... |
+| trivia.cs:55:1:55:75 | #nullable ... | trivia.cs:56:1:56:73 | #nullable ... |
+| trivia.cs:56:1:56:73 | #nullable ... | trivia.cs:57:1:57:87 | #nullable ... |
+last
+| trivia.cs:57:1:57:87 | #nullable ... |
diff --git a/csharp/ql/test/library-tests/comments/NullableDirectives.ql b/csharp/ql/test/library-tests/comments/NullableDirectives.ql
new file mode 100644
index 00000000000..a5312df39e9
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/NullableDirectives.ql
@@ -0,0 +1,11 @@
+import csharp
+
+query predicate nullables(NullableDirective d, int setting, int target) {
+ directive_nullables(d, setting, target)
+}
+
+query predicate succ(NullableDirective d, NullableDirective succ) {
+ d.getSuccNullableDirective() = succ
+}
+
+query predicate last(NullableDirective d) { not d.hasSuccNullableDirective() }
From fe0a494bab546ba05458a68bc3ca756cc5033e3b Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 20 Jan 2021 10:06:05 +0100
Subject: [PATCH 116/429] Extract line directives
---
.../PreprocessorDirectives/LineDirective.cs | 34 +++++++++
.../Populators/DirectiveVisitor.cs | 5 ++
.../Semmle.Extraction.CSharp/Tuples.cs | 10 +++
.../src/semmle/code/csharp/Preprocessor.qll | 69 +++++++++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 11 ++-
.../comments/LineDirectives.expected | 13 ++++
.../library-tests/comments/LineDirectives.ql | 13 ++++
.../ql/test/library-tests/comments/trivia.cs | 2 +-
8 files changed, 155 insertions(+), 2 deletions(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/LineDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/LineDirectives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs
new file mode 100644
index 00000000000..2ca34d6c7a1
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/LineDirective.cs
@@ -0,0 +1,34 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class LineDirective : PreprocessorDirective
+ {
+ public LineDirective(Context cx, LineDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ var type = trivia.Line.Kind() switch
+ {
+ SyntaxKind.DefaultKeyword => 0,
+ SyntaxKind.HiddenKeyword => 1,
+ SyntaxKind.NumericLiteralToken => 2,
+ _ => throw new InternalError(trivia, "Unhandled line token kind")
+ };
+
+ trapFile.directive_lines(this, type);
+
+ if (trivia.Line.IsKind(SyntaxKind.NumericLiteralToken))
+ {
+ var value = (int)trivia.Line.Value;
+ trapFile.directive_line_values(this, value, trivia.File.ValueText);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index d16b4ca041b..baa52ec7f03 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -47,5 +47,10 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.NullableDirective(cx, node);
}
+
+ public override void VisitLineDirectiveTrivia(LineDirectiveTriviaSyntax node)
+ {
+ new Entities.LineDirective(cx, node);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 4b834d42ae1..1ee94ff1426 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -645,5 +645,15 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("directive_nullables", directive, setting, target);
}
+
+ internal static void directive_lines(this TextWriter trapFile, LineDirective directive, int kind)
+ {
+ trapFile.WriteTuple("directive_lines", directive, kind);
+ }
+
+ internal static void directive_line_values(this TextWriter trapFile, LineDirective directive, int line, string file)
+ {
+ trapFile.WriteTuple("directive_line_values", directive, line, file);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 8996756d069..983e58da2cb 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -140,3 +140,72 @@ class NullableDirective extends PreprocessorDirective, @directive_nullable {
override string getAPrimaryQlClass() { result = "NullableDirective" }
}
+
+/**
+ * A `#line` directive, such as `#line default`, `#line hidden`, or `#line`
+ * directive with line number.
+ */
+class LineDirective extends PreprocessorDirective, @directive_line {
+ /** Gets the succeeding `#line` directive in the file, if any. */
+ LineDirective getSuccLineDirective() {
+ result =
+ rank[1](LineDirective next |
+ next.getFile() = this.getFile() and
+ next.getLocation().getStartLine() > this.getLocation().getStartLine()
+ |
+ next order by next.getLocation().getStartLine()
+ )
+ }
+
+ /** Holds if there is a succeeding `#line` directive in the file. */
+ predicate hasSuccLineDirective() {
+ exists(LineDirective other |
+ other.getFile() = this.getFile() and
+ other.getLocation().getStartLine() > this.getLocation().getStartLine()
+ )
+ }
+
+ override string toString() { result = "#line ..." }
+
+ override string getAPrimaryQlClass() { result = "LineDirective" }
+}
+
+/**
+ * A `#line default` directive.
+ */
+class DefaultLineDirective extends LineDirective {
+ DefaultLineDirective() { directive_lines(this, 0) }
+
+ override string toString() { result = "#line default" }
+
+ override string getAPrimaryQlClass() { result = "DefaultLineDirective" }
+}
+
+/**
+ * A `#line hidden` directive.
+ */
+class HiddenLineDirective extends LineDirective {
+ HiddenLineDirective() { directive_lines(this, 1) }
+
+ override string toString() { result = "#line hidden" }
+
+ override string getAPrimaryQlClass() { result = "HiddenLineDirective" }
+}
+
+/**
+ * A numeric `#line` directive, such as `#line 200 file`
+ */
+class NumericLineDirective extends LineDirective {
+ NumericLineDirective() { directive_lines(this, 2) }
+
+ /** Gets the line number of this directive. */
+ int getLine() { directive_line_values(this, result, _) }
+
+ /** Holds if this directive specifies a file name. */
+ predicate hasFileName() { this.getFileName() != "" }
+
+ /** Gets the file name of this directive. */
+ string getFileName() { directive_line_values(this, _, result) }
+
+ override string getAPrimaryQlClass() { result = "NumericLineDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index e815200408e..92e9947958b 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -332,7 +332,16 @@ using_directive_location(
int loc: @location ref);
@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning
- | @directive_error | @directive_nullable;
+ | @directive_error | @directive_nullable | @directive_line;
+
+directive_lines(
+ unique int id: @directive_line,
+ int kind: int ref); /* 0: default, 1: hidden, 2: numeric */
+
+directive_line_values(
+ unique int id: @directive_line ref,
+ int line: int ref,
+ string file: string ref);
directive_nullables(
unique int id: @directive_nullable,
diff --git a/csharp/ql/test/library-tests/comments/LineDirectives.expected b/csharp/ql/test/library-tests/comments/LineDirectives.expected
new file mode 100644
index 00000000000..ae79e53d1b4
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/LineDirectives.expected
@@ -0,0 +1,13 @@
+default
+| trivia.cs:21:1:21:13 | #line default |
+hidden
+| trivia.cs:25:1:25:38 | #line hidden |
+lines
+| trivia.cs:18:1:18:19 | #line ... | 200 | Special |
+| trivia.cs:27:1:27:9 | #line ... | 300 | |
+succ
+| trivia.cs:18:1:18:19 | #line ... | trivia.cs:21:1:21:13 | #line default |
+| trivia.cs:21:1:21:13 | #line default | trivia.cs:25:1:25:38 | #line hidden |
+| trivia.cs:25:1:25:38 | #line hidden | trivia.cs:27:1:27:9 | #line ... |
+last
+| trivia.cs:27:1:27:9 | #line ... |
diff --git a/csharp/ql/test/library-tests/comments/LineDirectives.ql b/csharp/ql/test/library-tests/comments/LineDirectives.ql
new file mode 100644
index 00000000000..db05488911a
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/LineDirectives.ql
@@ -0,0 +1,13 @@
+import csharp
+
+query predicate default(DefaultLineDirective line) { any() }
+
+query predicate hidden(HiddenLineDirective line) { any() }
+
+query predicate lines(NumericLineDirective line, int l, string file) {
+ line.getLine() = l and line.getFileName() = file
+}
+
+query predicate succ(LineDirective d, LineDirective succ) { d.getSuccLineDirective() = succ }
+
+query predicate last(LineDirective d) { not d.hasSuccLineDirective() }
diff --git a/csharp/ql/test/library-tests/comments/trivia.cs b/csharp/ql/test/library-tests/comments/trivia.cs
index e22ddf77a63..69aea425499 100644
--- a/csharp/ql/test/library-tests/comments/trivia.cs
+++ b/csharp/ql/test/library-tests/comments/trivia.cs
@@ -24,7 +24,7 @@ class Tr1
float f;
#line hidden // numbering not affected
string s;
-#line default
+#line 300
double d;
}
}
From a5d18f9b6882e8f99c55deb667e482f8aa53829d Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 20 Jan 2021 11:07:24 +0100
Subject: [PATCH 117/429] Extract region directives
---
.../EndRegionDirective.cs | 23 ++++++++++++++++
.../PreprocessorDirectives/RegionDirective.cs | 18 +++++++++++++
.../Populators/DirectiveVisitor.cs | 23 ++++++++++++++++
.../Semmle.Extraction.CSharp/Tuples.cs | 15 +++++++++++
.../src/semmle/code/csharp/Preprocessor.qll | 27 +++++++++++++++++++
csharp/ql/src/semmlecode.csharp.dbscheme | 14 +++++++++-
.../comments/RegionDirectives.expected | 6 +++++
.../comments/RegionDirectives.ql | 8 ++++++
8 files changed, 133 insertions(+), 1 deletion(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/RegionDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/RegionDirectives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
new file mode 100644
index 00000000000..a320c89028c
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
@@ -0,0 +1,23 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class EndRegionDirective : PreprocessorDirective
+ {
+ public EndRegionDirective(Context cx, EndRegionDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_endregions(this);
+ }
+
+ internal static void WriteRegionBlock(Context cx, RegionDirective region, EndRegionDirective endregion)
+ {
+ cx.TrapWriter.Writer.regions(region, endregion);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
new file mode 100644
index 00000000000..28a707ee4de
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class RegionDirective : PreprocessorDirective
+ {
+ public RegionDirective(Context cx, RegionDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_regions(this, trivia.EndOfDirectiveToken.LeadingTrivia.ToString());
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index baa52ec7f03..a825ecead61 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -52,5 +54,26 @@ namespace Semmle.Extraction.CSharp.Populators
{
new Entities.LineDirective(cx, node);
}
+
+ private readonly Stack regionStarts = new Stack();
+
+ public override void VisitRegionDirectiveTrivia(RegionDirectiveTriviaSyntax node)
+ {
+ var region = new Entities.RegionDirective(cx, node);
+ regionStarts.Push(region);
+ }
+
+ public override void VisitEndRegionDirectiveTrivia(EndRegionDirectiveTriviaSyntax node)
+ {
+ var endregion = new Entities.EndRegionDirective(cx, node);
+ if (regionStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start region", null,
+ Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+ var start = regionStarts.Pop();
+ Entities.EndRegionDirective.WriteRegionBlock(cx, start, endregion);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 1ee94ff1426..1eebf07c37f 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -655,5 +655,20 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("directive_line_values", directive, line, file);
}
+
+ internal static void directive_regions(this TextWriter trapFile, RegionDirective directive, string name)
+ {
+ trapFile.WriteTuple("directive_regions", directive, name);
+ }
+
+ internal static void directive_endregions(this TextWriter trapFile, EndRegionDirective directive)
+ {
+ trapFile.WriteTuple("directive_endregions", directive);
+ }
+
+ internal static void regions(this TextWriter trapFile, RegionDirective start, EndRegionDirective end)
+ {
+ trapFile.WriteTuple("regions", start, end);
+ }
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 983e58da2cb..10397230f2e 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -209,3 +209,30 @@ class NumericLineDirective extends LineDirective {
override string getAPrimaryQlClass() { result = "NumericLineDirective" }
}
+
+/**
+ * A `#region` directive.
+ */
+class RegionDirective extends PreprocessorDirective, @directive_region {
+ /** Gets the name of this region. */
+ string getName() { directive_regions(this, result) }
+
+ /** Gets the closing `#endregion` directive. */
+ EndRegionDirective getEnd() { regions(this, result) }
+
+ override string toString() { result = "#region ..." }
+
+ override string getAPrimaryQlClass() { result = "RegionDirective" }
+}
+
+/**
+ * A `#endregion` directive.
+ */
+class EndRegionDirective extends PreprocessorDirective, @directive_endregion {
+ /** Gets the opening `#region` directive. */
+ RegionDirective getStart() { regions(result, this) }
+
+ override string toString() { result = "#endregion" }
+
+ override string getAPrimaryQlClass() { result = "EndRegionDirective" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index 92e9947958b..ad723e09a4d 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -332,7 +332,19 @@ using_directive_location(
int loc: @location ref);
@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning
- | @directive_error | @directive_nullable | @directive_line;
+ | @directive_error | @directive_nullable | @directive_line | @directive_region | @directive_endregion;
+
+directive_regions(
+ unique int id: @directive_region,
+ string name: string ref);
+
+directive_endregions(
+ unique int id: @directive_endregion);
+
+#keyset[start, end]
+regions(
+ unique int start: @directive_region ref,
+ unique int end: @directive_endregion ref);
directive_lines(
unique int id: @directive_line,
diff --git a/csharp/ql/test/library-tests/comments/RegionDirectives.expected b/csharp/ql/test/library-tests/comments/RegionDirectives.expected
new file mode 100644
index 00000000000..aead68e7ff0
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/RegionDirectives.expected
@@ -0,0 +1,6 @@
+regionDirectives
+| trivia.cs:36:9:36:22 | #region ... | fields | trivia.cs:41:9:41:18 | #endregion |
+| trivia.cs:38:9:38:22 | #region ... | nested | trivia.cs:40:9:40:18 | #endregion |
+endregions
+| trivia.cs:40:9:40:18 | #endregion | trivia.cs:38:9:38:22 | #region ... |
+| trivia.cs:41:9:41:18 | #endregion | trivia.cs:36:9:36:22 | #region ... |
diff --git a/csharp/ql/test/library-tests/comments/RegionDirectives.ql b/csharp/ql/test/library-tests/comments/RegionDirectives.ql
new file mode 100644
index 00000000000..9b1296819f2
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/RegionDirectives.ql
@@ -0,0 +1,8 @@
+import csharp
+
+query predicate regionDirectives(RegionDirective d, string name, EndRegionDirective end) {
+ d.getName() = name and
+ d.getEnd() = end
+}
+
+query predicate endregions(EndRegionDirective d, RegionDirective start) { d.getStart() = start }
From 41fbce0ad00899ce5bb791a911a9ab93e0fad631 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 20 Jan 2021 15:21:36 +0100
Subject: [PATCH 118/429] Extract #if directives
---
.../Entities/Expression.cs | 38 +++-
.../Entities/Expressions/DefineSymbol.cs | 18 ++
.../Entities/Expressions/Literal.cs | 5 +
.../Entities/Expressions/Name.cs | 12 +-
.../PreprocessorDirectives/ElifDirective.cs | 22 +++
.../PreprocessorDirectives/ElseDirective.cs | 18 ++
.../PreprocessorDirectives/EndIfDirective.cs | 18 ++
.../IIfSiblingDirective.cs | 4 +
.../PreprocessorDirectives/IfDirective.cs | 40 ++++
.../PreprocessorDirectives/RegionDirective.cs | 1 +
.../Kinds/ExprKind.cs | 2 +
.../Populators/DirectiveVisitor.cs | 47 +++++
.../Semmle.Extraction.CSharp/Tuples.cs | 38 ++++
.../src/semmle/code/csharp/Preprocessor.qll | 92 +++++++++
csharp/ql/src/semmle/code/csharp/PrintAst.qll | 2 +
.../ql/src/semmle/code/csharp/exprs/Expr.qll | 18 ++
csharp/ql/src/semmlecode.csharp.dbscheme | 41 +++-
.../comments/IfDirectives.expected | 19 ++
.../library-tests/comments/IfDirectives.ql | 33 ++++
.../library-tests/comments/PrintAst.expected | 175 ++++++++++++++++++
.../library-tests/comments/PrintAst.qlref | 1 +
21 files changed, 639 insertions(+), 5 deletions(-)
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/DefineSymbol.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
create mode 100644 csharp/ql/test/library-tests/comments/IfDirectives.expected
create mode 100644 csharp/ql/test/library-tests/comments/IfDirectives.ql
create mode 100644 csharp/ql/test/library-tests/comments/PrintAst.expected
create mode 100644 csharp/ql/test/library-tests/comments/PrintAst.qlref
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
index 9d701d242a3..5829bbffa9e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
@@ -529,7 +529,17 @@ namespace Semmle.Extraction.CSharp.Entities
get
{
var c = Model.GetConstantValue(Node);
- return c.HasValue ? Expression.ValueAsString(c.Value) : null;
+ if (c.HasValue)
+ {
+ return Expression.ValueAsString(c.Value);
+ }
+
+ if (TryGetBoolValueInsideIfDirective(out var val))
+ {
+ return Expression.ValueAsString(val);
+ }
+
+ return null;
}
}
@@ -593,5 +603,31 @@ namespace Semmle.Extraction.CSharp.Entities
}
public NullableFlowState FlowState => TypeInfo.Nullability.FlowState;
+
+ public bool IsInsideIfDirective()
+ {
+ return Node.Ancestors().Any(a => a is ElifDirectiveTriviaSyntax || a is IfDirectiveTriviaSyntax);
+ }
+
+ public bool TryGetBoolValueInsideIfDirective(out bool val)
+ {
+ var isTrue = Node.IsKind(SyntaxKind.TrueLiteralExpression);
+ var isFalse = Node.IsKind(SyntaxKind.FalseLiteralExpression);
+
+ if (!isTrue && !isFalse)
+ {
+ val = false;
+ return false;
+ }
+
+ if (!IsInsideIfDirective())
+ {
+ val = false;
+ return false;
+ }
+
+ val = isTrue;
+ return true;
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/DefineSymbol.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/DefineSymbol.cs
new file mode 100644
index 00000000000..d0111c010b2
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/DefineSymbol.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Semmle.Extraction.Kinds;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities.Expressions
+{
+ internal class DefineSymbol : Expression
+ {
+ private DefineSymbol(ExpressionNodeInfo info) : base(info.SetKind(ExprKind.DEFINE_SYMBOL)) { }
+
+ public static Expression Create(ExpressionNodeInfo info) => new DefineSymbol(info).TryPopulate();
+
+ protected override void PopulateExpression(TextWriter trapFile)
+ {
+ trapFile.directive_define_symbols(this, Syntax.Identifier.Text);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs
index ad63cc26ac6..d91f426cfd7 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs
@@ -25,6 +25,11 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
return ExprKind.NULL_LITERAL;
}
+ if (info.TryGetBoolValueInsideIfDirective(out var _))
+ {
+ return ExprKind.BOOL_LITERAL;
+ }
+
var type = info.Type?.Symbol;
return GetExprKind(type, info.Node, info.Context);
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs
index d44c85d6ddc..d74afd16329 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
namespace Semmle.Extraction.CSharp.Entities.Expressions
@@ -26,8 +27,15 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
if (target == null)
{
- info.Context.ModelError(info.Node, "Failed to resolve name");
- return new Unknown(info);
+ if (info.IsInsideIfDirective())
+ {
+ return DefineSymbol.Create(info);
+ }
+ else
+ {
+ info.Context.ModelError(info.Node, "Failed to resolve name");
+ return new Unknown(info);
+ }
}
// There is a very strange bug in Microsoft.CodeAnalysis whereby
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
new file mode 100644
index 00000000000..26314244ac6
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
@@ -0,0 +1,22 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class ElifDirective : PreprocessorDirective, IIfSiblingDirective, IExpressionParentEntity
+ {
+ public ElifDirective(Context cx, ElifDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ public bool IsTopLevelParent => true;
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_elifs(this, trivia.BranchTaken, trivia.ConditionValue);
+
+ Expression.Create(cx, trivia.Condition, this, 0);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
new file mode 100644
index 00000000000..48c786627ac
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class ElseDirective : PreprocessorDirective, IIfSiblingDirective
+ {
+ public ElseDirective(Context cx, ElseDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_elses(this, trivia.BranchTaken);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
new file mode 100644
index 00000000000..85d5c89f353
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
@@ -0,0 +1,18 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class EndIfDirective : PreprocessorDirective
+ {
+ public EndIfDirective(Context cx, EndIfDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_endifs(this);
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs
new file mode 100644
index 00000000000..7a849962f09
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IIfSiblingDirective.cs
@@ -0,0 +1,4 @@
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal interface IIfSiblingDirective { }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
new file mode 100644
index 00000000000..fc9af188c1d
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
@@ -0,0 +1,40 @@
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ internal class IfDirective : PreprocessorDirective, IExpressionParentEntity
+ {
+ private readonly List branches = new List();
+
+ public IfDirective(Context cx, IfDirectiveTriviaSyntax trivia)
+ : base(cx, trivia)
+ {
+ }
+
+ public bool IsTopLevelParent => true;
+
+ protected override void PopulatePreprocessor(TextWriter trapFile)
+ {
+ trapFile.directive_ifs(this, trivia.BranchTaken, trivia.ConditionValue);
+
+ Expression.Create(cx, trivia.Condition, this, 0);
+ }
+
+ internal void Add(IIfSiblingDirective branch)
+ {
+ branches.Add(branch);
+ }
+
+ internal void WriteBranches(EndIfDirective endif)
+ {
+ cx.TrapWriter.Writer.directive_if_endif(this, endif);
+ var siblings = 0;
+ foreach (var branch in branches)
+ {
+ cx.TrapWriter.Writer.directive_if_siblings(this, branch, siblings++);
+ }
+ }
+ }
+}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
index 28a707ee4de..b2f017688a3 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/RegionDirective.cs
@@ -1,4 +1,5 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System;
using System.IO;
namespace Semmle.Extraction.CSharp.Entities
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs
index ee1185da30d..292e8c47d98 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Kinds/ExprKind.cs
@@ -124,5 +124,7 @@ namespace Semmle.Extraction.Kinds
AND_PATTERN = 127,
OR_PATTERN = 128,
FUNCTION_POINTER_INVOCATION = 129,
+
+ DEFINE_SYMBOL = 999
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index a825ecead61..074441a70cb 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -75,5 +75,52 @@ namespace Semmle.Extraction.CSharp.Populators
var start = regionStarts.Pop();
Entities.EndRegionDirective.WriteRegionBlock(cx, start, endregion);
}
+
+ private readonly Stack ifStarts = new Stack();
+
+ public override void VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node)
+ {
+ var ifStart = new Entities.IfDirective(cx, node);
+ ifStarts.Push(ifStart);
+ }
+
+ public override void VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node)
+ {
+ var endif = new Entities.EndIfDirective(cx, node);
+ if (ifStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start if", null,
+ Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+ var start = ifStarts.Pop();
+ start.WriteBranches(endif);
+ }
+
+ public override void VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node)
+ {
+ var elif = new Entities.ElifDirective(cx, node);
+ if (ifStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start if", null,
+ Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+ var start = ifStarts.Peek();
+ start.Add(elif);
+ }
+
+ public override void VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node)
+ {
+ var elseDirective = new Entities.ElseDirective(cx, node);
+ if (ifStarts.Count == 0)
+ {
+ cx.ExtractionError("Couldn't find start if", null,
+ Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
+ return;
+ }
+ var start = ifStarts.Peek();
+ start.Add(elseDirective);
+ }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 1eebf07c37f..19b730f7cc0 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -1,6 +1,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Semmle.Extraction.CommentProcessing;
using Semmle.Extraction.CSharp.Entities;
+using Semmle.Extraction.CSharp.Entities.Expressions;
using Semmle.Extraction.Entities;
using Semmle.Extraction.Kinds;
using Semmle.Util;
@@ -670,5 +671,42 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("regions", start, end);
}
+
+ internal static void directive_ifs(this TextWriter trapFile, IfDirective directive, bool branchTaken, bool conditionValue)
+ {
+ trapFile.WriteTuple("directive_ifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0);
+ }
+
+ internal static void directive_elifs(this TextWriter trapFile, ElifDirective directive, bool branchTaken, bool conditionValue)
+ {
+ trapFile.WriteTuple("directive_elifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0);
+ }
+
+ internal static void directive_elses(this TextWriter trapFile, ElseDirective directive, bool branchTaken)
+ {
+ trapFile.WriteTuple("directive_elses", directive, branchTaken ? 1 : 0);
+ }
+
+ internal static void directive_endifs(this TextWriter trapFile, EndIfDirective directive)
+ {
+ trapFile.WriteTuple("directive_endifs", directive);
+ }
+
+ internal static void directive_if_endif(this TextWriter trapFile, IfDirective start, EndIfDirective end)
+ {
+ trapFile.WriteTuple("directive_if_endif", start, end);
+ }
+
+ internal static void directive_if_siblings(this TextWriter trapFile, IfDirective start, IIfSiblingDirective siblingDirective, int index)
+ {
+ trapFile.WriteTuple("directive_if_siblings", start, siblingDirective, index);
+ }
+
+ internal static void directive_define_symbols(this TextWriter trapFile, DefineSymbol symb, string name)
+ {
+ trapFile.WriteTuple("directive_define_symbols", symb, name);
+ }
+
+
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 10397230f2e..1f23275a7a7 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -236,3 +236,95 @@ class EndRegionDirective extends PreprocessorDirective, @directive_endregion {
override string getAPrimaryQlClass() { result = "EndRegionDirective" }
}
+
+/**
+ * A branching preprocessor directive, such as `IfDirective`, `ElifDirective`, or
+ * `ElseDirective`.
+ */
+class BranchDirective extends PreprocessorDirective {
+ /** Holds if the branch is taken by the preprocessor. */
+ predicate branchTaken() { none() }
+}
+
+/**
+ * A preprocessor directive with a branching condition, such as `IfDirective` or
+ * `ElifDirective`.
+ */
+class ConditionalDirective extends BranchDirective {
+ /** Gets the condition. */
+ Expr getCondition() { result = this.getChild(0) }
+
+ /** Holds if the condition is matched. */
+ predicate conditionMatched() { none() }
+}
+
+/**
+ * An `#if` preprocessor directive.
+ */
+class IfDirective extends ConditionalDirective, @directive_if {
+ override predicate branchTaken() { directive_ifs(this, 1, _) }
+
+ override predicate conditionMatched() { directive_ifs(this, _, 1) }
+
+ /** Gets the closing `#endif` preprocessor directive. */
+ EndifDirective getEndifDirective() { directive_if_endif(this, result) }
+
+ /** Gets the sibling `#elif` or `#else` preprocessor directive at index `sibling`. */
+ BranchDirective getSiblingDirective(int sibling) { directive_if_siblings(this, result, sibling) }
+
+ /** Gets a sibling `#elif` or `#else` preprocessor directive. */
+ BranchDirective getASiblingDirective() { result = getSiblingDirective(_) }
+
+ override string toString() { result = "#if ..." }
+
+ override string getAPrimaryQlClass() { result = "IfDirective" }
+}
+
+/**
+ * An `#elif` preprocessor directive.
+ */
+class ElifDirective extends ConditionalDirective, @directive_elif {
+ override predicate branchTaken() { directive_elifs(this, 1, _) }
+
+ override predicate conditionMatched() { directive_elifs(this, _, 1) }
+
+ /** Gets the opening `#if` preprocessor directive. */
+ IfDirective getIfDirective() { directive_if_siblings(result, this, _) }
+
+ /** Gets the successive branching preprocessor directive (`#elif` or `#else`), if any. */
+ BranchDirective getSuccSiblingDirective() {
+ exists(IfDirective i, int index |
+ directive_if_siblings(i, this, index) and directive_if_siblings(i, result, index + 1)
+ )
+ }
+
+ override string toString() { result = "#elif ..." }
+
+ override string getAPrimaryQlClass() { result = "ElifDirective" }
+}
+
+/**
+ * An `#else` preprocessor directive.
+ */
+class ElseDirective extends BranchDirective, @directive_else {
+ /** Gets the opening `#if` preprocessor directive. */
+ IfDirective getIfDirective() { directive_if_siblings(result, this, _) }
+
+ override predicate branchTaken() { directive_elses(this, 1) }
+
+ override string toString() { result = "#else" }
+
+ override string getAPrimaryQlClass() { result = "ElseDirective" }
+}
+
+/**
+ * An `#endif` preprocessor directive.
+ */
+class EndifDirective extends PreprocessorDirective, @directive_endif {
+ /** Gets the opening `#if` preprocessor directive. */
+ IfDirective getIfDirective() { directive_if_endif(result, this) }
+
+ override string toString() { result = "#endif" }
+
+ override string getAPrimaryQlClass() { result = "EndifDirective" }
+}
diff --git a/csharp/ql/src/semmle/code/csharp/PrintAst.qll b/csharp/ql/src/semmle/code/csharp/PrintAst.qll
index 63ff928f9fc..a4ab6f7632c 100644
--- a/csharp/ql/src/semmle/code/csharp/PrintAst.qll
+++ b/csharp/ql/src/semmle/code/csharp/PrintAst.qll
@@ -48,6 +48,8 @@ private predicate isNotNeeded(Element e) {
or
e instanceof TupleType
or
+ e instanceof ConditionalDirective
+ or
isNotNeeded(e.(Declaration).getDeclaringType())
or
isNotNeeded(e.(Parameter).getDeclaringElement())
diff --git a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll
index 4078557f366..5a954b7a925 100644
--- a/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll
+++ b/csharp/ql/src/semmle/code/csharp/exprs/Expr.qll
@@ -1134,3 +1134,21 @@ class SuppressNullableWarningExpr extends Expr, @suppress_nullable_warning_expr
override string getAPrimaryQlClass() { result = "SuppressNullableWarningExpr" }
}
+
+/**
+ * A preprocessor symbol inside an expression, such as DEBUG in line 2
+ * ```csharp
+ * #define DEBUG
+ * #if (DEBUG == true)
+ * Console.WriteLine("Debug version");
+ * #endif
+ * ```
+ */
+class DefineSymbolExpr extends Expr, @define_symbol_expr {
+ /** Gets the name of the symbol. */
+ string getName() { directive_define_symbols(this, result) }
+
+ override string toString() { result = getName() }
+
+ override string getAPrimaryQlClass() { result = "DefineSymbolExpr" }
+}
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index ad723e09a4d..a13a6f6d628 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -332,7 +332,42 @@ using_directive_location(
int loc: @location ref);
@preprocessor_directive = @pragma_warning | @pragma_checksum | @directive_define | @directive_undefine | @directive_warning
- | @directive_error | @directive_nullable | @directive_line | @directive_region | @directive_endregion;
+ | @directive_error | @directive_nullable | @directive_line | @directive_region | @directive_endregion | @directive_if
+ | @directive_elif | @directive_else | @directive_endif;
+
+directive_ifs(
+ unique int id: @directive_if,
+ int branchTaken: int ref, /* 0: false, 1: true */
+ int conditionValue: int ref); /* 0: false, 1: true */
+
+directive_elifs(
+ unique int id: @directive_elif,
+ int branchTaken: int ref, /* 0: false, 1: true */
+ int conditionValue: int ref); /* 0: false, 1: true */
+
+directive_elses(
+ unique int id: @directive_else,
+ int branchTaken: int ref); /* 0: false, 1: true */
+
+directive_endifs(
+ unique int id: @directive_endif);
+
+#keyset[start, end]
+directive_if_endif(
+ unique int start: @directive_if ref,
+ unique int end: @directive_endif ref);
+
+@directive_if_sibling = @directive_else | @directive_elif;
+
+#keyset[start, index]
+directive_if_siblings(
+ int start: @directive_if ref,
+ int sibling: @directive_if_sibling ref,
+ int index: int ref);
+
+directive_define_symbols(
+ unique int id: @define_symbol_expr ref,
+ string name: string ref);
directive_regions(
unique int id: @directive_region,
@@ -955,7 +990,7 @@ expr_parent(
int index: int ref,
int parent: @control_flow_element ref);
-@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter;
+@top_level_expr_parent = @attribute | @field | @property | @indexer | @parameter | @directive_if | @directive_elif;
@top_level_exprorstmt_parent = @top_level_expr_parent | @top_level_stmt_parent;
@@ -1104,6 +1139,8 @@ case @expr.kind of
| 127 = @and_pattern_expr
| 128 = @or_pattern_expr
| 129 = @function_pointer_invocation_expr
+/* Preprocessor */
+| 999 = @define_symbol_expr
;
@switch = @switch_stmt | @switch_expr;
diff --git a/csharp/ql/test/library-tests/comments/IfDirectives.expected b/csharp/ql/test/library-tests/comments/IfDirectives.expected
new file mode 100644
index 00000000000..3a3a24ef798
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/IfDirectives.expected
@@ -0,0 +1,19 @@
+ifDirectives
+| trivia.cs:65:1:65:9 | #if ... | trivia.cs:76:1:76:6 | #endif | not taken | false | trivia.cs:65:5:65:9 | DEBUG |
+| trivia.cs:68:1:68:10 | #if ... | trivia.cs:70:1:70:6 | #endif | not taken | false | trivia.cs:68:5:68:10 | NESTED |
+siblings
+| trivia.cs:65:1:65:9 | #if ... | trivia.cs:71:1:71:35 | #elif ... | 0 | taken |
+| trivia.cs:65:1:65:9 | #if ... | trivia.cs:74:1:74:5 | #else | 1 | not taken |
+conditionalDirectives
+| trivia.cs:65:1:65:9 | #if ... | not taken | false | trivia.cs:65:5:65:9 | DEBUG |
+| trivia.cs:68:1:68:10 | #if ... | not taken | false | trivia.cs:68:5:68:10 | NESTED |
+| trivia.cs:71:1:71:35 | #elif ... | taken | true | trivia.cs:71:7:71:35 | ... \|\| ... |
+expressions
+| trivia.cs:65:5:65:9 | DEBUG |
+| trivia.cs:68:5:68:10 | NESTED |
+| trivia.cs:71:7:71:35 | ... \|\| ... |
+| trivia.cs:71:8:71:15 | NOTDEBUG |
+| trivia.cs:71:8:71:23 | ... == ... |
+| trivia.cs:71:20:71:23 | true |
+| trivia.cs:71:29:71:35 | !... |
+| trivia.cs:71:31:71:34 | TEST |
diff --git a/csharp/ql/test/library-tests/comments/IfDirectives.ql b/csharp/ql/test/library-tests/comments/IfDirectives.ql
new file mode 100644
index 00000000000..d7e97d3abf8
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/IfDirectives.ql
@@ -0,0 +1,33 @@
+import csharp
+
+private string getConditionValue(ConditionalDirective d) {
+ if d.conditionMatched() then result = "true" else result = "false"
+}
+
+private string getBranchValue(BranchDirective d) {
+ if d.branchTaken() then result = "taken" else result = "not taken"
+}
+
+query predicate ifDirectives(
+ IfDirective d, EndifDirective endif, string taken, string condValue, Expr expr
+) {
+ d.getEndifDirective() = endif and
+ d.getCondition() = expr and
+ taken = getBranchValue(d) and
+ condValue = getConditionValue(d)
+}
+
+query predicate siblings(IfDirective d, BranchDirective sibling, int index, string taken) {
+ d.getSiblingDirective(index) = sibling and
+ taken = getBranchValue(sibling)
+}
+
+query predicate conditionalDirectives(
+ ConditionalDirective cond, string taken, string condValue, Expr expr
+) {
+ cond.getCondition() = expr and
+ taken = getBranchValue(cond) and
+ condValue = getConditionValue(cond)
+}
+
+query predicate expressions(Expr e) { e.getParent+() instanceof ConditionalDirective }
diff --git a/csharp/ql/test/library-tests/comments/PrintAst.expected b/csharp/ql/test/library-tests/comments/PrintAst.expected
new file mode 100644
index 00000000000..96f0f088501
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/PrintAst.expected
@@ -0,0 +1,175 @@
+comments1.cs:
+# 9| [Class] C
+# 35| [Class] Foo
+# 40| 5: [Field] x
+# 40| -1: [TypeMention] int
+# 43| 6: [Field] y
+# 43| -1: [TypeMention] int
+# 44| 7: [Field] z
+# 44| -1: [TypeMention] int
+comments2.cs:
+# 11| [Class] C2
+# 13| 4: [Field] field1
+# 13| -1: [TypeMention] int
+# 14| 5: [Field] field2
+# 14| -1: [TypeMention] int
+# 19| 6: [Method] f
+# 19| -1: [TypeMention] Void
+# 20| 4: [BlockStmt] {...}
+# 23| 0: [ExprStmt] ...;
+# 23| 0: [MethodCall] call to method f
+# 26| 1: [ExprStmt] ...;
+# 26| 0: [MethodCall] call to method g
+# 26| 0: [IntLiteral] 2
+# 29| 7: [Method] g
+# 29| -1: [TypeMention] Void
+#-----| 2: (Parameters)
+# 29| 0: [Parameter] x
+# 29| -1: [TypeMention] int
+# 30| 4: [BlockStmt] {...}
+# 32| 0: [LocalVariableDeclStmt] ... ...;
+# 32| 0: [LocalVariableDeclAndInitExpr] Int32 y = ...
+# 32| -1: [TypeMention] int
+# 32| 0: [LocalVariableAccess] access to local variable y
+# 32| 1: [IntLiteral] 0
+# 34| 1: [LocalVariableDeclStmt] ... ...;
+# 34| 0: [LocalVariableDeclAndInitExpr] Int32 z = ...
+# 34| -1: [TypeMention] int
+# 34| 0: [LocalVariableAccess] access to local variable z
+# 34| 1: [IntLiteral] 0
+# 40| 8: [Class] C3
+# 48| 9: [IndexerProperty] S1
+# 48| -1: [TypeMention] string
+# 52| 3: [Getter] get_S1
+# 52| 4: [BlockStmt] {...}
+# 52| 0: [ReturnStmt] return ...;
+# 52| 0: [StringLiteral] ""
+# 53| 4: [Setter] set_S1
+#-----| 2: (Parameters)
+# 53| 0: [Parameter] value
+# 54| 4: [BlockStmt] {...}
+# 61| 10: [Enum] Values
+# 66| 5: [Field] First
+# 67| 6: [Field] Second
+# 73| 7: [Field] Third
+# 79| 11: [InstanceConstructor] C2
+# 80| 4: [BlockStmt] {...}
+# 85| 12: [Destructor] ~C2
+# 86| 4: [BlockStmt] {...}
+# 90| 13: [AddOperator] +
+# 90| -1: [TypeMention] int
+#-----| 2: (Parameters)
+# 90| 0: [Parameter] x
+# 90| -1: [TypeMention] C2
+# 90| 1: [Parameter] b
+# 90| -1: [TypeMention] C2
+# 91| 4: [BlockStmt] {...}
+# 92| 0: [ReturnStmt] return ...;
+# 92| 0: [IntLiteral] 2
+# 95| 14: [Method] f
+# 95| -1: [TypeMention] Void
+#-----| 2: (Parameters)
+# 96| 0: [Parameter] x
+# 96| -1: [TypeMention] int
+# 97| 1: [Parameter] y
+# 97| -1: [TypeMention] int
+# 99| 4: [BlockStmt] {...}
+# 103| 15: [DelegateType] D
+# 107| 16: [Event] E
+# 107| -1: [TypeMention] D
+# 107| 3: [AddEventAccessor] add_E
+#-----| 2: (Parameters)
+# 107| 0: [Parameter] value
+# 107| 4: [RemoveEventAccessor] remove_E
+#-----| 2: (Parameters)
+# 107| 0: [Parameter] value
+# 110| 17: [Method] gen
+# 110| -1: [TypeMention] Void
+# 111| 4: [BlockStmt] {...}
+# 112| 0: [LocalVariableDeclStmt] ... ...;
+# 112| 0: [LocalVariableDeclAndInitExpr] GenericClass t1 = ...
+# 112| -1: [TypeMention] GenericClass
+# 112| 0: [LocalVariableAccess] access to local variable t1
+# 112| 1: [ObjectCreation] object creation of type GenericClass
+# 112| 0: [TypeMention] GenericClass
+# 112| 1: [TypeMention] int
+# 113| 1: [LocalVariableDeclStmt] ... ...;
+# 113| 0: [LocalVariableDeclAndInitExpr] Int32 t2 = ...
+# 113| -1: [TypeMention] int
+# 113| 0: [LocalVariableAccess] access to local variable t2
+# 113| 1: [MethodCall] call to method GenericFn
+# 114| 2: [LocalVariableDeclStmt] ... ...;
+# 114| 0: [LocalVariableDeclAndInitExpr] GenericClass t3 = ...
+# 114| -1: [TypeMention] GenericClass
+# 114| 0: [LocalVariableAccess] access to local variable t3
+# 114| 1: [ObjectCreation] object creation of type GenericClass
+# 114| 0: [TypeMention] GenericClass
+# 114| 1: [TypeMention] double
+# 115| 3: [LocalVariableDeclStmt] ... ...;
+# 115| 0: [LocalVariableDeclAndInitExpr] Int32 t4 = ...
+# 115| -1: [TypeMention] int
+# 115| 0: [LocalVariableAccess] access to local variable t4
+# 115| 1: [MethodCall] call to method GenericFn
+# 119| 18: [Class] GenericClass<>
+#-----| 1: (Type parameters)
+# 119| 0: [TypeParameter] T
+# 121| 5: [Field] f
+# 121| -1: [TypeMention] int
+# 125| 21: [Method] GenericFn
+# 125| -1: [TypeMention] int
+#-----| 1: (Type parameters)
+# 125| 0: [TypeParameter] T
+# 126| 4: [BlockStmt] {...}
+# 127| 0: [LocalVariableDeclStmt] ... ...;
+# 127| 0: [LocalVariableDeclAndInitExpr] Int32 x = ...
+# 127| -1: [TypeMention] int
+# 127| 0: [LocalVariableAccess] access to local variable x
+# 127| 1: [IntLiteral] 0
+# 128| 1: [ReturnStmt] return ...;
+# 128| 0: [IntLiteral] 0
+trivia.cs:
+# 14| [Class] Tr1
+# 16| 5: [Method] M1
+# 16| -1: [TypeMention] Void
+# 17| 4: [BlockStmt] {...}
+# 19| 0: [LocalVariableDeclStmt] ... ...;
+# 19| 0: [LocalVariableDeclExpr] Int32 i
+# 19| 0: [TypeMention] int
+# 20| 1: [LocalVariableDeclStmt] ... ...;
+# 20| 0: [LocalVariableDeclExpr] Int32 j
+# 20| 0: [TypeMention] int
+# 22| 2: [LocalVariableDeclStmt] ... ...;
+# 22| 0: [LocalVariableDeclExpr] Char c
+# 22| 0: [TypeMention] char
+# 24| 3: [LocalVariableDeclStmt] ... ...;
+# 24| 0: [LocalVariableDeclExpr] Single f
+# 24| 0: [TypeMention] float
+# 26| 4: [LocalVariableDeclStmt] ... ...;
+# 26| 0: [LocalVariableDeclExpr] String s
+# 26| 0: [TypeMention] string
+# 28| 5: [LocalVariableDeclStmt] ... ...;
+# 28| 0: [LocalVariableDeclExpr] Double d
+# 28| 0: [TypeMention] double
+# 32| [Class] Tr2
+# 34| 5: [Method] M1
+# 34| -1: [TypeMention] Void
+# 35| 4: [BlockStmt] {...}
+# 37| 0: [LocalVariableDeclStmt] ... ...;
+# 37| 0: [LocalVariableDeclExpr] Int32 i
+# 37| 0: [TypeMention] int
+# 39| 1: [LocalVariableDeclStmt] ... ...;
+# 39| 0: [LocalVariableDeclExpr] Int32 j
+# 39| 0: [TypeMention] int
+# 45| [Class] Tr3
+# 47| 5: [Method] M1
+# 47| -1: [TypeMention] Void
+# 48| 4: [BlockStmt] {...}
+# 61| [Class] Tr4
+# 63| 5: [Method] M1
+# 63| -1: [TypeMention] Void
+# 64| 4: [BlockStmt] {...}
+# 73| 0: [LocalVariableDeclStmt] ... ...;
+# 73| 0: [LocalVariableDeclAndInitExpr] Int32 i = ...
+# 73| -1: [TypeMention] int
+# 73| 0: [LocalVariableAccess] access to local variable i
+# 73| 1: [IntLiteral] 1
diff --git a/csharp/ql/test/library-tests/comments/PrintAst.qlref b/csharp/ql/test/library-tests/comments/PrintAst.qlref
new file mode 100644
index 00000000000..15af8b109dd
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/PrintAst.qlref
@@ -0,0 +1 @@
+semmle/code/csharp/PrintAst.ql
\ No newline at end of file
From a896e1522d240aaadf827e53673ecacd7209bfc1 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 20 Jan 2021 15:42:27 +0100
Subject: [PATCH 119/429] Extract active flag from directives, fix missing
assembly location
---
.../PreprocessorDirective.cs | 1 +
.../Semmle.Extraction.CSharp/Tuples.cs | 9 ++-
.../src/semmle/code/csharp/Preprocessor.qll | 6 +-
csharp/ql/src/semmlecode.csharp.dbscheme | 4 ++
.../comments/BindingAfter.expected | 2 +-
.../library-tests/comments/Comments.expected | 2 +-
.../comments/Directives.expected | 60 +++++++++++++++++++
.../test/library-tests/comments/Directives.ql | 6 ++
.../comments/ErrorDirectives.expected | 2 +-
.../comments/WarningDirectives.expected | 2 +-
.../ql/test/library-tests/comments/trivia.cs | 6 +-
11 files changed, 90 insertions(+), 10 deletions(-)
create mode 100644 csharp/ql/test/library-tests/comments/Directives.expected
create mode 100644 csharp/ql/test/library-tests/comments/Directives.ql
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
index e19ffe43549..2a968d7fa46 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
@@ -19,6 +19,7 @@ namespace Semmle.Extraction.CSharp.Entities
{
PopulatePreprocessor(trapFile);
+ trapFile.preprocessor_directive_active(this, trivia.IsActive);
trapFile.preprocessor_directive_location(this, cx.Create(ReportingLocation));
if (!cx.Extractor.Standalone)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 19b730f7cc0..46609d38bde 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -607,6 +607,13 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("preprocessor_directive_assembly", directive, assembly);
}
+ internal static void preprocessor_directive_active(this TextWriter trapFile,
+ PreprocessorDirective directive, bool isActive)
+ where TDirective : DirectiveTriviaSyntax
+ {
+ trapFile.WriteTuple("preprocessor_directive_active", directive, isActive ? 1 : 0);
+ }
+
internal static void pragma_warnings(this TextWriter trapFile, PragmaWarningDirective pragma, int kind)
{
trapFile.WriteTuple("pragma_warnings", pragma, kind);
@@ -706,7 +713,5 @@ namespace Semmle.Extraction.CSharp
{
trapFile.WriteTuple("directive_define_symbols", symb, name);
}
-
-
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 1f23275a7a7..5b44fd3f30f 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -5,7 +5,11 @@
import Element
class PreprocessorDirective extends Element, @preprocessor_directive {
- override Location getALocation() { preprocessor_directive_location(this, result) }
+ predicate active() { preprocessor_directive_active(this, 1) }
+
+ override Location getALocation() {
+ preprocessor_directive_location(this, result) or preprocessor_directive_assembly(this, result)
+ }
}
/**
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index a13a6f6d628..a904422305b 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -435,6 +435,10 @@ preprocessor_directive_assembly(
unique int id: @preprocessor_directive ref,
int loc: @assembly ref);
+preprocessor_directive_active(
+ unique int id: @preprocessor_directive ref,
+ int active: int ref); /* 0: false, 1: true */
+
/** TYPES **/
types(
diff --git a/csharp/ql/test/library-tests/comments/BindingAfter.expected b/csharp/ql/test/library-tests/comments/BindingAfter.expected
index a44d0360f73..df5cd92ade0 100644
--- a/csharp/ql/test/library-tests/comments/BindingAfter.expected
+++ b/csharp/ql/test/library-tests/comments/BindingAfter.expected
@@ -55,6 +55,6 @@
| comments2.cs:118:5:118:21 | // ... | comments2.cs:119:11:119:25 | GenericClass<> | GenericClass<> |
| comments2.cs:124:5:124:16 | // ... | comments2.cs:125:9:125:20 | GenericFn | GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:128:9:128:17 | return ...; | x |
-| trivia.cs:1:1:3:15 | // ... | trivia.cs:14:7:14:9 | Tr1 | semmle-extractor-options: --standalone |
+| trivia.cs:1:1:3:15 | // ... | trivia.cs:14:7:14:9 | Tr1 | |
| trivia.cs:13:84:13:98 | // ... | trivia.cs:14:7:14:9 | Tr1 | New checksum |
| trivia.cs:25:14:25:38 | // ... | trivia.cs:26:9:26:17 | ... ...; | numbering not affected |
diff --git a/csharp/ql/test/library-tests/comments/Comments.expected b/csharp/ql/test/library-tests/comments/Comments.expected
index 176211192a6..a6df0ef7bc6 100644
--- a/csharp/ql/test/library-tests/comments/Comments.expected
+++ b/csharp/ql/test/library-tests/comments/Comments.expected
@@ -70,7 +70,7 @@ singlelineComment
| comments2.cs:124:5:124:16 | // ... | comments2.cs:124:5:124:16 | // ... | 1 | GenericFn | // GenericFn |
| comments2.cs:127:20:127:23 | // ... | comments2.cs:127:20:127:23 | // ... | 1 | x | // x |
| comments2.cs:132:1:132:21 | // ... | comments2.cs:132:1:132:21 | // ... | 1 | End of comment2.cs | // End of comment2.cs |
-| trivia.cs:1:1:3:15 | // ... | trivia.cs:1:1:1:42 | // ... | 3 | semmle-extractor-options: --standalone | // semmle-extractor-options: --standalone |
+| trivia.cs:1:1:3:15 | // ... | trivia.cs:1:1:1:2 | // ... | 3 | | // |
| trivia.cs:1:1:3:15 | // ... | trivia.cs:2:1:2:21 | // ... | 3 | Start of trivia.cs | // Start of trivia.cs |
| trivia.cs:1:1:3:15 | // ... | trivia.cs:3:1:3:15 | // ... | 3 | Unassociated | // Unassociated |
| trivia.cs:13:84:13:98 | // ... | trivia.cs:13:84:13:98 | // ... | 1 | New checksum | // New checksum |
diff --git a/csharp/ql/test/library-tests/comments/Directives.expected b/csharp/ql/test/library-tests/comments/Directives.expected
new file mode 100644
index 00000000000..2fa3a1acae3
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/Directives.expected
@@ -0,0 +1,60 @@
+| trivia.cs:4:1:4:13 | #define ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:4:1:4:13 | #define ... | trivia.cs:4:1:4:13 | trivia.cs:4:1:4:13 | active |
+| trivia.cs:6:1:6:12 | #undef ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:6:1:6:12 | #undef ... | trivia.cs:6:1:6:12 | trivia.cs:6:1:6:12 | active |
+| trivia.cs:12:1:12:35 | #pragma warning ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:12:1:12:35 | #pragma warning ... | trivia.cs:12:1:12:35 | trivia.cs:12:1:12:35 | active |
+| trivia.cs:13:1:13:98 | #pragma checksum ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:13:1:13:98 | #pragma checksum ... | trivia.cs:13:1:13:98 | trivia.cs:13:1:13:98 | active |
+| trivia.cs:18:1:18:19 | #line ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:18:1:18:19 | #line ... | trivia.cs:18:1:18:19 | trivia.cs:18:1:18:19 | active |
+| trivia.cs:21:1:21:13 | #line default | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:21:1:21:13 | #line default | trivia.cs:21:1:21:13 | trivia.cs:21:1:21:13 | active |
+| trivia.cs:23:1:23:23 | #pragma warning ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:23:1:23:23 | #pragma warning ... | trivia.cs:23:1:23:23 | trivia.cs:23:1:23:23 | active |
+| trivia.cs:25:1:25:38 | #line hidden | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:25:1:25:38 | #line hidden | trivia.cs:25:1:25:38 | trivia.cs:25:1:25:38 | active |
+| trivia.cs:27:1:27:9 | #line ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:27:1:27:9 | #line ... | trivia.cs:27:1:27:9 | trivia.cs:27:1:27:9 | active |
+| trivia.cs:36:9:36:22 | #region ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:36:9:36:22 | #region ... | trivia.cs:36:9:36:22 | trivia.cs:36:9:36:22 | active |
+| trivia.cs:38:9:38:22 | #region ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:38:9:38:22 | #region ... | trivia.cs:38:9:38:22 | trivia.cs:38:9:38:22 | active |
+| trivia.cs:40:9:40:18 | #endregion | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:40:9:40:18 | #endregion | trivia.cs:40:9:40:18 | trivia.cs:40:9:40:18 | active |
+| trivia.cs:41:9:41:18 | #endregion | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:41:9:41:18 | #endregion | trivia.cs:41:9:41:18 | trivia.cs:41:9:41:18 | active |
+| trivia.cs:49:1:49:82 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:49:1:49:82 | #nullable ... | trivia.cs:49:1:49:82 | trivia.cs:49:1:49:82 | active |
+| trivia.cs:50:1:50:80 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:50:1:50:80 | #nullable ... | trivia.cs:50:1:50:80 | trivia.cs:50:1:50:80 | active |
+| trivia.cs:51:1:51:94 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:51:1:51:94 | #nullable ... | trivia.cs:51:1:51:94 | trivia.cs:51:1:51:94 | active |
+| trivia.cs:52:1:52:81 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:52:1:52:81 | #nullable ... | trivia.cs:52:1:52:81 | trivia.cs:52:1:52:81 | active |
+| trivia.cs:53:1:53:79 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:53:1:53:79 | #nullable ... | trivia.cs:53:1:53:79 | trivia.cs:53:1:53:79 | active |
+| trivia.cs:54:1:54:93 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:54:1:54:93 | #nullable ... | trivia.cs:54:1:54:93 | trivia.cs:54:1:54:93 | active |
+| trivia.cs:55:1:55:75 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:55:1:55:75 | #nullable ... | trivia.cs:55:1:55:75 | trivia.cs:55:1:55:75 | active |
+| trivia.cs:56:1:56:73 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:56:1:56:73 | #nullable ... | trivia.cs:56:1:56:73 | trivia.cs:56:1:56:73 | active |
+| trivia.cs:57:1:57:87 | #nullable ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:57:1:57:87 | #nullable ... | trivia.cs:57:1:57:87 | trivia.cs:57:1:57:87 | active |
+| trivia.cs:65:1:65:9 | #if ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:65:1:65:9 | #if ... | trivia.cs:65:1:65:9 | trivia.cs:65:1:65:9 | active |
+| trivia.cs:66:1:66:23 | #error ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | inactive |
+| trivia.cs:66:1:66:23 | #error ... | trivia.cs:66:1:66:23 | trivia.cs:66:1:66:23 | inactive |
+| trivia.cs:68:1:68:10 | #if ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | inactive |
+| trivia.cs:68:1:68:10 | #if ... | trivia.cs:68:1:68:10 | trivia.cs:68:1:68:10 | inactive |
+| trivia.cs:70:1:70:6 | #endif | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | inactive |
+| trivia.cs:70:1:70:6 | #endif | trivia.cs:70:1:70:6 | trivia.cs:70:1:70:6 | inactive |
+| trivia.cs:71:1:71:35 | #elif ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:71:1:71:35 | #elif ... | trivia.cs:71:1:71:35 | trivia.cs:71:1:71:35 | active |
+| trivia.cs:72:1:72:43 | #warning ... | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:72:1:72:43 | #warning ... | trivia.cs:72:1:72:43 | trivia.cs:72:1:72:43 | active |
+| trivia.cs:74:1:74:5 | #else | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:74:1:74:5 | #else | trivia.cs:74:1:74:5 | trivia.cs:74:1:74:5 | active |
+| trivia.cs:76:1:76:6 | #endif | comments2.dll:0:0:0:0 | comments2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null | active |
+| trivia.cs:76:1:76:6 | #endif | trivia.cs:76:1:76:6 | trivia.cs:76:1:76:6 | active |
diff --git a/csharp/ql/test/library-tests/comments/Directives.ql b/csharp/ql/test/library-tests/comments/Directives.ql
new file mode 100644
index 00000000000..606ffd042bb
--- /dev/null
+++ b/csharp/ql/test/library-tests/comments/Directives.ql
@@ -0,0 +1,6 @@
+import csharp
+
+query predicate directives(PreprocessorDirective d, Location l, string isActive) {
+ d.getALocation() = l and
+ if d.active() then isActive = "active" else isActive = "inactive"
+}
diff --git a/csharp/ql/test/library-tests/comments/ErrorDirectives.expected b/csharp/ql/test/library-tests/comments/ErrorDirectives.expected
index 656b501c904..e3fb9babaf0 100644
--- a/csharp/ql/test/library-tests/comments/ErrorDirectives.expected
+++ b/csharp/ql/test/library-tests/comments/ErrorDirectives.expected
@@ -1 +1 @@
-| trivia.cs:72:1:72:41 | #error ... | NOTDEBUG is defined or TEST is not |
+| trivia.cs:66:1:66:23 | #error ... | DEBUG is defined |
diff --git a/csharp/ql/test/library-tests/comments/WarningDirectives.expected b/csharp/ql/test/library-tests/comments/WarningDirectives.expected
index 105119a8127..b4ed32ea179 100644
--- a/csharp/ql/test/library-tests/comments/WarningDirectives.expected
+++ b/csharp/ql/test/library-tests/comments/WarningDirectives.expected
@@ -1 +1 @@
-| trivia.cs:66:1:66:25 | #warning ... | DEBUG is defined |
+| trivia.cs:72:1:72:43 | #warning ... | NOTDEBUG is defined or TEST is not |
diff --git a/csharp/ql/test/library-tests/comments/trivia.cs b/csharp/ql/test/library-tests/comments/trivia.cs
index 69aea425499..61cfe454409 100644
--- a/csharp/ql/test/library-tests/comments/trivia.cs
+++ b/csharp/ql/test/library-tests/comments/trivia.cs
@@ -1,4 +1,4 @@
-// semmle-extractor-options: --standalone
+//
// Start of trivia.cs
// Unassociated
#define DEBUG
@@ -63,13 +63,13 @@ class Tr4
static void M1()
{
#if DEBUG
-#warning DEBUG is defined
+#error DEBUG is defined
var i = 0;
#if NESTED
i--;
#endif
#elif (NOTDEBUG == true) || !(TEST)
-#error NOTDEBUG is defined or TEST is not
+#warning NOTDEBUG is defined or TEST is not
var i = 1;
#else
var i = 2;
From 3900698b41571635cf9da85a2bda21d1c1cb852a Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Thu, 21 Jan 2021 11:28:23 +0100
Subject: [PATCH 120/429] Add doc comments for preprocessor directive base
class
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 5b44fd3f30f..df33fc746e0 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -4,7 +4,17 @@
import Element
+/**
+ * A preprocessor directive, such as `PragmaWarningDirective`, `PragmaChecksumDirective`,
+ * `DefineDirective`, `UndefineDirective`, `WarningDirective`, `ErrorDirective`,
+ * `NullableDirective`, `LineDirective`, `RegionDirective`, `EndRegionDirective`,
+ * `BranchDirective`, or `EndifDirective`.
+ */
class PreprocessorDirective extends Element, @preprocessor_directive {
+ /**
+ * Holds if this directive is processed by the preprocessor, such as any directive
+ * that is not inside a not taken `BranchDirective`.
+ */
predicate active() { preprocessor_directive_active(this, 1) }
override Location getALocation() {
From bd64dda4c393db0fafa2ec2271e242477ab7fe7b Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 15:34:49 +0100
Subject: [PATCH 121/429] Fix code review findings in pragma warning directives
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 6 +++---
csharp/ql/test/library-tests/comments/PragmaWarnings.ql | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index df33fc746e0..b22058c05eb 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -27,13 +27,13 @@ class PreprocessorDirective extends Element, @preprocessor_directive {
*/
class PragmaWarningDirective extends PreprocessorDirective, @pragma_warning {
/** Holds if this is a `#pragma warning restore` directive. */
- predicate restore() { pragma_warnings(this, 1) }
+ predicate isRestore() { pragma_warnings(this, 1) }
/** Holds if this is a `#pragma warning disable` directive. */
- predicate disable() { pragma_warnings(this, 0) }
+ predicate isDisable() { pragma_warnings(this, 0) }
/** Holds if this directive specifies error codes. */
- predicate hasErrorCodes() { exists(string s | pragma_warning_error_codes(this, s, _)) }
+ predicate hasErrorCodes() { pragma_warning_error_codes(this, _, _) }
/** Gets a specified error code from this directive. */
string getAnErrorCode() { pragma_warning_error_codes(this, result, _) }
diff --git a/csharp/ql/test/library-tests/comments/PragmaWarnings.ql b/csharp/ql/test/library-tests/comments/PragmaWarnings.ql
index 15d793b6392..58ce589474e 100644
--- a/csharp/ql/test/library-tests/comments/PragmaWarnings.ql
+++ b/csharp/ql/test/library-tests/comments/PragmaWarnings.ql
@@ -1,8 +1,8 @@
import csharp
-query predicate disable(PragmaWarningDirective pragma) { pragma.disable() }
+query predicate disable(PragmaWarningDirective pragma) { pragma.isDisable() }
-query predicate restore(PragmaWarningDirective pragma) { pragma.restore() }
+query predicate restore(PragmaWarningDirective pragma) { pragma.isRestore() }
query predicate errorCodes(PragmaWarningDirective pragma, string code) {
pragma.hasErrorCodes() and code = pragma.getAnErrorCode()
From 567516471c2a53d81503c64f1c2e1f0d938f006d Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 15:37:05 +0100
Subject: [PATCH 122/429] Fix code review findings in 'define' directives
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index b22058c05eb..d747c870296 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -62,7 +62,7 @@ class PragmaChecksumDirective extends PreprocessorDirective, @pragma_checksum {
}
/**
- * An `#define` directive.
+ * A `#define` directive.
*/
class DefineDirective extends PreprocessorDirective, @directive_define {
/** Gets the name of the preprocessor symbol that is being set by this directive. */
From f7832adfb8762d99ff18de1091ed9a43ef433254 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 15:38:34 +0100
Subject: [PATCH 123/429] Fix code review findings in 'nullable' directives
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index d747c870296..416082596b1 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -114,13 +114,13 @@ class ErrorDirective extends PreprocessorDirective, @directive_error {
*/
class NullableDirective extends PreprocessorDirective, @directive_nullable {
/** Holds if this is a `#nullable disable` directive. */
- predicate disable() { directive_nullables(this, 0, _) }
+ predicate isDisable() { directive_nullables(this, 0, _) }
/** Holds if this is a `#nullable enable` directive. */
- predicate enable() { directive_nullables(this, 1, _) }
+ predicate isEnable() { directive_nullables(this, 1, _) }
/** Holds if this is a `#nullable restore` directive. */
- predicate restore() { directive_nullables(this, 2, _) }
+ predicate isRestore() { directive_nullables(this, 2, _) }
/** Holds if this directive targets all nullable contexts. */
predicate targetsAll() { directive_nullables(this, _, 0) }
@@ -134,7 +134,7 @@ class NullableDirective extends PreprocessorDirective, @directive_nullable {
/** Gets the succeeding `#nullable` directive in the file, if any. */
NullableDirective getSuccNullableDirective() {
result =
- rank[1](NullableDirective next |
+ min(NullableDirective next |
next.getFile() = this.getFile() and
next.getLocation().getStartLine() > this.getLocation().getStartLine()
|
From 6ef8e51bcfc6307bc75c3ca477538c7e63b2c206 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 15:58:25 +0100
Subject: [PATCH 124/429] Fix code review findings in 'line' directives
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 8 ++++----
.../test/library-tests/comments/LineDirectives.expected | 2 +-
csharp/ql/test/library-tests/comments/LineDirectives.ql | 3 ++-
3 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 416082596b1..e391fccbb6c 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -163,7 +163,7 @@ class LineDirective extends PreprocessorDirective, @directive_line {
/** Gets the succeeding `#line` directive in the file, if any. */
LineDirective getSuccLineDirective() {
result =
- rank[1](LineDirective next |
+ min(LineDirective next |
next.getFile() = this.getFile() and
next.getLocation().getStartLine() > this.getLocation().getStartLine()
|
@@ -216,10 +216,10 @@ class NumericLineDirective extends LineDirective {
int getLine() { directive_line_values(this, result, _) }
/** Holds if this directive specifies a file name. */
- predicate hasFileName() { this.getFileName() != "" }
+ predicate hasFileName() { exists(this.getFileName()) }
- /** Gets the file name of this directive. */
- string getFileName() { directive_line_values(this, _, result) }
+ /** Gets the file name of this directive, if any. */
+ string getFileName() { directive_line_values(this, _, result) and result != "" }
override string getAPrimaryQlClass() { result = "NumericLineDirective" }
}
diff --git a/csharp/ql/test/library-tests/comments/LineDirectives.expected b/csharp/ql/test/library-tests/comments/LineDirectives.expected
index ae79e53d1b4..c8be4e27890 100644
--- a/csharp/ql/test/library-tests/comments/LineDirectives.expected
+++ b/csharp/ql/test/library-tests/comments/LineDirectives.expected
@@ -4,7 +4,7 @@ hidden
| trivia.cs:25:1:25:38 | #line hidden |
lines
| trivia.cs:18:1:18:19 | #line ... | 200 | Special |
-| trivia.cs:27:1:27:9 | #line ... | 300 | |
+| trivia.cs:27:1:27:9 | #line ... | 300 | no file |
succ
| trivia.cs:18:1:18:19 | #line ... | trivia.cs:21:1:21:13 | #line default |
| trivia.cs:21:1:21:13 | #line default | trivia.cs:25:1:25:38 | #line hidden |
diff --git a/csharp/ql/test/library-tests/comments/LineDirectives.ql b/csharp/ql/test/library-tests/comments/LineDirectives.ql
index db05488911a..4fa2adedb9d 100644
--- a/csharp/ql/test/library-tests/comments/LineDirectives.ql
+++ b/csharp/ql/test/library-tests/comments/LineDirectives.ql
@@ -5,7 +5,8 @@ query predicate default(DefaultLineDirective line) { any() }
query predicate hidden(HiddenLineDirective line) { any() }
query predicate lines(NumericLineDirective line, int l, string file) {
- line.getLine() = l and line.getFileName() = file
+ line.getLine() = l and
+ if line.hasFileName() then line.getFileName() = file else file = "no file"
}
query predicate succ(LineDirective d, LineDirective succ) { d.getSuccLineDirective() = succ }
From 60b23dc505e90c779c49477e589c99a867b864a6 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 16:03:43 +0100
Subject: [PATCH 125/429] Fix code review findings in 'endregion' directives
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 4 ++--
.../ql/test/library-tests/comments/RegionDirectives.expected | 4 ----
csharp/ql/test/library-tests/comments/RegionDirectives.ql | 2 --
3 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index e391fccbb6c..090ee91b618 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -240,11 +240,11 @@ class RegionDirective extends PreprocessorDirective, @directive_region {
}
/**
- * A `#endregion` directive.
+ * An `#endregion` directive.
*/
class EndRegionDirective extends PreprocessorDirective, @directive_endregion {
/** Gets the opening `#region` directive. */
- RegionDirective getStart() { regions(result, this) }
+ RegionDirective getStart() { result.getEnd() = this }
override string toString() { result = "#endregion" }
diff --git a/csharp/ql/test/library-tests/comments/RegionDirectives.expected b/csharp/ql/test/library-tests/comments/RegionDirectives.expected
index aead68e7ff0..9ede79514b6 100644
--- a/csharp/ql/test/library-tests/comments/RegionDirectives.expected
+++ b/csharp/ql/test/library-tests/comments/RegionDirectives.expected
@@ -1,6 +1,2 @@
-regionDirectives
| trivia.cs:36:9:36:22 | #region ... | fields | trivia.cs:41:9:41:18 | #endregion |
| trivia.cs:38:9:38:22 | #region ... | nested | trivia.cs:40:9:40:18 | #endregion |
-endregions
-| trivia.cs:40:9:40:18 | #endregion | trivia.cs:38:9:38:22 | #region ... |
-| trivia.cs:41:9:41:18 | #endregion | trivia.cs:36:9:36:22 | #region ... |
diff --git a/csharp/ql/test/library-tests/comments/RegionDirectives.ql b/csharp/ql/test/library-tests/comments/RegionDirectives.ql
index 9b1296819f2..ea4c7cab4ec 100644
--- a/csharp/ql/test/library-tests/comments/RegionDirectives.ql
+++ b/csharp/ql/test/library-tests/comments/RegionDirectives.ql
@@ -4,5 +4,3 @@ query predicate regionDirectives(RegionDirective d, string name, EndRegionDirect
d.getName() = name and
d.getEnd() = end
}
-
-query predicate endregions(EndRegionDirective d, RegionDirective start) { d.getStart() = start }
From e450b614648d42b4c64d51591d8a2cc36e577f91 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 16:09:45 +0100
Subject: [PATCH 126/429] Fix code review findings in directives base class
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 2 +-
csharp/ql/test/library-tests/comments/Directives.ql | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 090ee91b618..11a2da1d4a5 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -15,7 +15,7 @@ class PreprocessorDirective extends Element, @preprocessor_directive {
* Holds if this directive is processed by the preprocessor, such as any directive
* that is not inside a not taken `BranchDirective`.
*/
- predicate active() { preprocessor_directive_active(this, 1) }
+ predicate isActive() { preprocessor_directive_active(this, 1) }
override Location getALocation() {
preprocessor_directive_location(this, result) or preprocessor_directive_assembly(this, result)
diff --git a/csharp/ql/test/library-tests/comments/Directives.ql b/csharp/ql/test/library-tests/comments/Directives.ql
index 606ffd042bb..3af699b9a0c 100644
--- a/csharp/ql/test/library-tests/comments/Directives.ql
+++ b/csharp/ql/test/library-tests/comments/Directives.ql
@@ -2,5 +2,5 @@ import csharp
query predicate directives(PreprocessorDirective d, Location l, string isActive) {
d.getALocation() = l and
- if d.active() then isActive = "active" else isActive = "inactive"
+ if d.isActive() then isActive = "active" else isActive = "inactive"
}
From 2b7cc1575765bd69130ffb0ed0cbc5a11a20905f Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 16:12:56 +0100
Subject: [PATCH 127/429] Introduce base class for branching and conditional
directives
---
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 4 ++--
csharp/ql/src/semmlecode.csharp.dbscheme | 3 +++
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 11a2da1d4a5..8ed13ce0426 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -255,7 +255,7 @@ class EndRegionDirective extends PreprocessorDirective, @directive_endregion {
* A branching preprocessor directive, such as `IfDirective`, `ElifDirective`, or
* `ElseDirective`.
*/
-class BranchDirective extends PreprocessorDirective {
+class BranchDirective extends PreprocessorDirective, @branch_directive {
/** Holds if the branch is taken by the preprocessor. */
predicate branchTaken() { none() }
}
@@ -264,7 +264,7 @@ class BranchDirective extends PreprocessorDirective {
* A preprocessor directive with a branching condition, such as `IfDirective` or
* `ElifDirective`.
*/
-class ConditionalDirective extends BranchDirective {
+class ConditionalDirective extends BranchDirective, @conditional_directive {
/** Gets the condition. */
Expr getCondition() { result = this.getChild(0) }
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index a904422305b..b85e3971657 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -335,6 +335,9 @@ using_directive_location(
| @directive_error | @directive_nullable | @directive_line | @directive_region | @directive_endregion | @directive_if
| @directive_elif | @directive_else | @directive_endif;
+@conditional_directive = @directive_if | @directive_elif;
+@branch_directive = @directive_if | @directive_elif | @directive_else;
+
directive_ifs(
unique int id: @directive_if,
int branchTaken: int ref, /* 0: false, 1: true */
From a5dec5b4aa754ea0e0bb72d7819ccd76d6fabcb6 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 16:32:05 +0100
Subject: [PATCH 128/429] C#: Limit ancestor traversal for 'if' and 'elif'
lookup
---
.../Entities/Expression.cs | 28 ++++++-------------
.../Entities/Expressions/Literal.cs | 3 +-
.../Entities/Expressions/Name.cs | 15 ++++++----
3 files changed, 19 insertions(+), 27 deletions(-)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
index 5829bbffa9e..6c738da3af1 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expression.cs
@@ -534,7 +534,7 @@ namespace Semmle.Extraction.CSharp.Entities
return Expression.ValueAsString(c.Value);
}
- if (TryGetBoolValueInsideIfDirective(out var val))
+ if (TryGetBoolValueFromLiteral(out var val))
{
return Expression.ValueAsString(val);
}
@@ -604,30 +604,18 @@ namespace Semmle.Extraction.CSharp.Entities
public NullableFlowState FlowState => TypeInfo.Nullability.FlowState;
- public bool IsInsideIfDirective()
- {
- return Node.Ancestors().Any(a => a is ElifDirectiveTriviaSyntax || a is IfDirectiveTriviaSyntax);
- }
-
- public bool TryGetBoolValueInsideIfDirective(out bool val)
+ private bool TryGetBoolValueFromLiteral(out bool val)
{
var isTrue = Node.IsKind(SyntaxKind.TrueLiteralExpression);
var isFalse = Node.IsKind(SyntaxKind.FalseLiteralExpression);
- if (!isTrue && !isFalse)
- {
- val = false;
- return false;
- }
-
- if (!IsInsideIfDirective())
- {
- val = false;
- return false;
- }
-
val = isTrue;
- return true;
+ return isTrue || isFalse;
+ }
+
+ public bool IsBoolLiteral()
+ {
+ return TryGetBoolValueFromLiteral(out var _);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs
index d91f426cfd7..51acebef5c8 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Literal.cs
@@ -25,7 +25,8 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
return ExprKind.NULL_LITERAL;
}
- if (info.TryGetBoolValueInsideIfDirective(out var _))
+ // short circuit bool literals, because they have no type in `#if A = true`
+ if (info.IsBoolLiteral())
{
return ExprKind.BOOL_LITERAL;
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs
index d74afd16329..faf5c9e2bd1 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Expressions/Name.cs
@@ -27,15 +27,13 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
if (target == null)
{
- if (info.IsInsideIfDirective())
+ if (IsInsideIfDirective(info.Node))
{
return DefineSymbol.Create(info);
}
- else
- {
- info.Context.ModelError(info.Node, "Failed to resolve name");
- return new Unknown(info);
- }
+
+ info.Context.ModelError(info.Node, "Failed to resolve name");
+ return new Unknown(info);
}
// There is a very strange bug in Microsoft.CodeAnalysis whereby
@@ -72,5 +70,10 @@ namespace Semmle.Extraction.CSharp.Entities.Expressions
throw new InternalError(info.Node, $"Unhandled identifier kind '{target.Kind}'");
}
}
+
+ private static bool IsInsideIfDirective(ExpressionSyntax node)
+ {
+ return node.Ancestors().Any(a => a is ElifDirectiveTriviaSyntax || a is IfDirectiveTriviaSyntax);
+ }
}
}
From 72547b89e60f4494f8c67a68e7b23b0275aa4ff7 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Tue, 26 Jan 2021 16:56:46 +0100
Subject: [PATCH 129/429] Rework endregion extraction
---
.../PreprocessorDirectives/EndRegionDirective.cs | 15 +++++++--------
.../PreprocessorDirective.cs | 7 +++++--
.../Populators/DirectiveVisitor.cs | 4 ++--
.../extractor/Semmle.Extraction.CSharp/Tuples.cs | 4 ++--
csharp/ql/src/semmle/code/csharp/Preprocessor.qll | 2 +-
csharp/ql/src/semmlecode.csharp.dbscheme | 9 +++------
6 files changed, 20 insertions(+), 21 deletions(-)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
index a320c89028c..d4d75470a97 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndRegionDirective.cs
@@ -5,19 +5,18 @@ namespace Semmle.Extraction.CSharp.Entities
{
internal class EndRegionDirective : PreprocessorDirective
{
- public EndRegionDirective(Context cx, EndRegionDirectiveTriviaSyntax trivia)
- : base(cx, trivia)
+ private readonly RegionDirective region;
+
+ public EndRegionDirective(Context cx, EndRegionDirectiveTriviaSyntax trivia, RegionDirective region)
+ : base(cx, trivia, populateFromBase: false)
{
+ this.region = region;
+ TryPopulate();
}
protected override void PopulatePreprocessor(TextWriter trapFile)
{
- trapFile.directive_endregions(this);
- }
-
- internal static void WriteRegionBlock(Context cx, RegionDirective region, EndRegionDirective endregion)
- {
- cx.TrapWriter.Writer.regions(region, endregion);
+ trapFile.directive_endregions(this, region);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
index 2a968d7fa46..62c70b3360e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/PreprocessorDirective.cs
@@ -8,11 +8,14 @@ namespace Semmle.Extraction.CSharp.Entities
{
protected readonly TDirective trivia;
- protected PreprocessorDirective(Context cx, TDirective trivia)
+ protected PreprocessorDirective(Context cx, TDirective trivia, bool populateFromBase = true)
: base(cx)
{
this.trivia = trivia;
- TryPopulate();
+ if (populateFromBase)
+ {
+ TryPopulate();
+ }
}
protected sealed override void Populate(TextWriter trapFile)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index 074441a70cb..f0044e891bc 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -65,15 +65,15 @@ namespace Semmle.Extraction.CSharp.Populators
public override void VisitEndRegionDirectiveTrivia(EndRegionDirectiveTriviaSyntax node)
{
- var endregion = new Entities.EndRegionDirective(cx, node);
if (regionStarts.Count == 0)
{
cx.ExtractionError("Couldn't find start region", null,
Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
return;
}
+
var start = regionStarts.Pop();
- Entities.EndRegionDirective.WriteRegionBlock(cx, start, endregion);
+ new Entities.EndRegionDirective(cx, node, start);
}
private readonly Stack ifStarts = new Stack();
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 46609d38bde..95f5db2d744 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -669,9 +669,9 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("directive_regions", directive, name);
}
- internal static void directive_endregions(this TextWriter trapFile, EndRegionDirective directive)
+ internal static void directive_endregions(this TextWriter trapFile, EndRegionDirective directive, RegionDirective start)
{
- trapFile.WriteTuple("directive_endregions", directive);
+ trapFile.WriteTuple("directive_endregions", directive, start);
}
internal static void regions(this TextWriter trapFile, RegionDirective start, EndRegionDirective end)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 8ed13ce0426..77c6bd199bb 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -232,7 +232,7 @@ class RegionDirective extends PreprocessorDirective, @directive_region {
string getName() { directive_regions(this, result) }
/** Gets the closing `#endregion` directive. */
- EndRegionDirective getEnd() { regions(this, result) }
+ EndRegionDirective getEnd() { directive_endregions(result, this) }
override string toString() { result = "#region ..." }
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index b85e3971657..6dd5048ac60 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -376,13 +376,10 @@ directive_regions(
unique int id: @directive_region,
string name: string ref);
+#keyset[id, start]
directive_endregions(
- unique int id: @directive_endregion);
-
-#keyset[start, end]
-regions(
- unique int start: @directive_region ref,
- unique int end: @directive_endregion ref);
+ unique int id: @directive_endregion,
+ unique int start: @directive_region ref);
directive_lines(
unique int id: @directive_line,
From 1ab4af275d1abc131af11b68ea54fc25b647028e Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 27 Jan 2021 09:24:25 +0100
Subject: [PATCH 130/429] Rework if/elif/else/endif extraction
---
.../PreprocessorDirectives/ElifDirective.cs | 12 ++++++--
.../PreprocessorDirectives/ElseDirective.cs | 12 ++++++--
.../PreprocessorDirectives/EndIfDirective.cs | 10 +++++--
.../PreprocessorDirectives/IfDirective.cs | 18 ------------
.../Populators/DirectiveVisitor.cs | 28 +++++++++++++------
.../Semmle.Extraction.CSharp/Tuples.cs | 24 ++++++----------
.../src/semmle/code/csharp/Preprocessor.qll | 22 +++++++++------
csharp/ql/src/semmlecode.csharp.dbscheme | 27 +++++++-----------
8 files changed, 76 insertions(+), 77 deletions(-)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
index 26314244ac6..ace89464b96 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElifDirective.cs
@@ -5,16 +5,22 @@ namespace Semmle.Extraction.CSharp.Entities
{
internal class ElifDirective : PreprocessorDirective, IIfSiblingDirective, IExpressionParentEntity
{
- public ElifDirective(Context cx, ElifDirectiveTriviaSyntax trivia)
- : base(cx, trivia)
+ private readonly IfDirective start;
+ private readonly int index;
+
+ public ElifDirective(Context cx, ElifDirectiveTriviaSyntax trivia, IfDirective start, int index)
+ : base(cx, trivia, populateFromBase: false)
{
+ this.start = start;
+ this.index = index;
+ TryPopulate();
}
public bool IsTopLevelParent => true;
protected override void PopulatePreprocessor(TextWriter trapFile)
{
- trapFile.directive_elifs(this, trivia.BranchTaken, trivia.ConditionValue);
+ trapFile.directive_elifs(this, trivia.BranchTaken, trivia.ConditionValue, start, index);
Expression.Create(cx, trivia.Condition, this, 0);
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
index 48c786627ac..7ab7d45b6e9 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/ElseDirective.cs
@@ -5,14 +5,20 @@ namespace Semmle.Extraction.CSharp.Entities
{
internal class ElseDirective : PreprocessorDirective, IIfSiblingDirective
{
- public ElseDirective(Context cx, ElseDirectiveTriviaSyntax trivia)
- : base(cx, trivia)
+ private readonly IfDirective start;
+ private readonly int index;
+
+ public ElseDirective(Context cx, ElseDirectiveTriviaSyntax trivia, IfDirective start, int index)
+ : base(cx, trivia, populateFromBase: false)
{
+ this.start = start;
+ this.index = index;
+ TryPopulate();
}
protected override void PopulatePreprocessor(TextWriter trapFile)
{
- trapFile.directive_elses(this, trivia.BranchTaken);
+ trapFile.directive_elses(this, trivia.BranchTaken, start, index);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
index 85d5c89f353..9c349844dc6 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/EndIfDirective.cs
@@ -5,14 +5,18 @@ namespace Semmle.Extraction.CSharp.Entities
{
internal class EndIfDirective : PreprocessorDirective
{
- public EndIfDirective(Context cx, EndIfDirectiveTriviaSyntax trivia)
- : base(cx, trivia)
+ private readonly IfDirective start;
+
+ public EndIfDirective(Context cx, EndIfDirectiveTriviaSyntax trivia, IfDirective start)
+ : base(cx, trivia, populateFromBase: false)
{
+ this.start = start;
+ TryPopulate();
}
protected override void PopulatePreprocessor(TextWriter trapFile)
{
- trapFile.directive_endifs(this);
+ trapFile.directive_endifs(this, start);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
index fc9af188c1d..e1b81e60d8a 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/PreprocessorDirectives/IfDirective.cs
@@ -1,13 +1,10 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using System.Collections.Generic;
using System.IO;
namespace Semmle.Extraction.CSharp.Entities
{
internal class IfDirective : PreprocessorDirective, IExpressionParentEntity
{
- private readonly List branches = new List();
-
public IfDirective(Context cx, IfDirectiveTriviaSyntax trivia)
: base(cx, trivia)
{
@@ -21,20 +18,5 @@ namespace Semmle.Extraction.CSharp.Entities
Expression.Create(cx, trivia.Condition, this, 0);
}
-
- internal void Add(IIfSiblingDirective branch)
- {
- branches.Add(branch);
- }
-
- internal void WriteBranches(EndIfDirective endif)
- {
- cx.TrapWriter.Writer.directive_if_endif(this, endif);
- var siblings = 0;
- foreach (var branch in branches)
- {
- cx.TrapWriter.Writer.directive_if_siblings(this, branch, siblings++);
- }
- }
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
index f0044e891bc..4098576a86d 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Populators/DirectiveVisitor.cs
@@ -76,51 +76,63 @@ namespace Semmle.Extraction.CSharp.Populators
new Entities.EndRegionDirective(cx, node, start);
}
- private readonly Stack ifStarts = new Stack();
+ private class IfDirectiveStackElement
+ {
+ public Entities.IfDirective Entity { get; }
+ public int SiblingCount { get; set; }
+
+
+ public IfDirectiveStackElement(Entities.IfDirective entity)
+ {
+ Entity = entity;
+ }
+ }
+
+ private readonly Stack ifStarts = new Stack();
public override void VisitIfDirectiveTrivia(IfDirectiveTriviaSyntax node)
{
var ifStart = new Entities.IfDirective(cx, node);
- ifStarts.Push(ifStart);
+ ifStarts.Push(new IfDirectiveStackElement(ifStart));
}
public override void VisitEndIfDirectiveTrivia(EndIfDirectiveTriviaSyntax node)
{
- var endif = new Entities.EndIfDirective(cx, node);
if (ifStarts.Count == 0)
{
cx.ExtractionError("Couldn't find start if", null,
Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
return;
}
+
var start = ifStarts.Pop();
- start.WriteBranches(endif);
+ new Entities.EndIfDirective(cx, node, start.Entity);
}
public override void VisitElifDirectiveTrivia(ElifDirectiveTriviaSyntax node)
{
- var elif = new Entities.ElifDirective(cx, node);
if (ifStarts.Count == 0)
{
cx.ExtractionError("Couldn't find start if", null,
Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
return;
}
+
var start = ifStarts.Peek();
- start.Add(elif);
+ new Entities.ElifDirective(cx, node, start.Entity, start.SiblingCount++);
}
public override void VisitElseDirectiveTrivia(ElseDirectiveTriviaSyntax node)
{
- var elseDirective = new Entities.ElseDirective(cx, node);
if (ifStarts.Count == 0)
{
cx.ExtractionError("Couldn't find start if", null,
Extraction.Entities.Location.Create(cx, node.GetLocation()), null, Util.Logging.Severity.Warning);
return;
}
+
var start = ifStarts.Peek();
- start.Add(elseDirective);
+ new Entities.ElseDirective(cx, node, start.Entity, start.SiblingCount++);
}
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
index 95f5db2d744..82416e64978 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Tuples.cs
@@ -684,29 +684,21 @@ namespace Semmle.Extraction.CSharp
trapFile.WriteTuple("directive_ifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0);
}
- internal static void directive_elifs(this TextWriter trapFile, ElifDirective directive, bool branchTaken, bool conditionValue)
+ internal static void directive_elifs(this TextWriter trapFile, ElifDirective directive, bool branchTaken, bool conditionValue,
+ IfDirective start, int index)
{
- trapFile.WriteTuple("directive_elifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0);
+ trapFile.WriteTuple("directive_elifs", directive, branchTaken ? 1 : 0, conditionValue ? 1 : 0, start, index);
}
- internal static void directive_elses(this TextWriter trapFile, ElseDirective directive, bool branchTaken)
+ internal static void directive_elses(this TextWriter trapFile, ElseDirective directive, bool branchTaken,
+ IfDirective start, int index)
{
- trapFile.WriteTuple("directive_elses", directive, branchTaken ? 1 : 0);
+ trapFile.WriteTuple("directive_elses", directive, branchTaken ? 1 : 0, start, index);
}
- internal static void directive_endifs(this TextWriter trapFile, EndIfDirective directive)
+ internal static void directive_endifs(this TextWriter trapFile, EndIfDirective directive, IfDirective start)
{
- trapFile.WriteTuple("directive_endifs", directive);
- }
-
- internal static void directive_if_endif(this TextWriter trapFile, IfDirective start, EndIfDirective end)
- {
- trapFile.WriteTuple("directive_if_endif", start, end);
- }
-
- internal static void directive_if_siblings(this TextWriter trapFile, IfDirective start, IIfSiblingDirective siblingDirective, int index)
- {
- trapFile.WriteTuple("directive_if_siblings", start, siblingDirective, index);
+ trapFile.WriteTuple("directive_endifs", directive, start);
}
internal static void directive_define_symbols(this TextWriter trapFile, DefineSymbol symb, string name)
diff --git a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
index 77c6bd199bb..8548db264fd 100644
--- a/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
+++ b/csharp/ql/src/semmle/code/csharp/Preprocessor.qll
@@ -281,10 +281,13 @@ class IfDirective extends ConditionalDirective, @directive_if {
override predicate conditionMatched() { directive_ifs(this, _, 1) }
/** Gets the closing `#endif` preprocessor directive. */
- EndifDirective getEndifDirective() { directive_if_endif(this, result) }
+ EndifDirective getEndifDirective() { directive_endifs(result, this) }
/** Gets the sibling `#elif` or `#else` preprocessor directive at index `sibling`. */
- BranchDirective getSiblingDirective(int sibling) { directive_if_siblings(this, result, sibling) }
+ BranchDirective getSiblingDirective(int sibling) {
+ directive_elifs(result, _, _, this, sibling) or
+ directive_elses(result, _, this, sibling)
+ }
/** Gets a sibling `#elif` or `#else` preprocessor directive. */
BranchDirective getASiblingDirective() { result = getSiblingDirective(_) }
@@ -298,17 +301,18 @@ class IfDirective extends ConditionalDirective, @directive_if {
* An `#elif` preprocessor directive.
*/
class ElifDirective extends ConditionalDirective, @directive_elif {
- override predicate branchTaken() { directive_elifs(this, 1, _) }
+ override predicate branchTaken() { directive_elifs(this, 1, _, _, _) }
- override predicate conditionMatched() { directive_elifs(this, _, 1) }
+ override predicate conditionMatched() { directive_elifs(this, _, 1, _, _) }
/** Gets the opening `#if` preprocessor directive. */
- IfDirective getIfDirective() { directive_if_siblings(result, this, _) }
+ IfDirective getIfDirective() { directive_elifs(this, _, _, result, _) }
/** Gets the successive branching preprocessor directive (`#elif` or `#else`), if any. */
BranchDirective getSuccSiblingDirective() {
exists(IfDirective i, int index |
- directive_if_siblings(i, this, index) and directive_if_siblings(i, result, index + 1)
+ this = i.getSiblingDirective(index) and
+ result = i.getSiblingDirective(index + 1)
)
}
@@ -322,9 +326,9 @@ class ElifDirective extends ConditionalDirective, @directive_elif {
*/
class ElseDirective extends BranchDirective, @directive_else {
/** Gets the opening `#if` preprocessor directive. */
- IfDirective getIfDirective() { directive_if_siblings(result, this, _) }
+ IfDirective getIfDirective() { directive_elses(this, _, result, _) }
- override predicate branchTaken() { directive_elses(this, 1) }
+ override predicate branchTaken() { directive_elses(this, 1, _, _) }
override string toString() { result = "#else" }
@@ -336,7 +340,7 @@ class ElseDirective extends BranchDirective, @directive_else {
*/
class EndifDirective extends PreprocessorDirective, @directive_endif {
/** Gets the opening `#if` preprocessor directive. */
- IfDirective getIfDirective() { directive_if_endif(result, this) }
+ IfDirective getIfDirective() { directive_endifs(this, result) }
override string toString() { result = "#endif" }
diff --git a/csharp/ql/src/semmlecode.csharp.dbscheme b/csharp/ql/src/semmlecode.csharp.dbscheme
index 6dd5048ac60..f5d3daf62b8 100644
--- a/csharp/ql/src/semmlecode.csharp.dbscheme
+++ b/csharp/ql/src/semmlecode.csharp.dbscheme
@@ -346,28 +346,21 @@ directive_ifs(
directive_elifs(
unique int id: @directive_elif,
int branchTaken: int ref, /* 0: false, 1: true */
- int conditionValue: int ref); /* 0: false, 1: true */
+ int conditionValue: int ref, /* 0: false, 1: true */
+ int parent: @directive_if ref,
+ int index: int ref);
directive_elses(
unique int id: @directive_else,
- int branchTaken: int ref); /* 0: false, 1: true */
-
-directive_endifs(
- unique int id: @directive_endif);
-
-#keyset[start, end]
-directive_if_endif(
- unique int start: @directive_if ref,
- unique int end: @directive_endif ref);
-
-@directive_if_sibling = @directive_else | @directive_elif;
-
-#keyset[start, index]
-directive_if_siblings(
- int start: @directive_if ref,
- int sibling: @directive_if_sibling ref,
+ int branchTaken: int ref, /* 0: false, 1: true */
+ int parent: @directive_if ref,
int index: int ref);
+#keyset[id, start]
+directive_endifs(
+ unique int id: @directive_endif,
+ unique int start: @directive_if ref);
+
directive_define_symbols(
unique int id: @define_symbol_expr ref,
string name: string ref);
From 967765342ee1161140442c9ae378e88cb15abaf8 Mon Sep 17 00:00:00 2001
From: Tamas Vajk
Date: Wed, 27 Jan 2021 16:17:32 +0100
Subject: [PATCH 131/429] Assign preprocessor directives to compilation + make
compilation cached
---
.../Semmle.Extraction.CSharp/Analyser.cs | 10 +-
.../Entities/Compilation.cs | 125 ------------------
.../Entities/Compilations/Compilation.cs | 105 +++++++++++++++
.../Entities/Compilations/Diagnostic.cs | 23 ++++
.../Compilations/PerformanceMetrics.cs | 32 +++++
.../Entities/Compilations/Timings.cs | 11 ++
.../PreprocessorDirective.cs | 4 +-
.../Semmle.Extraction.CSharp/Extractor.cs | 12 +-
.../Semmle.Extraction.CSharp/Tuples.cs | 6 +-
csharp/extractor/Semmle.Extraction/Context.cs | 12 +-
.../src/semmle/code/csharp/Preprocessor.qll | 8 +-
csharp/ql/src/semmlecode.csharp.dbscheme | 4 +-
.../comments/Directives.expected | 62 ++++-----
.../test/library-tests/comments/Directives.ql | 3 +
14 files changed, 236 insertions(+), 181 deletions(-)
delete mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Diagnostic.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/PerformanceMetrics.cs
create mode 100644 csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Timings.cs
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
index 5aa4f452d92..75db5c0e951 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
@@ -215,14 +215,12 @@ namespace Semmle.Extraction.CSharp
///
/// Extracts compilation-wide entities, such as compilations and compiler diagnostics.
///
- public void AnalyseCompilation(string cwd, string[] args)
+ public void AnalyseCompilation()
{
- extractionTasks.Add(() => DoAnalyseCompilation(cwd, args));
+ extractionTasks.Add(() => DoAnalyseCompilation());
}
-
-
- private void DoAnalyseCompilation(string cwd, string[] args)
+ private void DoAnalyseCompilation()
{
try
{
@@ -234,7 +232,7 @@ namespace Semmle.Extraction.CSharp
compilationTrapFile = trapWriter; // Dispose later
var cx = extractor.CreateContext(compilation.Clone(), trapWriter, new AssemblyScope(assembly, assemblyPath), AddAssemblyTrapPrefix);
- compilationEntity = new Entities.Compilation(cx, cwd, args);
+ compilationEntity = Entities.Compilation.Create(cx);
}
catch (Exception ex) // lgtm[cs/catch-of-all-exceptions]
{
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs
deleted file mode 100644
index d4f0da99a28..00000000000
--- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilation.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-using Microsoft.CodeAnalysis;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using Semmle.Util;
-
-namespace Semmle.Extraction.CSharp.Entities
-{
- internal class Compilation : FreshEntity
- {
- private readonly string cwd;
- private readonly string[] args;
-
- public Compilation(Context cx, string cwd, string[] args) : base(cx)
- {
- this.cwd = cwd;
- this.args = args;
- TryPopulate();
- }
-
- protected override void Populate(TextWriter trapFile)
- {
- var assembly = Extraction.Entities.Assembly.CreateOutputAssembly(cx);
-
- trapFile.compilations(this, FileUtils.ConvertToUnix(cwd));
- trapFile.compilation_assembly(this, assembly);
-
- // Arguments
- var index = 0;
- foreach (var arg in args)
- {
- trapFile.compilation_args(this, index++, arg);
- }
-
- // Files
- index = 0;
- foreach (var file in cx.Compilation.SyntaxTrees.Select(tree => Extraction.Entities.File.Create(cx, tree.FilePath)))
- {
- trapFile.compilation_compiling_files(this, index++, file);
- }
-
- // References
- index = 0;
- foreach (var file in cx.Compilation.References.OfType().Select(r => Extraction.Entities.File.Create(cx, r.FilePath)))
- {
- trapFile.compilation_referencing_files(this, index++, file);
- }
-
- // Diagnostics
- index = 0;
- foreach (var diag in cx.Compilation.GetDiagnostics().Select(d => new Diagnostic(cx, d)))
- {
- trapFile.diagnostic_for(diag, this, 0, index++);
- }
- }
-
- public void PopulatePerformance(PerformanceMetrics p)
- {
- var trapFile = cx.TrapWriter.Writer;
- var index = 0;
- foreach (var metric in p.Metrics)
- {
- trapFile.compilation_time(this, -1, index++, metric);
- }
- trapFile.compilation_finished(this, (float)p.Total.Cpu.TotalSeconds, (float)p.Total.Elapsed.TotalSeconds);
- }
-
- public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
- }
-
- internal class Diagnostic : FreshEntity
- {
- public override TrapStackBehaviour TrapStackBehaviour => TrapStackBehaviour.NoLabel;
-
- private readonly Microsoft.CodeAnalysis.Diagnostic diagnostic;
-
- public Diagnostic(Context cx, Microsoft.CodeAnalysis.Diagnostic diag) : base(cx)
- {
- diagnostic = diag;
- TryPopulate();
- }
-
- protected override void Populate(TextWriter trapFile)
- {
- trapFile.diagnostics(this, (int)diagnostic.Severity, diagnostic.Id, diagnostic.Descriptor.Title.ToString(),
- diagnostic.GetMessage(), Extraction.Entities.Location.Create(cx, diagnostic.Location));
- }
- }
-
- public struct Timings
- {
- public TimeSpan Elapsed { get; set; }
- public TimeSpan Cpu { get; set; }
- public TimeSpan User { get; set; }
- }
-
- ///
- /// The various performance metrics to log.
- ///
- public struct PerformanceMetrics
- {
- public Timings Frontend { get; set; }
- public Timings Extractor { get; set; }
- public Timings Total { get; set; }
- public long PeakWorkingSet { get; set; }
-
- ///
- /// These are in database order (0 indexed)
- ///
- public IEnumerable Metrics
- {
- get
- {
- yield return (float)Frontend.Cpu.TotalSeconds;
- yield return (float)Frontend.Elapsed.TotalSeconds;
- yield return (float)Extractor.Cpu.TotalSeconds;
- yield return (float)Extractor.Elapsed.TotalSeconds;
- yield return (float)Frontend.User.TotalSeconds;
- yield return (float)Extractor.User.TotalSeconds;
- yield return PeakWorkingSet / 1024.0f / 1024.0f;
- }
- }
- }
-}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs
new file mode 100644
index 00000000000..800e35d5ff4
--- /dev/null
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/Compilations/Compilation.cs
@@ -0,0 +1,105 @@
+using Microsoft.CodeAnalysis;
+using System;
+using System.IO;
+using System.Linq;
+using Semmle.Util;
+
+namespace Semmle.Extraction.CSharp.Entities
+{
+ public class Compilation : CachedEntity