From ea297dd4424d03718a9063dcb3dd9e47a933f179 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 16:06:44 +0100
Subject: [PATCH 01/98] JS: bugfix in handling of custom flow labels
---
.../semmle/javascript/dataflow/internal/FlowSteps.qll | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll
index 69646cec816..95fb763f8cd 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll
@@ -74,10 +74,12 @@ predicate localFlowStep(DataFlow::Node pred, DataFlow::Node succ,
any(DataFlow::AdditionalFlowStep afs).step(pred, succ) and predlbl = succlbl
or
exists (boolean vp | configuration.isAdditionalFlowStep(pred, succ, vp) |
- if vp = false and (predlbl = FlowLabel::data() or predlbl = FlowLabel::taint()) then
- succlbl = FlowLabel::taint()
- else
- predlbl = succlbl
+ vp = true and
+ predlbl = succlbl
+ or
+ vp = false and
+ (predlbl = FlowLabel::data() or predlbl = FlowLabel::taint()) and
+ succlbl = FlowLabel::taint()
)
or
configuration.isAdditionalFlowStep(pred, succ, predlbl, succlbl)
From 03b479114fa08c2a7a368229d1dcfc6e7d1cf8ac Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 17:05:39 +0100
Subject: [PATCH 02/98] JS: preserve document.url label out of .href property
---
.../javascript/security/dataflow/ClientSideUrlRedirect.qll | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll
index 35bb5fa950d..dc331b7d2a8 100644
--- a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll
+++ b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirect.qll
@@ -65,6 +65,11 @@ module ClientSideUrlRedirect {
queryAccess(pred, succ) and
f instanceof DocumentUrl and
g = DataFlow::FlowLabel::taint()
+ or
+ // preserve document.url label in step from `location` to `location.href`
+ f instanceof DocumentUrl and
+ g instanceof DocumentUrl and
+ succ.(DataFlow::PropRead).accesses(pred, "href")
}
}
From 46b20150652be5f39d922fb30fe2c217c97832f8 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 16:08:04 +0100
Subject: [PATCH 03/98] JS: fix an outdated comment
---
javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
index 8a33ebb5c80..bef9a766b17 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
@@ -130,7 +130,7 @@ module TaintTracking {
* configurations it is used in.
*
* Note: For performance reasons, all subclasses of this class should be part
- * of the standard library. Override `Configuration::isTaintSanitizerGuard`
+ * of the standard library. Override `Configuration::isSanitizer`
* for analysis-specific taint steps.
*/
abstract class AdditionalSanitizerGuardNode extends SanitizerGuardNode {
From 396ad336a3f0e2c346b6aec8c6fca498f92818f0 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 16:09:43 +0100
Subject: [PATCH 04/98] JS: add RemoteFlowSource.isDeepObject() and populate it
---
.../semmle/javascript/frameworks/Express.qll | 14 ++++++
.../javascript/frameworks/ExpressModules.qll | 48 +++++++++++++++++++
.../security/dataflow/RemoteFlowSources.qll | 5 ++
3 files changed, 67 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
index 484ffaf0c49..c654314cb79 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
@@ -488,6 +488,20 @@ module Express {
override string getKind() {
result = kind
}
+
+ override predicate isDeepObject() {
+ kind = "body" and
+ exists (ExpressLibraries::BodyParser bodyParser, RouteHandlerExpr expr |
+ expr.getBody() = rh and
+ bodyParser.isDeepObject() and
+ bodyParser.flowsToExpr(expr.getAMatchingAncestor())
+ )
+ or
+ // If we can't find the middlewares for the route handler,
+ // but all known body parsers are deep, assume req.body is a deep object.
+ kind = "body" and
+ forall(ExpressLibraries::BodyParser bodyParser | bodyParser.isDeepObject())
+ }
}
/**
diff --git a/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll b/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll
index e7538c54612..2a7beadee5c 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll
@@ -230,4 +230,52 @@ module ExpressLibraries {
}
+ /**
+ * An instance of the Express `body-parser` middleware.
+ */
+ class BodyParser extends DataFlow::InvokeNode {
+ string kind;
+
+ BodyParser() {
+ this = DataFlow::moduleImport("body-parser").getACall() and kind = "json"
+ or
+ exists (string moduleName |
+ (moduleName = "body-parser" or moduleName = "express") and
+ (kind = "json" or kind = "urlencoded") and
+ this = DataFlow::moduleMember(moduleName, kind).getACall()
+ )
+ }
+
+ /**
+ * Holds if this is a JSON body parser.
+ */
+ predicate isJson() {
+ kind = "json"
+ }
+
+ /**
+ * Holds if this is a URL-encoded body parser.
+ */
+ predicate isUrlEncoded() {
+ kind = "urlencoded"
+ }
+
+ /**
+ * Holds if this is an extended URL-encoded body parser.
+ *
+ * The extended URL-encoding allows for nested objects, like JSON.
+ */
+ predicate isExtendedUrlEncoded() {
+ kind = "urlencoded" and
+ not getOptionArgument(0, "extended").mayHaveBooleanValue(false)
+ }
+
+ /**
+ * Holds if this parses the input as JSON or extended URL-encoding.
+ */
+ predicate isDeepObject() {
+ isJson() or isExtendedUrlEncoded()
+ }
+ }
+
}
diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll
index a1138a0eb5e..c63a80cad64 100644
--- a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll
+++ b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll
@@ -10,6 +10,11 @@ import semmle.javascript.security.dataflow.DOM
abstract class RemoteFlowSource extends DataFlow::Node {
/** Gets a string that describes the type of this remote flow source. */
abstract string getSourceType();
+
+ /**
+ * Holds if this can be a user-controlled deep object, such as a JSON object parsed from user-controlled data.
+ */
+ predicate isDeepObject() { none() }
}
/**
From b70f70f7227d9540ff274434c0b0f4a2a89ef2c7 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 16:37:11 +0100
Subject: [PATCH 05/98] JS: Add TaintedObject flow label library
---
.../javascript/security/TaintedObject.qll | 84 +++++++++++++++++++
1 file changed, 84 insertions(+)
create mode 100644 javascript/ql/src/semmle/javascript/security/TaintedObject.qll
diff --git a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
new file mode 100644
index 00000000000..fb45e6dd753
--- /dev/null
+++ b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
@@ -0,0 +1,84 @@
+/**
+ * Provides methods for reasoning about the flow of deeply tainted objects, such as JSON objects
+ * parsed from user-controlled data.
+ *
+ * Deeply tainted objects are arrays or objects with user-controlled property names, containing
+ * tainted values or deeply tainted objects in their properties.
+ *
+ * To track deeply tainted objects, a flow-tracking configuration should generally include the following:
+ *
+ * 1. One or more sinks associated with the label `TaintedObject::label()`.
+ * 2. The sources from `TaintedObject::isSource`.
+ * 3. The flow steps from `TaintedObject::step`.
+ */
+import javascript
+
+module TaintedObject {
+ private import DataFlow
+
+ private class TaintedObjectLabel extends FlowLabel {
+ TaintedObjectLabel() { this = "tainted-object" }
+ }
+
+ /**
+ * Gets the flow label representing a deeply tainted objects.
+ *
+ * A "tainted object" is an array or object whose values are all assumed to be tainted as well.
+ *
+ * Note that the presence of the `object-taint` label generally implies the presence of the `taint` label as well.
+ */
+ FlowLabel label() { result instanceof TaintedObjectLabel }
+
+ /**
+ * Holds for the flows steps that are relevant for tracking user-controlled JSON objects.
+ */
+ predicate step(Node src, Node trg, FlowLabel inlbl, FlowLabel outlbl) {
+ // JSON parsers map tainted inputs to tainted JSON
+ (inlbl = FlowLabel::data() or inlbl = FlowLabel::taint()) and
+ outlbl = label() and
+ exists (JsonParserCall parse |
+ src = parse.getInput() and
+ trg = parse.getOutput())
+ or
+ // Property reads preserve deep object taint.
+ inlbl = label() and
+ outlbl = label() and
+ trg.(PropRead).getBase() = src
+ or
+ // Property projection preserves deep object taint
+ inlbl = label() and
+ outlbl = label() and
+ trg.(PropertyProjection).getObject() = src
+ or
+ // Extending objects preserves deep object taint
+ inlbl = label() and
+ outlbl = label() and
+ exists (ExtendCall call |
+ src = call.getAnOperand() and
+ trg = call
+ or
+ src = call.getASourceOperand() and
+ trg = call.getDestinationOperand().getALocalSource())
+ }
+
+ /**
+ * Holds if `node` is a source of JSON taint and label is the JSON taint label.
+ */
+ predicate isSource(Node source, FlowLabel label) {
+ source instanceof Source and label = label()
+ }
+
+ /**
+ * A source of a user-controlled deep object object.
+ */
+ abstract class Source extends DataFlow::Node {}
+
+ /** Request input accesses as a JSON source. */
+ private class RequestInputAsSource extends Source {
+ RequestInputAsSource() {
+ this.(HTTP::RequestInputAccess).isDeepObject()
+ }
+ }
+
+ // TODO: string tests should be classified as sanitizer guards; need support for flow labels on guards
+}
From d72d7345b8c89f213fbbad5259a82937d9e7599b Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 16:39:11 +0100
Subject: [PATCH 06/98] JS: make NosqlInjection use object taint
---
.../src/Security/CWE-089/SqlInjection.actual | 0
.../security/dataflow/NosqlInjection.qll | 30 ++++++++++++++----
.../Security/CWE-089/SqlInjection.expected | 1 -
.../query-tests/Security/CWE-089/mongodb.js | 8 ++++-
.../Security/CWE-089/mongodb_bodySafe.js | 31 +++++++++++++++++++
5 files changed, 62 insertions(+), 8 deletions(-)
create mode 100644 javascript/ql/src/Security/CWE-089/SqlInjection.actual
create mode 100644 javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js
diff --git a/javascript/ql/src/Security/CWE-089/SqlInjection.actual b/javascript/ql/src/Security/CWE-089/SqlInjection.actual
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll b/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll
index 9c031728d7e..571ca4d4964 100644
--- a/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll
+++ b/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll
@@ -4,6 +4,7 @@
*/
import javascript
+import semmle.javascript.security.TaintedObject
module NosqlInjection {
/**
@@ -14,7 +15,16 @@ module NosqlInjection {
/**
* A data flow sink for SQL-injection vulnerabilities.
*/
- abstract class Sink extends DataFlow::Node { }
+ abstract class Sink extends DataFlow::Node {
+ /**
+ * Gets a flow label relevant for this sink.
+ *
+ * Defaults to deeply tainted objects only.
+ */
+ DataFlow::FlowLabel getAFlowLabel() {
+ result = TaintedObject::label()
+ }
+ }
/**
* A sanitizer for SQL-injection vulnerabilities.
@@ -31,8 +41,12 @@ module NosqlInjection {
source instanceof Source
}
- override predicate isSink(DataFlow::Node sink) {
- sink instanceof Sink
+ override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
+ TaintedObject::isSource(source, label)
+ }
+
+ override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
+ sink.(Sink).getAFlowLabel() = label
}
override predicate isSanitizer(DataFlow::Node node) {
@@ -40,12 +54,16 @@ module NosqlInjection {
node instanceof Sanitizer
}
- override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
+ override predicate isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg, DataFlow::FlowLabel inlbl, DataFlow::FlowLabel outlbl) {
+ TaintedObject::step(src, trg, inlbl, outlbl)
+ or
// additional flow step to track taint through NoSQL query objects
+ inlbl = TaintedObject::label() and
+ outlbl = TaintedObject::label() and
exists (NoSQL::Query query, DataFlow::SourceNode queryObj |
queryObj.flowsToExpr(query) and
- queryObj.flowsTo(succ) and
- pred = queryObj.getAPropertyWrite().getRhs()
+ queryObj.flowsTo(trg) and
+ src = queryObj.getAPropertyWrite().getRhs()
)
}
}
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
index 7a99482adb8..d432a2a9363 100644
--- a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
@@ -1,5 +1,4 @@
| mongodb.js:18:16:18:20 | query | This query depends on $@. | mongodb.js:13:19:13:26 | req.body | a user-provided value |
-| mongodb.js:39:16:39:20 | query | This query depends on $@. | mongodb.js:34:19:34:33 | req.query.title | a user-provided value |
| mongoose.js:27:20:27:24 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:30:25:30:29 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:33:24:33:28 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
index 900cad1cbba..f8c41a55f76 100644
--- a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
+++ b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
@@ -16,6 +16,12 @@ app.post('/documents/find', (req, res) => {
// NOT OK: query is tainted by user-provided object value
doc.find(query);
+
+ // OK: user-data is coerced to a string
+ doc.find({ title: '' + query.body.title });
+
+ // OK: throws unless user-data is a string
+ doc.find({ title: query.body.title.substr(1) });
});
});
@@ -36,6 +42,6 @@ app.post('/documents/find', (req, res) => {
let doc = db.collection('doc');
// NOT OK: query is tainted by user-provided object value
- doc.find(query);
+ doc.find(query); // Not currently detected
});
});
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js b/javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js
new file mode 100644
index 00000000000..61a87bfaa5a
--- /dev/null
+++ b/javascript/ql/test/query-tests/Security/CWE-089/mongodb_bodySafe.js
@@ -0,0 +1,31 @@
+const express = require('express'),
+ mongodb = require('mongodb'),
+ bodyParser = require('body-parser');
+
+const MongoClient = mongodb.MongoClient;
+
+const app = express();
+
+app.use(bodyParser.urlencoded({ extended: false }));
+
+app.post('/documents/find', (req, res) => {
+ const query = {};
+ query.title = req.body.title;
+ MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
+ let doc = db.collection('doc');
+
+ // OK: req.body is safe
+ doc.find(query);
+ });
+});
+
+app.post('/documents/find', (req, res) => {
+ const query = {};
+ query.title = req.query.title;
+ MongoClient.connect('mongodb://localhost:27017/test', (err, db) => {
+ let doc = db.collection('doc');
+
+ // NOT OK: regardless of body parser, query value is still tainted
+ doc.find(query);
+ });
+});
From 5e720486d54b74b45d7601c31a227bd903df5b34 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 17:15:56 +0100
Subject: [PATCH 07/98] JS: recognize req.query.x as deep object taint
---
.../ql/src/semmle/javascript/frameworks/Express.qll | 11 +++++++++++
.../Security/CWE-089/SqlInjection.expected | 2 ++
.../ql/test/query-tests/Security/CWE-089/mongodb.js | 2 +-
3 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
index c654314cb79..6a2c7af99d0 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
@@ -501,6 +501,17 @@ module Express {
// but all known body parsers are deep, assume req.body is a deep object.
kind = "body" and
forall(ExpressLibraries::BodyParser bodyParser | bodyParser.isDeepObject())
+ or
+ kind = "parameter" and
+ exists (DataFlow::Node request | request = DataFlow::valueNode(rh.getARequestExpr()) |
+ this.(DataFlow::MethodCallNode).calls(request, "param")
+ or
+ exists (DataFlow::PropRead base |
+ // `req.query.name`
+ base.accesses(request, "query") and
+ this = base.getAPropertyReference(_)
+ )
+ )
}
}
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
index d432a2a9363..f5279170af9 100644
--- a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
@@ -1,4 +1,6 @@
| mongodb.js:18:16:18:20 | query | This query depends on $@. | mongodb.js:13:19:13:26 | req.body | a user-provided value |
+| mongodb.js:45:16:45:20 | query | This query depends on $@. | mongodb.js:40:19:40:33 | req.query.title | a user-provided value |
+| mongodb_bodySafe.js:29:16:29:20 | query | This query depends on $@. | mongodb_bodySafe.js:24:19:24:33 | req.query.title | a user-provided value |
| mongoose.js:27:20:27:24 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:30:25:30:29 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:33:24:33:28 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
index f8c41a55f76..3c40b43a13e 100644
--- a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
+++ b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
@@ -42,6 +42,6 @@ app.post('/documents/find', (req, res) => {
let doc = db.collection('doc');
// NOT OK: query is tainted by user-provided object value
- doc.find(query); // Not currently detected
+ doc.find(query);
});
});
From 9b10254cd498a37aa51440c2c81df4c7215e7d08 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Wed, 10 Oct 2018 18:01:57 +0100
Subject: [PATCH 08/98] JS: support label-specific sanitizer guards
---
.../javascript/dataflow/Configuration.qll | 31 +++++++++++++--
.../javascript/security/TaintedObject.qll | 39 ++++++++++++++++++-
.../security/dataflow/NosqlInjection.qll | 4 ++
.../Security/CWE-089/SqlInjection.expected | 3 +-
.../query-tests/Security/CWE-089/mongodb.js | 9 +++++
5 files changed, 81 insertions(+), 5 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
index 8125784a5e3..6dc998c2d49 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
@@ -146,6 +146,7 @@ abstract class Configuration extends string {
*/
predicate isBarrier(DataFlow::Node node) {
exists (BarrierGuardNode guard |
+ guard.blocksAllLabels() and
isBarrierGuard(guard) and
guard.blocks(node)
)
@@ -161,6 +162,17 @@ abstract class Configuration extends string {
*/
predicate isBarrier(DataFlow::Node src, DataFlow::Node trg, FlowLabel lbl) { none() }
+ /**
+ * Holds if flow with label `lbl` cannot flow into `node`.
+ */
+ predicate isBarrierForLabel(DataFlow::Node node, FlowLabel lbl) {
+ exists (BarrierGuardNode guard |
+ guard.blocksSpecificLabel(lbl) and
+ isBarrierGuard(guard) and
+ guard.blocks(node)
+ )
+ }
+
/**
* Holds if data flow node `guard` can act as a barrier when appearing
* in a condition.
@@ -298,6 +310,15 @@ abstract class BarrierGuardNode extends DataFlow::Node {
*/
abstract predicate blocks(boolean outcome, Expr e);
+ /**
+ * Holds if this barrier guard blocks all labels.
+ */
+ predicate blocksAllLabels() { any() }
+
+ /**
+ * Holds if this barrier guard only blocks specific labels, and `label` is one of them.
+ */
+ predicate blocksSpecificLabel(FlowLabel label) { none() }
}
/**
@@ -570,7 +591,8 @@ private predicate flowThroughCall(DataFlow::Node input, DataFlow::Node invk,
ret.asExpr() = f.getAReturnedExpr() and
calls(invk, f) and // Do not consider partial calls
reachableFromInput(f, invk, input, ret, cfg, summary) and
- not cfg.isBarrier(ret, invk)
+ not cfg.isBarrier(ret, invk) and
+ not cfg.isBarrierForLabel(invk, summary.getEndLabel())
)
}
@@ -641,7 +663,8 @@ private predicate flowStep(DataFlow::Node pred, DataFlow::Configuration cfg,
flowThroughProperty(pred, succ, cfg, summary)
) and
not cfg.isBarrier(succ) and
- not cfg.isBarrier(pred, succ)
+ not cfg.isBarrier(pred, succ) and
+ not cfg.isBarrierForLabel(succ, summary.getEndLabel())
}
/**
@@ -666,6 +689,7 @@ private predicate reachableFromSource(DataFlow::Node nd, DataFlow::Configuration
exists (FlowLabel lbl |
isSource(nd, cfg, lbl) and
not cfg.isBarrier(nd) and
+ not cfg.isBarrierForLabel(nd, lbl) and
summary = MkPathSummary(false, false, lbl, lbl)
)
or
@@ -684,7 +708,8 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg,
PathSummary summary) {
reachableFromSource(nd, cfg, summary) and
isSink(nd, cfg, summary.getEndLabel()) and
- not cfg.isBarrier(nd)
+ not cfg.isBarrier(nd) and
+ not cfg.isBarrierForLabel(nd, summary.getEndLabel())
or
exists (DataFlow::Node mid, PathSummary stepSummary |
reachableFromSource(nd, cfg, summary) and
diff --git a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
index fb45e6dd753..4f7d715b30b 100644
--- a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
+++ b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
@@ -10,6 +10,7 @@
* 1. One or more sinks associated with the label `TaintedObject::label()`.
* 2. The sources from `TaintedObject::isSource`.
* 3. The flow steps from `TaintedObject::step`.
+ * 4. The sanitizing guards `TaintedObject::SanitizerGuard`.
*/
import javascript
@@ -80,5 +81,41 @@ module TaintedObject {
}
}
- // TODO: string tests should be classified as sanitizer guards; need support for flow labels on guards
+ /**
+ * Sanitizer guard that blocks deep object taint.
+ */
+ abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode {
+ override predicate blocksAllLabels() { none() }
+
+ override predicate blocksSpecificLabel(FlowLabel label) {
+ label = label()
+ }
+ }
+
+ /**
+ * A test of form `typeof x === "something"`, preventing `x` from being an object in some cases.
+ */
+ private class TypeTestGuard extends SanitizerGuard, ValueNode {
+ override EqualityTest astNode;
+ TypeofExpr typeof;
+ boolean polarity;
+
+ TypeTestGuard() {
+ astNode.getAnOperand() = typeof and
+ (
+ // typeof x === "object" sanitizes `x` when it evaluates to false
+ astNode.getAnOperand().getStringValue() = "object" and
+ polarity = astNode.getPolarity().booleanNot()
+ or
+ // typeof x === "string" sanitizes `x` when it evaluates to true
+ astNode.getAnOperand().getStringValue() != "object" and
+ polarity = astNode.getPolarity()
+ )
+ }
+
+ override predicate sanitizes(boolean outcome, Expr e) {
+ polarity = outcome and
+ e = typeof.getOperand()
+ }
+ }
}
diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll b/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll
index 571ca4d4964..c338e6ced50 100644
--- a/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll
+++ b/javascript/ql/src/semmle/javascript/security/dataflow/NosqlInjection.qll
@@ -54,6 +54,10 @@ module NosqlInjection {
node instanceof Sanitizer
}
+ 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)
or
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
index f5279170af9..e6c84156dd0 100644
--- a/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-089/SqlInjection.expected
@@ -1,5 +1,6 @@
| mongodb.js:18:16:18:20 | query | This query depends on $@. | mongodb.js:13:19:13:26 | req.body | a user-provided value |
-| mongodb.js:45:16:45:20 | query | This query depends on $@. | mongodb.js:40:19:40:33 | req.query.title | a user-provided value |
+| mongodb.js:32:18:32:45 | { title ... itle) } | This query depends on $@. | mongodb.js:26:19:26:26 | req.body | a user-provided value |
+| mongodb.js:54:16:54:20 | query | This query depends on $@. | mongodb.js:49:19:49:33 | req.query.title | a user-provided value |
| mongodb_bodySafe.js:29:16:29:20 | query | This query depends on $@. | mongodb_bodySafe.js:24:19:24:33 | req.query.title | a user-provided value |
| mongoose.js:27:20:27:24 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
| mongoose.js:30:25:30:29 | query | This query depends on $@. | mongoose.js:21:19:21:26 | req.body | a user-provided value |
diff --git a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
index 3c40b43a13e..00f3422ca40 100644
--- a/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
+++ b/javascript/ql/test/query-tests/Security/CWE-089/mongodb.js
@@ -22,6 +22,15 @@ app.post('/documents/find', (req, res) => {
// OK: throws unless user-data is a string
doc.find({ title: query.body.title.substr(1) });
+
+ let title = req.body.title;
+ if (typeof title === "string") {
+ // OK: input checked to be a string
+ doc.find({ title: title });
+
+ // NOT OK: input is parsed as JSON after string check
+ doc.find({ title: JSON.parse(title) });
+ }
});
});
From 6dfbb72fc88e95432c2736d5905e085d04b82868 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Tue, 9 Oct 2018 12:04:37 +0200
Subject: [PATCH 09/98] Java: Add constant array lengths to
ConstantIntegerExpr.
---
java/ql/src/semmle/code/java/dataflow/RangeUtils.qll | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
index 9b5e9a257a2..bec801248d7 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
@@ -15,6 +15,13 @@ private predicate constantIntegerExpr(Expr e, int val) {
src = v.getDefiningExpr().(VariableAssign).getSource() and
constantIntegerExpr(src, val)
)
+ or
+ exists(SsaExplicitUpdate v, FieldRead arrlen |
+ e = arrlen and
+ arrlen.getField() instanceof ArrayLengthField and
+ arrlen.getQualifier() = v.getAUse() and
+ v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = val
+ )
}
/** An expression that always has the same integer value. */
From 8659bedbd914674b8ea3af6746729c9662966937 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Tue, 9 Oct 2018 16:16:22 +0200
Subject: [PATCH 10/98] Java: Extract Bound class to its own file.
---
.../src/semmle/code/java/dataflow/Bound.qll | 58 +++++++++++++++++++
.../code/java/dataflow/RangeAnalysis.qll | 58 +------------------
2 files changed, 60 insertions(+), 56 deletions(-)
create mode 100644 java/ql/src/semmle/code/java/dataflow/Bound.qll
diff --git a/java/ql/src/semmle/code/java/dataflow/Bound.qll b/java/ql/src/semmle/code/java/dataflow/Bound.qll
new file mode 100644
index 00000000000..f607444908a
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/Bound.qll
@@ -0,0 +1,58 @@
+import java
+private import SSA
+private import RangeUtils
+
+private newtype TBound =
+ TBoundZero() or
+ TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
+ TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) }
+
+/**
+ * A bound that may be inferred for an expression plus/minus an integer delta.
+ */
+abstract class Bound extends TBound {
+ abstract string toString();
+ /** Gets an expression that equals this bound plus `delta`. */
+ abstract Expr getExpr(int delta);
+ /** Gets an expression that equals this bound. */
+ Expr getExpr() {
+ result = getExpr(0)
+ }
+ predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
+ }
+}
+
+/**
+ * The bound that corresponds to the integer 0. This is used to represent all
+ * integer bounds as bounds are always accompanied by an added integer delta.
+ */
+class ZeroBound extends Bound, TBoundZero {
+ override string toString() { result = "0" }
+ override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
+}
+
+/**
+ * A bound corresponding to the value of an SSA variable.
+ */
+class SsaBound extends Bound, TBoundSsa {
+ /** Gets the SSA variable that equals this bound. */
+ SsaVariable getSsa() { this = TBoundSsa(result) }
+ override string toString() { result = getSsa().toString() }
+ override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
+ }
+}
+
+/**
+ * A bound that corresponds to the value of a specific expression that might be
+ * interesting, but isn't otherwise represented by the value of an SSA variable.
+ */
+class ExprBound extends Bound, TBoundExpr {
+ override string toString() { result = getExpr().toString() }
+ override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
+ override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
+ getExpr().hasLocationInfo(path, sl, sc, el, ec)
+ }
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
index d6d396e8c6a..d588da9e2dd 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
@@ -72,6 +72,7 @@ private import ParityAnalysis
private import semmle.code.java.Reflection
private import semmle.code.java.Collections
private import semmle.code.java.Maps
+import Bound
cached private module RangeAnalysisCache {
@@ -409,61 +410,6 @@ private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) {
)
}
-private newtype TBound =
- TBoundZero() or
- TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
- TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) }
-
-/**
- * A bound that may be inferred for an expression plus/minus an integer delta.
- */
-abstract class Bound extends TBound {
- abstract string toString();
- /** Gets an expression that equals this bound plus `delta`. */
- abstract Expr getExpr(int delta);
- /** Gets an expression that equals this bound. */
- Expr getExpr() {
- result = getExpr(0)
- }
- predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
- path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
- }
-}
-
-/**
- * The bound that corresponds to the integer 0. This is used to represent all
- * integer bounds as bounds are always accompanied by an added integer delta.
- */
-class ZeroBound extends Bound, TBoundZero {
- override string toString() { result = "0" }
- override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
-}
-
-/**
- * A bound corresponding to the value of an SSA variable.
- */
-class SsaBound extends Bound, TBoundSsa {
- /** Gets the SSA variable that equals this bound. */
- SsaVariable getSsa() { this = TBoundSsa(result) }
- override string toString() { result = getSsa().toString() }
- override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
- override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
- getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
- }
-}
-
-/**
- * A bound that corresponds to the value of a specific expression that might be
- * interesting, but isn't otherwise represented by the value of an SSA variable.
- */
-class ExprBound extends Bound, TBoundExpr {
- override string toString() { result = getExpr().toString() }
- override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
- override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
- getExpr().hasLocationInfo(path, sl, sc, el, ec)
- }
-}
-
/**
* Holds if `b + delta` is a valid bound for `v` at `pos`.
* - `upper = true` : `v <= b + delta`
@@ -632,7 +578,7 @@ private predicate baseBound(Expr e, int b, boolean upper) {
*/
private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) {
exists(int bound |
- bounded(cast.getExpr(), TBoundZero(), bound, upper, _, _, _)
+ bounded(cast.getExpr(), any(ZeroBound zb), bound, upper, _, _, _)
|
upper = true and bound <= cast.getUpperBound() or
upper = false and bound >= cast.getLowerBound()
From a78a0b52ec9142bb7d1b79a6ec5ef6f1eb8017ca Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Tue, 9 Oct 2018 16:24:25 +0200
Subject: [PATCH 11/98] Java: Add test.
---
java/ql/test/query-tests/RangeAnalysis/A.java | 154 ++++++++++++++++++
.../ArrayIndexOutOfBounds.expected | 15 ++
.../RangeAnalysis/ArrayIndexOutOfBounds.qlref | 1 +
3 files changed, 170 insertions(+)
create mode 100644 java/ql/test/query-tests/RangeAnalysis/A.java
create mode 100644 java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected
create mode 100644 java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref
diff --git a/java/ql/test/query-tests/RangeAnalysis/A.java b/java/ql/test/query-tests/RangeAnalysis/A.java
new file mode 100644
index 00000000000..abe2b3d4aed
--- /dev/null
+++ b/java/ql/test/query-tests/RangeAnalysis/A.java
@@ -0,0 +1,154 @@
+public class A {
+ private static final int[] arr1 = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
+ private final int[] arr2;
+ private final int[] arr3;
+
+ public A(int[] arr2, int n) {
+ if (arr2.length % 2 != 0)
+ throw new Exception();
+ this.arr2 = arr2;
+ this.arr3 = new int[n << 1];
+ }
+
+ void m1(int[] a) {
+ int sum = 0;
+ for (int i = 0; i <= a.length; i++) {
+ sum += a[i]; // Out of bounds
+ }
+ }
+
+ void m2(int[] a) {
+ int sum = 0;
+ for (int i = 0; i < a.length; i += 2) {
+ sum += a[i] + a[i + 1]; // Out of bounds (unless len%2==0)
+ }
+ }
+
+ void m3(int[] a) {
+ if (a.length % 2 != 0)
+ return;
+ int sum = 0;
+ for (int i = 0; i < a.length; ) {
+ sum += a[i++]; // OK
+ sum += a[i++]; // OK
+ }
+ for (int i = 0; i < arr1.length; ) {
+ sum += arr1[i++]; // OK
+ sum += arr1[i++]; // OK - FP
+ i += 2;
+ }
+ for (int i = 0; i < arr2.length; ) {
+ sum += arr2[i++]; // OK
+ sum += arr2[i++]; // OK - FP
+ }
+ for (int i = 0; i < arr3.length; ) {
+ sum += arr3[i++]; // OK
+ sum += arr3[i++]; // OK - FP
+ }
+ int[] b;
+ if (sum > 3)
+ b = a;
+ else
+ b = arr1;
+ for (int i = 0; i < b.length; i++) {
+ sum += b[i]; // OK
+ sum += b[++i]; // OK - FP
+ }
+ }
+
+ void m4(int[] a, int[] b) {
+ assert a.length % 2 == 0;
+ int sum = 0;
+ for (int i = 0; i < a.length; ) {
+ sum += a[i++]; // OK
+ sum += a[i++]; // OK - FP
+ }
+ int len = b.length;
+ if ((len & 1) != 0)
+ return;
+ for (int i = 0; i < len; ) {
+ sum += b[i++]; // OK
+ sum += b[i++]; // OK
+ }
+ }
+
+ void m5(int n) {
+ int[] a = new int[3 * n];
+ int sum = 0;
+ for (int i = 0; i < a.length; i += 3) {
+ sum += a[i] + a[i + 1] + a[i + 2]; // OK - FP
+ }
+ }
+
+ int m6(int[] a, int ix) {
+ if (ix < 0 || ix > a.length)
+ return 0;
+ return a[ix]; // Out of bounds
+ }
+
+ void m7() {
+ int[] xs = new int[11];
+ int sum = 0;
+ for (int i = 0; i < 11; i++) {
+ for (int j = 0; j < 11; j++) {
+ sum += xs[i]; // OK
+ sum += xs[j]; // OK
+ if (i < j)
+ sum += xs[i + 11 - j]; // OK - FP
+ else
+ sum += xs[i - j]; // OK
+ }
+ }
+ }
+
+ void m8(int[] a) {
+ if ((a.length - 4) % 3 != 0)
+ return;
+ int sum = 0;
+ for (int i = 4; i < a.length; i += 3) {
+ sum += a[i]; // OK
+ sum += a[i + 1]; // OK - FP
+ sum += a[i + 2]; // OK - FP
+ }
+ }
+
+ void m9() {
+ int[] a = new int[] { 1, 2, 3, 4, 5 };
+ int sum = 0;
+ for (int i = 0; i < 10; i++) {
+ if (i < 5)
+ sum += a[i]; // OK
+ else
+ sum += a[9 - i]; // OK - FP
+ }
+ }
+
+ void m10(int n, int m) {
+ int len = Math.min(n, m);
+ int[] a = new int[n];
+ int sum = 0;
+ for (int i = n - 1; i >= 0; i--) {
+ sum += a[i]; // OK
+ for (int j = i + 1; j < len; j++) {
+ sum += a[j]; // OK
+ sum += a[i + 1]; // OK - FP
+ }
+ }
+ }
+
+ void m11(int n) {
+ int len = n*2;
+ int[] a = new int[len];
+ int sum = 0;
+ for (int i = 0; i < len; i = i + 2) {
+ sum += a[i+1]; // OK
+ for (int j = i; j < len - 2; j = j + 2) {
+ sum += a[j]; // OK
+ sum += a[j+1]; // OK
+ sum += a[j+2]; // OK
+ sum += a[j+3]; // OK
+ }
+ }
+ }
+
+}
diff --git a/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected
new file mode 100644
index 00000000000..eb59dd6d262
--- /dev/null
+++ b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected
@@ -0,0 +1,15 @@
+| A.java:16:14:16:17 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:23:21:23:28 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:37:14:37:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:42:14:42:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:46:14:46:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:55:14:55:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:64:14:64:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:79:21:79:28 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:79:32:79:39 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 1. |
+| A.java:86:12:86:16 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:97:18:97:31 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 8. |
+| A.java:110:14:110:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
+| A.java:111:14:111:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 1. |
+| A.java:122:16:122:23 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 3. |
+| A.java:134:16:134:23 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
diff --git a/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref
new file mode 100644
index 00000000000..439f2fd18de
--- /dev/null
+++ b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.qlref
@@ -0,0 +1 @@
+Likely Bugs/Collections/ArrayIndexOutOfBounds.ql
From e7b0d399d14b4e398afc528fd4db682050c4377d Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Tue, 9 Oct 2018 16:31:11 +0200
Subject: [PATCH 12/98] Java: Refactor parts of RangeAnalysis needed for
ModulusAnalysis.
---
.../code/java/dataflow/RangeAnalysis.qll | 49 ++-----------
.../semmle/code/java/dataflow/RangeUtils.qll | 72 +++++++++++++++++++
2 files changed, 77 insertions(+), 44 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
index d588da9e2dd..09a47566f08 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
@@ -100,23 +100,6 @@ cached private module RangeAnalysisCache {
private import RangeAnalysisCache
import RangeAnalysisPublic
-/**
- * Gets a condition that tests whether `v` equals `e + delta`.
- *
- * If the condition evaluates to `testIsTrue`:
- * - `isEq = true` : `v == e + delta`
- * - `isEq = false` : `v != e + delta`
- */
-private Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) {
- exists(boolean eqpolarity |
- result.isEquality(ssaRead(v, delta), e, eqpolarity) and
- (testIsTrue = true or testIsTrue = false) and
- eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
- )
- or
- exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0))
-}
-
/**
* Holds if `comp` corresponds to:
* - `upper = true` : `v <= e + delta` or `v < e + delta`
@@ -207,14 +190,8 @@ class CondReason extends Reason, TCondReason {
* - `upper = false` : `v >= e + delta`
*/
private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason) {
- exists(SsaExplicitUpdate upd | v = upd and pos.hasReadOfVar(v) and reason = TNoReason() |
- upd.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 and (upper = true or upper = false) or
- upd.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or
- upd.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 and (upper = true or upper = false) or
- upd.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or
- upd.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 and (upper = true or upper = false) or
- upd.getDefiningExpr().(AssignOp) = e and delta = 0 and (upper = true or upper = false)
- ) or
+ ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v) and (upper = true or upper = false) and reason = TNoReason()
+ or
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = boundFlowCond(v, e, delta, upper, testIsTrue) and
@@ -291,22 +268,8 @@ private class NarrowingCastExpr extends CastExpr {
* - `upper = false` : `e2 >= e1 + delta`
*/
private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
- e2.(ParExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
- e2.(AssignExpr).getSource() = e1 and delta = 0 and (upper = true or upper = false) or
- e2.(PlusExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
- e2.(PostIncExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
- e2.(PostDecExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
- e2.(PreIncExpr).getExpr() = e1 and delta = 1 and (upper = true or upper = false) or
- e2.(PreDecExpr).getExpr() = e1 and delta = -1 and (upper = true or upper = false) or
+ valueFlowStep(e2, e1, delta) and (upper = true or upper = false) or
e2.(SafeCastExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
- exists(SsaExplicitUpdate v, FieldRead arrlen |
- e2 = arrlen and
- arrlen.getField() instanceof ArrayLengthField and
- arrlen.getQualifier() = v.getAUse() and
- v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and
- delta = 0 and
- (upper = true or upper = false)
- ) or
exists(Expr x |
e2.(AddExpr).hasOperands(e1, x) or
exists(AssignAddExpr add | add = e2 |
@@ -314,8 +277,7 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
add.getDest() = x and add.getRhs() = e1
)
|
- x.(ConstantIntegerExpr).getIntValue() = delta and (upper = true or upper = false)
- or
+ // `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
not e1 instanceof ConstantIntegerExpr and
if strictlyPositive(x) then
@@ -341,8 +303,7 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
sub.getRhs() = x
)
|
- x.(ConstantIntegerExpr).getIntValue() = -delta and (upper = true or upper = false)
- or
+ // `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
if strictlyPositive(x) then
(upper = true and delta = -1)
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
index bec801248d7..131d62ccdaf 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
@@ -138,3 +138,75 @@ predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean
guardControlsSsaRead(guard0, controlled, testIsTrue0)
)
}
+
+/**
+ * Gets a condition that tests whether `v` equals `e + delta`.
+ *
+ * If the condition evaluates to `testIsTrue`:
+ * - `isEq = true` : `v == e + delta`
+ * - `isEq = false` : `v != e + delta`
+ */
+Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsTrue) {
+ exists(boolean eqpolarity |
+ result.isEquality(ssaRead(v, delta), e, eqpolarity) and
+ (testIsTrue = true or testIsTrue = false) and
+ eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
+ )
+ or
+ exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0))
+}
+
+/**
+ * Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`.
+ */
+predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) {
+ v.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 or
+ v.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 or
+ v.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 or
+ v.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 or
+ v.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 or
+ v.getDefiningExpr().(AssignOp) = e and delta = 0
+}
+
+/**
+ * Holds if `e1 + delta` equals `e2`.
+ */
+predicate valueFlowStep(Expr e2, Expr e1, int delta) {
+ e2.(ParExpr).getExpr() = e1 and delta = 0 or
+ e2.(AssignExpr).getSource() = e1 and delta = 0 or
+ e2.(PlusExpr).getExpr() = e1 and delta = 0 or
+ e2.(PostIncExpr).getExpr() = e1 and delta = 0 or
+ e2.(PostDecExpr).getExpr() = e1 and delta = 0 or
+ e2.(PreIncExpr).getExpr() = e1 and delta = 1 or
+ e2.(PreDecExpr).getExpr() = e1 and delta = -1 or
+ exists(SsaExplicitUpdate v, FieldRead arrlen |
+ e2 = arrlen and
+ arrlen.getField() instanceof ArrayLengthField and
+ arrlen.getQualifier() = v.getAUse() and
+ v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and
+ delta = 0
+ ) or
+ exists(Expr x |
+ e2.(AddExpr).hasOperands(e1, x) or
+ exists(AssignAddExpr add | add = e2 |
+ add.getDest() = e1 and add.getRhs() = x or
+ add.getDest() = x and add.getRhs() = e1
+ )
+ |
+ x.(ConstantIntegerExpr).getIntValue() = delta
+ ) or
+ exists(Expr x |
+ exists(SubExpr sub |
+ e2 = sub and
+ sub.getLeftOperand() = e1 and
+ sub.getRightOperand() = x
+ ) or
+ exists(AssignSubExpr sub |
+ e2 = sub and
+ sub.getDest() = e1 and
+ sub.getRhs() = x
+ )
+ |
+ x.(ConstantIntegerExpr).getIntValue() = -delta
+ )
+}
From 5c5324961279804d2a1f622fa49479d93a096fc9 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Wed, 10 Oct 2018 14:02:51 +0200
Subject: [PATCH 13/98] Java: Add ModulusAnalysis.
---
.../code/java/dataflow/ModulusAnalysis.qll | 322 ++++++++++++++++++
.../code/java/dataflow/RangeAnalysis.qll | 33 +-
java/ql/test/query-tests/RangeAnalysis/A.java | 2 +-
.../ArrayIndexOutOfBounds.expected | 2 -
4 files changed, 351 insertions(+), 8 deletions(-)
create mode 100644 java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll
diff --git a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll
new file mode 100644
index 00000000000..32133d785ff
--- /dev/null
+++ b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll
@@ -0,0 +1,322 @@
+/**
+ * Provides inferences of the form: `e` equals `b + v` modulo `m` where `e` is
+ * an expression, `b` is a `Bound` (typically zero or the value of an SSA
+ * variable), and `v` is an integer in the range `[0 .. m-1]`.
+ */
+
+import java
+private import SSA
+private import RangeUtils
+private import semmle.code.java.controlflow.Guards
+import Bound
+
+/**
+ * Holds if `e + delta` equals `v` at `pos`.
+ */
+private predicate valueFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta) {
+ ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v)
+ or
+ exists(Guard guard, boolean testIsTrue |
+ pos.hasReadOfVar(v) and
+ guard = eqFlowCond(v, e, delta, true, testIsTrue) and
+ guardDirectlyControlsSsaRead(guard, pos, testIsTrue)
+ )
+}
+
+/**
+ * Holds if `add` is the addition of `larg` and `rarg`, neither of which are
+ * `ConstantIntegerExpr`s.
+ */
+private predicate nonConstAddition(Expr add, Expr larg, Expr rarg) {
+ (
+ exists(AddExpr a | a = add |
+ larg = a.getLeftOperand() and
+ rarg = a.getRightOperand()
+ ) or
+ exists(AssignAddExpr a | a = add |
+ larg = a.getDest() and
+ rarg = a.getRhs()
+ )
+ ) and
+ not larg instanceof ConstantIntegerExpr and
+ not rarg instanceof ConstantIntegerExpr
+}
+
+/**
+ * Holds if `sub` is the subtraction of `larg` and `rarg`, where `rarg` is not
+ * a `ConstantIntegerExpr`.
+ */
+private predicate nonConstSubtraction(Expr sub, Expr larg, Expr rarg) {
+ (
+ exists(SubExpr s | s = sub |
+ larg = s.getLeftOperand() and
+ rarg = s.getRightOperand()
+ ) or
+ exists(AssignSubExpr s | s = sub |
+ larg = s.getDest() and
+ rarg = s.getRhs()
+ )
+ ) and
+ not rarg instanceof ConstantIntegerExpr
+}
+
+/** Gets an expression that is the remainder modulo `mod` of `arg`. */
+private Expr modExpr(Expr arg, int mod) {
+ exists(RemExpr rem |
+ result = rem and
+ arg = rem.getLeftOperand() and
+ rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = mod and
+ mod >= 2
+ ) or
+ exists(CompileTimeConstantExpr c |
+ mod = 2.pow([1..30]) and
+ c.getIntValue() = mod - 1 and
+ result.(AndBitwiseExpr).hasOperands(arg, c)
+ ) or
+ result.(ParExpr).getExpr() = modExpr(arg, mod)
+}
+
+/**
+ * Gets a guard that tests whether `v` is congruent with `val` modulo `mod` on
+ * its `testIsTrue` branch.
+ */
+private Guard moduloCheck(SsaVariable v, int val, int mod, boolean testIsTrue) {
+ exists(Expr rem, CompileTimeConstantExpr c, int r, boolean polarity |
+ result.isEquality(rem, c, polarity) and
+ c.getIntValue() = r and
+ rem = modExpr(v.getAUse(), mod) and
+ (
+ testIsTrue = polarity and val = r
+ or
+ testIsTrue = polarity.booleanNot() and mod = 2 and val = 1 - r and
+ (r = 0 or r = 1)
+ )
+ )
+}
+
+/**
+ * Holds if a guard ensures that `v` at `pos` is congruent with `val` modulo `mod`.
+ */
+private predicate moduloGuardedRead(SsaVariable v, SsaReadPosition pos, int val, int mod) {
+ exists(Guard guard, boolean testIsTrue |
+ pos.hasReadOfVar(v) and
+ guard = moduloCheck(v, val, mod, testIsTrue) and
+ guardControlsSsaRead(guard, pos, testIsTrue)
+ )
+}
+
+/** Holds if `factor` is a power of 2 that divides `mask`. */
+bindingset[mask]
+private predicate andmaskFactor(int mask, int factor) {
+ mask % factor = 0 and
+ factor = 2.pow([1..30])
+}
+
+/** Holds if `e` is evenly divisible by `factor`. */
+private predicate evenlyDivisibleExpr(Expr e, int factor) {
+ exists(ConstantIntegerExpr c, int k | k = c.getIntValue() |
+ e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2 or
+ e.(AssignMulExpr).getSource() = c and factor = k.abs() and factor >= 2 or
+ e.(LShiftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0 or
+ e.(AssignLShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0 or
+ e.(AndBitwiseExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f)) or
+ e.(AssignAndExpr).getSource() = c and factor = max(int f | andmaskFactor(k, f))
+ )
+}
+
+private predicate id(BasicBlock x, BasicBlock y) { x = y }
+
+private predicate idOf(BasicBlock x, int y) = equivalenceRelation(id/2)(x, y)
+
+private int getId(BasicBlock bb) { idOf(bb, result) }
+
+/**
+ * Holds if `inp` is an input to `phi` along `edge` and this input has index `r`
+ * in an arbitrary 1-based numbering of the input edges to `phi`.
+ */
+private predicate rankedPhiInput(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int r) {
+ edge.phiInput(phi, inp) and
+ edge = rank[r](SsaReadPositionPhiInputEdge e | e.phiInput(phi, _) | e order by getId(e.getOrigBlock()))
+}
+
+/**
+ * Holds if `rix` is the number of input edges to `phi`.
+ */
+private predicate maxPhiInputRank(SsaPhiNode phi, int rix) {
+ rix = max(int r | rankedPhiInput(phi, _, _, r))
+}
+
+private int gcdLim() { result = 128 }
+
+/**
+ * Gets the greatest common divisor of `x` and `y`. This is restricted to small
+ * inputs and the case when `x` and `y` are not relatively prime.
+ */
+private int gcd(int x, int y) {
+ result != 1 and
+ result = x.abs() and y = 0 and x in [-gcdLim()..gcdLim()]
+ or
+ result = gcd(y, x % y) and y != 0 and x in [-gcdLim()..gcdLim()]
+}
+
+/**
+ * Gets the remainder of `val` modulo `mod`.
+ *
+ * For `mod = 0` the result equals `val` and for `mod > 1` the result is within
+ * the range `[0 .. mod-1]`.
+ */
+bindingset[val, mod]
+private int remainder(int val, int mod) {
+ mod = 0 and result = val or
+ mod > 1 and result = ((val % mod) + mod) % mod
+}
+
+/**
+ * Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
+ */
+private predicate phiSelfModulus(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod) {
+ exists(SsaBound phibound, int v, int m |
+ edge.phiInput(phi, inp) and
+ phibound.getSsa() = phi and
+ ssaModulus(inp, edge, phibound, v, m) and
+ mod = gcd(m, v) and
+ mod != 1
+ )
+}
+
+/**
+ * Holds if `b + val` modulo `mod` is a candidate congruence class for `phi`.
+ */
+private predicate phiModulusInit(SsaPhiNode phi, Bound b, int val, int mod) {
+ exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
+ edge.phiInput(phi, inp) and
+ ssaModulus(inp, edge, b, val, mod)
+ )
+}
+
+/**
+ * Holds if all inputs to `phi` numbered `1` to `rix` are equal to `b + val` modulo `mod`.
+ */
+private predicate phiModulusRankStep(SsaPhiNode phi, Bound b, int val, int mod, int rix) {
+ rix = 0 and
+ phiModulusInit(phi, b, val, mod)
+ or
+ exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge, int v1, int m1 |
+ mod != 1 and
+ val = remainder(v1, mod)
+ |
+ exists(int v2, int m2 |
+ rankedPhiInput(phi, inp, edge, rix) and
+ phiModulusRankStep(phi, b, v1, m1, rix - 1) and
+ ssaModulus(inp, edge, b, v2, m2) and
+ mod = gcd(gcd(m1, m2), v1 - v2)
+ )
+ or
+ exists(int m2 |
+ rankedPhiInput(phi, inp, edge, rix) and
+ phiModulusRankStep(phi, b, v1, m1, rix - 1) and
+ phiSelfModulus(phi, inp, edge, m2) and
+ mod = gcd(m1, m2)
+ )
+ )
+}
+
+/**
+ * Holds if `phi` is equal to `b + val` modulo `mod`.
+ */
+private predicate phiModulus(SsaPhiNode phi, Bound b, int val, int mod) {
+ exists(int r |
+ maxPhiInputRank(phi, r) and
+ phiModulusRankStep(phi, b, val, mod, r)
+ )
+}
+
+/**
+ * Holds if `v` at `pos` is equal to `b + val` modulo `mod`.
+ */
+private predicate ssaModulus(SsaVariable v, SsaReadPosition pos, Bound b, int val, int mod) {
+ phiModulus(v, b, val, mod) and pos.hasReadOfVar(v)
+ or
+ b.(SsaBound).getSsa() = v and pos.hasReadOfVar(v) and val = 0 and mod = 0
+ or
+ exists(Expr e, int val0, int delta |
+ exprModulus(e, b, val0, mod) and
+ valueFlowStepSsa(v, pos, e, delta) and
+ val = remainder(val0 + delta, mod)
+ )
+ or
+ moduloGuardedRead(v, pos, val, mod) and b instanceof ZeroBound
+}
+
+/**
+ * Holds if `e` is equal to `b + val` modulo `mod`.
+ *
+ * There are two cases for the modulus:
+ * - `mod = 0`: The equality `e = b + val` is an ordinary equality.
+ * - `mod > 1`: `val` lies within the range `[0 .. mod-1]`.
+ */
+cached
+predicate exprModulus(Expr e, Bound b, int val, int mod) {
+ e = b.getExpr(val) and mod = 0 or
+ evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound or
+ exists(SsaVariable v, SsaReadPositionBlock bb |
+ ssaModulus(v, bb, b, val, mod) and
+ e = v.getAUse() and
+ bb.getBlock() = e.getBasicBlock()
+ ) or
+ exists(Expr mid, int val0, int delta |
+ exprModulus(mid, b, val0, mod) and
+ valueFlowStep(e, mid, delta) and
+ val = remainder(val0 + delta, mod)
+ ) or
+ exists(ConditionalExpr cond, int v1, int v2, int m1, int m2 |
+ cond = e and
+ condExprBranchModulus(cond, true, b, v1, m1) and
+ condExprBranchModulus(cond, false, b, v2, m2) and
+ mod = gcd(gcd(m1, m2), v1 - v2) and
+ mod != 1 and
+ val = remainder(v1, mod)
+ ) or
+ exists(Bound b1, Bound b2, int v1, int v2, int m1, int m2 |
+ addModulus(e, true, b1, v1, m1) and
+ addModulus(e, false, b2, v2, m2) and
+ mod = gcd(m1, m2) and
+ mod != 1 and
+ val = remainder(v1 + v2, mod)
+ |
+ b = b1 and b2 instanceof ZeroBound or
+ b = b2 and b1 instanceof ZeroBound
+ ) or
+ exists(int v1, int v2, int m1, int m2 |
+ subModulus(e, true, b, v1, m1) and
+ subModulus(e, false, any(ZeroBound zb), v2, m2) and
+ mod = gcd(m1, m2) and
+ mod != 1 and
+ val = remainder(v1 - v2, mod)
+ )
+}
+
+private predicate condExprBranchModulus(ConditionalExpr cond, boolean branch, Bound b, int val, int mod) {
+ exprModulus(cond.getTrueExpr(), b, val, mod) and branch = true or
+ exprModulus(cond.getFalseExpr(), b, val, mod) and branch = false
+}
+
+private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod) {
+ exists(Expr larg, Expr rarg |
+ nonConstAddition(add, larg, rarg)
+ |
+ exprModulus(larg, b, val, mod) and isLeft = true
+ or
+ exprModulus(rarg, b, val, mod) and isLeft = false
+ )
+}
+
+private predicate subModulus(Expr sub, boolean isLeft, Bound b, int val, int mod) {
+ exists(Expr larg, Expr rarg |
+ nonConstSubtraction(sub, larg, rarg)
+ |
+ exprModulus(larg, b, val, mod) and isLeft = true
+ or
+ exprModulus(rarg, b, val, mod) and isLeft = false
+ )
+}
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
index 09a47566f08..d5acafa47ce 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
@@ -68,7 +68,7 @@ private import SSA
private import RangeUtils
private import semmle.code.java.controlflow.internal.GuardsLogic
private import SignAnalysis
-private import ParityAnalysis
+private import ModulusAnalysis
private import semmle.code.java.Reflection
private import semmle.code.java.Collections
private import semmle.code.java.Maps
@@ -133,6 +133,29 @@ private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int
)
}
+/**
+ * Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a
+ * fixed value modulo some `mod > 1`, such that the comparison can be
+ * strengthened by `strengthen` when evaluating to `testIsTrue`.
+ */
+private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) {
+ exists(Bound b, int v1, int v2, int mod, boolean resultIsStrict, int d, int k |
+ // If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
+ // `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
+ // `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
+ // strict then the strengthening amount is instead `k - 1` modulo `mod`:
+ // `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and
+ // thus `k - 1 < y - x` with `0 <= k - 1 < mod`.
+ exprModulus(comp.getLesserOperand(), b, v1, mod) and
+ exprModulus(comp.getGreaterOperand(), b, v2, mod) and
+ (testIsTrue = true or testIsTrue = false) and
+ (if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
+ (resultIsStrict = true and d = 1 or resultIsStrict = false and d = 0) and
+ (testIsTrue = true and k = v2 - v1 or testIsTrue = false and k = v1 - v2) and
+ strengthen = (((k - d) % mod) + mod) % mod
+ )
+}
+
/**
* Gets a condition that tests whether `v` is bounded by `e + delta`.
*
@@ -152,10 +175,10 @@ private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boo
upper = false and strengthen = 1)
else
strengthen = 0) and
- // A non-strict inequality `x <= y` can be strengthened to `x <= y - 1` if
- // `x` and `y` have opposite parities, and a strict inequality `x < y` can
- // be similarly strengthened if `x` and `y` have equal parities.
- (if parityComparison(comp, resultIsStrict) then d2 = strengthen else d2 = 0) and
+ (
+ exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k) or
+ not modulusComparison(comp, testIsTrue, _) and d2 = 0
+ ) and
// A strict inequality `x < y` can be strengthened to `x <= y - 1`.
(resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and
delta = d1 + d2 + d3
diff --git a/java/ql/test/query-tests/RangeAnalysis/A.java b/java/ql/test/query-tests/RangeAnalysis/A.java
index abe2b3d4aed..d18d09d2ccd 100644
--- a/java/ql/test/query-tests/RangeAnalysis/A.java
+++ b/java/ql/test/query-tests/RangeAnalysis/A.java
@@ -76,7 +76,7 @@ public class A {
int[] a = new int[3 * n];
int sum = 0;
for (int i = 0; i < a.length; i += 3) {
- sum += a[i] + a[i + 1] + a[i + 2]; // OK - FP
+ sum += a[i] + a[i + 1] + a[i + 2]; // OK
}
}
diff --git a/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected
index eb59dd6d262..23daa615a19 100644
--- a/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected
+++ b/java/ql/test/query-tests/RangeAnalysis/ArrayIndexOutOfBounds.expected
@@ -5,8 +5,6 @@
| A.java:46:14:46:22 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:55:14:55:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:64:14:64:19 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
-| A.java:79:21:79:28 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
-| A.java:79:32:79:39 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 1. |
| A.java:86:12:86:16 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
| A.java:97:18:97:31 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length + 8. |
| A.java:110:14:110:21 | ...[...] | This array access might be out of bounds, as the index might be equal to the array length. |
From ca8ca55828c49e6b72245ae9d57c1dde68d197cc Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Wed, 10 Oct 2018 14:09:19 +0200
Subject: [PATCH 14/98] Java: Deprecate ParityAnalysis.
---
java/ql/src/filters/ImportAdditionalLibraries.ql | 1 +
.../semmle/code/java/dataflow/ParityAnalysis.qll | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/java/ql/src/filters/ImportAdditionalLibraries.ql b/java/ql/src/filters/ImportAdditionalLibraries.ql
index d3a5f8bc52a..7fc55fbc960 100644
--- a/java/ql/src/filters/ImportAdditionalLibraries.ql
+++ b/java/ql/src/filters/ImportAdditionalLibraries.ql
@@ -10,6 +10,7 @@
import java
import semmle.code.java.dataflow.Guards
+import semmle.code.java.dataflow.ParityAnalysis
import semmle.code.java.security.DataFlow
from File f, string tag
diff --git a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
index 152f240914d..9014b08650a 100644
--- a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
@@ -1,4 +1,6 @@
/**
+ * DEPRECATED: semmle.code.java.dataflow.ModulusAnalysis instead.
+ *
* Parity Analysis.
*
* The analysis is implemented as an abstract interpretation over the
@@ -12,6 +14,7 @@ private import SignAnalysis
private import semmle.code.java.Reflection
/** Gets an expression that is the remainder modulo 2 of `arg`. */
+deprecated
private Expr mod2(Expr arg) {
exists(RemExpr rem |
result = rem and
@@ -23,6 +26,7 @@ private Expr mod2(Expr arg) {
}
/** An expression that calculates remainder modulo 2. */
+deprecated
private class Mod2 extends Expr {
Mod2() {
this = mod2(_)
@@ -38,6 +42,7 @@ private class Mod2 extends Expr {
* Parity represented as booleans. Even corresponds to `false` and odd
* corresponds to `true`.
*/
+deprecated
class Parity extends boolean {
Parity() { this = true or this = false }
predicate isEven() { this = false }
@@ -48,6 +53,7 @@ class Parity extends boolean {
* Gets a condition that performs a parity check on `v`, such that `v` has
* the given parity if the condition evaluates to `testIsTrue`.
*/
+deprecated
private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
exists(Mod2 rem, CompileTimeConstantExpr c, int r, boolean polarity |
result.isEquality(rem, c, polarity) and
@@ -65,6 +71,7 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
/**
* Gets the parity of `e` if it can be directly determined.
*/
+deprecated
private Parity certainExprParity(Expr e) {
exists(int i | e.(ConstantIntegerExpr).getIntValue() = i |
if i % 2 = 0 then result.isEven() else result.isOdd()
@@ -92,6 +99,7 @@ private Parity certainExprParity(Expr e) {
/**
* Gets the expression that defines the array length that equals `len`, if any.
*/
+deprecated
private Expr arrLenDef(FieldAccess len) {
exists(SsaVariable arr |
len.getField() instanceof ArrayLengthField and
@@ -101,6 +109,7 @@ private Expr arrLenDef(FieldAccess len) {
}
/** Gets a possible parity for `v`. */
+deprecated
private Parity ssaParity(SsaVariable v) {
exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() |
result = exprParity(def.(VariableAssign).getSource()) or
@@ -115,6 +124,7 @@ private Parity ssaParity(SsaVariable v) {
}
/** Gets a possible parity for `f`. */
+deprecated
private Parity fieldParity(Field f) {
result = exprParity(f.getAnAssignedValue()) or
exists(UnaryAssignExpr u | u.getExpr() = f.getAnAccess() and (result = true or result = false)) or
@@ -128,6 +138,7 @@ private Parity fieldParity(Field f) {
}
/** Holds if the parity of `e` is too complicated to determine. */
+deprecated
private predicate unknownParity(Expr e) {
e instanceof AssignDivExpr or
e instanceof AssignRShiftExpr or
@@ -144,6 +155,7 @@ private predicate unknownParity(Expr e) {
}
/** Gets a possible parity for `e`. */
+deprecated
private Parity exprParity(Expr e) {
result = certainExprParity(e) or
not exists(certainExprParity(e)) and
@@ -207,14 +219,18 @@ private Parity exprParity(Expr e) {
/**
* Gets the parity of `e` if it can be uniquely determined.
*/
+deprecated
Parity getExprParity(Expr e) {
result = exprParity(e) and 1 = count(exprParity(e))
}
/**
+ * DEPRECATED: semmle.code.java.dataflow.ModulusAnalysis instead.
+ *
* Holds if the parity can be determined for both sides of `comp`. The boolean
* `eqparity` indicates whether the two sides have equal or opposite parity.
*/
+deprecated
predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
exists(Expr left, Expr right, boolean lpar, boolean rpar |
comp.getLeftOperand() = left and
From 1a66f7e249ce3a77ae131c51499fbf442d2be70d Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Wed, 10 Oct 2018 15:39:56 +0200
Subject: [PATCH 15/98] Java: Add change note.
---
change-notes/1.19/analysis-java.md | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/change-notes/1.19/analysis-java.md b/change-notes/1.19/analysis-java.md
index becec5b9d98..3d5e09b5746 100644
--- a/change-notes/1.19/analysis-java.md
+++ b/change-notes/1.19/analysis-java.md
@@ -4,13 +4,17 @@
## New queries
-| **Query** | **Tags** | **Purpose** |
-|-----------------------------------------------|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| **Query** | **Tags** | **Purpose** |
+|-----------------------------|-----------|--------------------------------------------------------------------|
## Changes to existing queries
-| **Query** | **Expected impact** | **Change** |
-| Unreachable catch clause (`java/unreachable-catch-clause`) | Fewer false-positive results | This rule now accounts for calls to generic methods that throw generic exceptions. |
+| **Query** | **Expected impact** | **Change** |
+|----------------------------|------------------------|------------------------------------------------------------------|
+| Array index out of bounds (`java/index-out-of-bounds`) | Fewer false positive results | False positives involving arrays with a length evenly divisible by 3 or some greater number and an index being increased with a similar stride length are no longer reported. |
+| Unreachable catch clause (`java/unreachable-catch-clause`) | Fewer false positive results | This rule now accounts for calls to generic methods that throw generic exceptions. |
## Changes to QL libraries
+* The `ParityAnalysis` library is replaced with the more general `ModulusAnalysis` library, which improves the range analysis.
+
From da3e960e39c63b23e827f0a72a48dfbd94c92efb Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 11 Oct 2018 12:45:45 +0100
Subject: [PATCH 16/98] JS: address review comments
---
.../ql/src/semmle/javascript/dataflow/Configuration.qll | 7 ++++++-
.../ql/src/semmle/javascript/security/TaintedObject.qll | 6 +++---
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
index 6dc998c2d49..6daa44daa6c 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
@@ -311,12 +311,17 @@ abstract class BarrierGuardNode extends DataFlow::Node {
abstract predicate blocks(boolean outcome, Expr e);
/**
- * Holds if this barrier guard blocks all labels.
+ * Holds if this barrier guard should block all labels.
+ *
+ * To block specific labels only, subclasses should override this with `none()` and
+ * also override `blocksSpecificLabel`.
*/
predicate blocksAllLabels() { any() }
/**
* Holds if this barrier guard only blocks specific labels, and `label` is one of them.
+ *
+ * Subclasses that override this predicate should also override `blocksAllLabels`.
*/
predicate blocksSpecificLabel(FlowLabel label) { none() }
}
diff --git a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
index 4f7d715b30b..6778a65d2e6 100644
--- a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
+++ b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
@@ -22,11 +22,11 @@ module TaintedObject {
}
/**
- * Gets the flow label representing a deeply tainted objects.
+ * Gets the flow label representing a deeply tainted object.
*
- * A "tainted object" is an array or object whose values are all assumed to be tainted as well.
+ * A "tainted object" is an array or object whose properties values are all assumed to be tainted as well.
*
- * Note that the presence of the `object-taint` label generally implies the presence of the `taint` label as well.
+ * Note that the presence of the this label generally implies the presence of the `taint` label as well.
*/
FlowLabel label() { result instanceof TaintedObjectLabel }
From 9d5e674fc550826f5b79fad660c03f3f1bbfca2d Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 11 Oct 2018 16:42:36 +0100
Subject: [PATCH 17/98] CPP: Fix hasXMacro performance.
---
cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql
index b4eabe1f175..1f1ddef6a9c 100644
--- a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql
+++ b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql
@@ -107,15 +107,17 @@ predicate defUndef(File f, string macroName) {
}
/**
- * Header file `hf` looks like it contains an x-macro called
- * `macroName`. That is, a macro that is used to interpret the
+ * Header file `hf` looks like it contains an x-macro.
+ * That is, a macro that is used to interpret the
* data in `hf`, usually defined just before including that file
* and undefined immediately afterwards.
*/
-predicate hasXMacro(HeaderFile hf, string macroName) {
- usesMacro(hf, macroName) and
- forex(Include i | i.getIncludedFile() = hf |
- defUndef(i.getFile(), macroName)
+predicate hasXMacro(HeaderFile hf) {
+ exists(string macroName |
+ usesMacro(hf, macroName) and
+ forex(File f | f.getAnIncludedFile() = hf |
+ defUndef(f, macroName)
+ )
)
}
@@ -135,7 +137,7 @@ where not hf instanceof IncludeGuardedHeader
exists(UsingEntry ue | ue.getFile() = hf)
)
// Exclude files which look like they contain 'x-macros'
- and not hasXMacro(hf, _)
+ and not hasXMacro(hf)
// Exclude files which are always #imported.
and not forex(Include i | i.getIncludedFile() = hf | i instanceof Import)
// Exclude files which are only included once.
From 85283d63cec21a342b7367e0a1ed2f2e2b387c93 Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Fri, 12 Oct 2018 15:57:01 -0700
Subject: [PATCH 18/98] C++ : NULL application name with an unquoted path in
call to CreateProcess Calling a function of the CreatePorcess* family of
functions, which may result in a security vulnerability if the path contains
spaces.
---
.gitignore | 1 +
cpp/config/suites/security/cwe-428 | 3 +
cpp/config/suites/security/default | 1 +
.../CWE/CWE-428/UnsafeCreateProcessCall.cpp | 11 +
.../CWE/CWE-428/UnsafeCreateProcessCall.qhelp | 46 ++
.../CWE/CWE-428/UnsafeCreateProcessCall.ql | 135 +++++
.../CWE/CWE-428/UnsafeCreateProcessCall.cpp | 503 ++++++++++++++++++
.../CWE-428/UnsafeCreateProcessCall.expected | 13 +
.../CWE/CWE-428/UnsafeCreateProcessCall.qlref | 1 +
9 files changed, 714 insertions(+)
create mode 100644 cpp/config/suites/security/cwe-428
create mode 100644 cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
create mode 100644 cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp
create mode 100644 cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref
diff --git a/.gitignore b/.gitignore
index 7e82b2f488c..671c5aefe0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
+/.vs/ql/v15/.suo
diff --git a/cpp/config/suites/security/cwe-428 b/cpp/config/suites/security/cwe-428
new file mode 100644
index 00000000000..bef395b717a
--- /dev/null
+++ b/cpp/config/suites/security/cwe-428
@@ -0,0 +1,3 @@
+# CWE-428: Unquoted Search Path or Element
++ semmlecode-cpp-queries/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql: /CWE/CWE-428
+ @name NULL application name with an unquoted path in call to CreateProcess (CWE-428)
diff --git a/cpp/config/suites/security/default b/cpp/config/suites/security/default
index 4225ae9808b..b61734396ff 100644
--- a/cpp/config/suites/security/default
+++ b/cpp/config/suites/security/default
@@ -18,6 +18,7 @@
@import "cwe-327"
@import "cwe-367"
@import "cwe-416"
+@import "cwe-428"
@import "cwe-457"
@import "cwe-468"
@import "cwe-676"
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
new file mode 100644
index 00000000000..62d2af1d17a
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
@@ -0,0 +1,11 @@
+STARTUPINFOW si;
+PROCESS_INFORMATION pi;
+
+// ...
+
+CreateProcessW( // BUG
+ NULL, // lpApplicationName
+ (LPWSTR)L"C:\\Program Files\\MyApp", // lpCommandLine
+ NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
+
+// ...
\ No newline at end of file
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp
new file mode 100644
index 00000000000..9717c416e55
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp
@@ -0,0 +1,46 @@
+
+
+
+
+
This query indicates that there is a call to a function of the CreatePorcess* family of functions, which may result in a security vulnerability if the path contains spaces.
+
+
+
+
Do not use NULL for the lpApplicationName argument to the CreateProcess* function.
+
If you pass NULL for lpApplicationName, use quotation marks around the executable path in lpCommandLine.
+
+
+
+
In the following example, CreateProcessW is called with a NULL value for lpApplicationName,
+and the value for lpCommandLine that represent the application path is not quoted and has spaces int.
+
If an attacker has access to the file system, it is possible to elevate privileges by creating a file such as "C:\Program.exe" that will be executed instead of the intended application.
+
+
+
To fix this issue, specify a valid string for lpApplicationName, or quote the path for lpCommandLine. For example:
+
+
+
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
new file mode 100644
index 00000000000..7ff62a1cd86
--- /dev/null
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
@@ -0,0 +1,135 @@
+/**
+ * @name NULL application name with an unquoted path in call to CreateProcess
+ * @description Calling a function of the CreatePorcess* family of functions, which may result in a security vulnerability if the path contains spaces.
+ * @id cpp/unsafe-create-process-call
+ * @kind problem
+ * @problem.severity error
+ * @precision medium
+ * @tags security
+ * external/cwe/cwe-428
+ * external/microsoft/C6277
+ */
+
+import cpp
+import semmle.code.cpp.dataflow.DataFlow
+import semmle.code.cpp.dataflow.DataFlow2
+
+/**
+ * A function call to CreateProcess (either wide-char or single byte string versions)
+ */
+class CreateProcessFunctionCall extends FunctionCall {
+ CreateProcessFunctionCall() {
+ (
+ this.getTarget().hasGlobalName("CreateProcessA") or
+ this.getTarget().hasGlobalName("CreateProcessW") or
+ this.getTarget().hasGlobalName("CreateProcessWithTokenW") or
+ this.getTarget().hasGlobalName("CreateProcessWithLogonW") or
+ this.getTarget().hasGlobalName("CreateProcessAsUserA") or
+ this.getTarget().hasGlobalName("CreateProcessAsUserW")
+ )
+ }
+
+ int getApplicationNameArgumentId() {
+ if(
+ this.getTarget().hasGlobalName("CreateProcessA") or
+ this.getTarget().hasGlobalName("CreateProcessW")
+ ) then ( result = 0 )
+ else if (
+ this.getTarget().hasGlobalName("CreateProcessWithTokenW")
+ ) then ( result = 2 )
+ else if (
+ this.getTarget().hasGlobalName("CreateProcessWithLogonW")
+ ) then ( result = 4 )
+ else if(
+ this.getTarget().hasGlobalName("CreateProcessAsUserA") or
+ this.getTarget().hasGlobalName("CreateProcessAsUserW")
+ ) then ( result = 1 )
+ else (result = -1 )
+ }
+
+ int getCommandLineArgumentId() {
+ if(
+ this.getTarget().hasGlobalName("CreateProcessA") or
+ this.getTarget().hasGlobalName("CreateProcessW")
+ ) then ( result = 1 )
+ else if (
+ this.getTarget().hasGlobalName("CreateProcessWithTokenW")
+ ) then ( result = 3 )
+ else if (
+ this.getTarget().hasGlobalName("CreateProcessWithLogonW")
+ ) then ( result = 5 )
+ else if(
+ this.getTarget().hasGlobalName("CreateProcessAsUserA") or
+ this.getTarget().hasGlobalName("CreateProcessAsUserW")
+ ) then ( result = 2 )
+ else (result = -1 )
+ }
+}
+
+/**
+ * Dataflow that detects a call to CreateProcess with a NULL value for lpApplicationName argument
+ */
+class NullAppNameCreateProcessFunctionConfiguration extends DataFlow::Configuration {
+ NullAppNameCreateProcessFunctionConfiguration() {
+ this = "NullAppNameCreateProcessFunctionConfiguration"
+ }
+
+ override predicate isSource(DataFlow::Node source) {
+ nullValue(source.asExpr())
+ }
+
+ override predicate isSink(DataFlow::Node sink) {
+ exists(
+ CreateProcessFunctionCall call, Expr val |
+ val = sink.asExpr() |
+ val = call.getArgument(call.getApplicationNameArgumentId())
+ )
+ }
+}
+
+/**
+ * Dataflow that detects a call to CreateProcess with an unquoted commandLine argument
+ */
+class QuotedCommandInCreateProcessFunctionConfiguration extends DataFlow2::Configuration {
+ QuotedCommandInCreateProcessFunctionConfiguration() {
+ this = "QuotedCommandInCreateProcessFunctionConfiguration"
+ }
+
+ override predicate isSource(DataFlow2::Node source) {
+ exists( string s |
+ s = source.asExpr().getValue().toString()
+ and
+ not isQuotedApplicationNameOnCmd(s)
+ )
+ }
+
+ override predicate isSink(DataFlow2::Node sink) {
+ exists(
+ CreateProcessFunctionCall call, Expr val |
+ val = sink.asExpr() |
+ val = call.getArgument(call.getCommandLineArgumentId())
+ )
+ }
+}
+
+bindingset[s]
+predicate isQuotedApplicationNameOnCmd(string s){
+ s.regexpMatch("\"([^\"])*\"(\\s|.)*")
+}
+
+from CreateProcessFunctionCall call, string msg1, string msg2
+where
+ exists( Expr source, Expr appName,
+ NullAppNameCreateProcessFunctionConfiguration nullAppConfig |
+ appName = call.getArgument(call.getApplicationNameArgumentId())
+ and nullAppConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(appName))
+ and msg1 = call.toString() + " with lpApplicationName == NULL (" + appName + ")"
+ )
+ and
+ exists( Expr source, Expr cmd,
+ QuotedCommandInCreateProcessFunctionConfiguration quotedConfig |
+ cmd = call.getArgument(call.getCommandLineArgumentId())
+ and quotedConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(cmd))
+ and msg2 = " and with an unquoted lpCommandLine (" + cmd + ") may result in a security vulnerability if the path contains spaces."
+ )
+select call, msg1 + " " + msg2
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
new file mode 100644
index 00000000000..20508ee5f1b
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
@@ -0,0 +1,503 @@
+// semmle-extractor-options: --microsoft
+#define NULL 0
+#define FALSE 0
+#define far
+#define LOGON_WITH_PROFILE 0x00000001
+
+typedef char CHAR;
+typedef unsigned short WCHAR;
+typedef int BOOL;
+#define CONST const
+typedef CHAR *PCHAR, *LPCH, *PCH;
+typedef CONST CHAR *LPCCH, *PCCH;
+typedef CHAR *NPSTR, *LPSTR, *PSTR;
+typedef CONST PSTR *PCZPSTR;
+typedef CONST CHAR *LPCSTR, *PCSTR;
+typedef WCHAR *PWCHAR, *LPWCH, *PWCH;
+typedef CONST WCHAR *LPCWCH, *PCWCH;
+typedef WCHAR *NWPSTR, *LPWSTR, *PWSTR;
+typedef PWSTR *PZPWSTR;
+typedef CONST PWSTR *PCZPWSTR;
+typedef CONST WCHAR *LPCWSTR, *PCWSTR;
+typedef unsigned long DWORD;
+typedef void far *LPVOID;
+typedef unsigned short WORD;
+typedef unsigned char BYTE;
+typedef BYTE far *LPBYTE;
+typedef void *HANDLE;
+
+typedef struct _SECURITY_ATTRIBUTES {
+ DWORD nLength;
+ LPVOID lpSecurityDescriptor;
+ BOOL bInheritHandle;
+} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
+
+typedef struct _PROCESS_INFORMATION {
+ HANDLE hProcess;
+ HANDLE hThread;
+ DWORD dwProcessId;
+ DWORD dwThreadId;
+} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
+
+typedef struct _STARTUPINFOA {
+ DWORD cb;
+ LPSTR lpReserved;
+ LPSTR lpDesktop;
+ LPSTR lpTitle;
+ DWORD dwX;
+ DWORD dwY;
+ DWORD dwXSize;
+ DWORD dwYSize;
+ DWORD dwXCountChars;
+ DWORD dwYCountChars;
+ DWORD dwFillAttribute;
+ DWORD dwFlags;
+ WORD wShowWindow;
+ WORD cbReserved2;
+ LPBYTE lpReserved2;
+ HANDLE hStdInput;
+ HANDLE hStdOutput;
+ HANDLE hStdError;
+} STARTUPINFOA, *LPSTARTUPINFOA;
+typedef struct _STARTUPINFOW {
+ DWORD cb;
+ LPWSTR lpReserved;
+ LPWSTR lpDesktop;
+ LPWSTR lpTitle;
+ DWORD dwX;
+ DWORD dwY;
+ DWORD dwXSize;
+ DWORD dwYSize;
+ DWORD dwXCountChars;
+ DWORD dwYCountChars;
+ DWORD dwFillAttribute;
+ DWORD dwFlags;
+ WORD wShowWindow;
+ WORD cbReserved2;
+ LPBYTE lpReserved2;
+ HANDLE hStdInput;
+ HANDLE hStdOutput;
+ HANDLE hStdError;
+} STARTUPINFOW, *LPSTARTUPINFOW;
+
+typedef STARTUPINFOW STARTUPINFO;
+typedef LPSTARTUPINFOW LPSTARTUPINFO;
+
+
+BOOL
+CreateProcessA(
+ LPCSTR lpApplicationName,
+ LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation
+);
+
+BOOL
+CreateProcessW(
+ LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation
+);
+
+#define CreateProcess CreateProcessW
+
+BOOL
+CreateProcessWithTokenW(
+ HANDLE hToken,
+ DWORD dwLogonFlags,
+ LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation
+);
+
+BOOL
+CreateProcessWithLogonW(
+ LPCWSTR lpUsername,
+ LPCWSTR lpDomain,
+ LPCWSTR lpPassword,
+ DWORD dwLogonFlags,
+ LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation
+);
+
+BOOL
+CreateProcessAsUserA(
+ HANDLE hToken,
+ LPCSTR lpApplicationName,
+ LPSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCSTR lpCurrentDirectory,
+ LPSTARTUPINFOA lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation
+);
+
+BOOL
+CreateProcessAsUserW(
+ HANDLE hToken,
+ LPCWSTR lpApplicationName,
+ LPWSTR lpCommandLine,
+ LPSECURITY_ATTRIBUTES lpProcessAttributes,
+ LPSECURITY_ATTRIBUTES lpThreadAttributes,
+ BOOL bInheritHandles,
+ DWORD dwCreationFlags,
+ LPVOID lpEnvironment,
+ LPCWSTR lpCurrentDirectory,
+ LPSTARTUPINFOW lpStartupInfo,
+ LPPROCESS_INFORMATION lpProcessInformation
+);
+
+#define CreateProcessAsUser CreateProcessAsUserW
+
+void positiveTestCases()
+{
+ LPCWSTR lpCommandLine = (LPCWSTR)L"C:\\Program Files\\MyApp";
+ HANDLE h = 0;
+ LPWSTR lpApplicationName = NULL;
+
+ // CreatePorcessA
+ CreateProcessA( //BUG
+ NULL,
+ (LPSTR)"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcessW
+ CreateProcessW( //BUG
+ NULL,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcess
+ CreateProcess( //BUG
+ NULL,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // lpCommandLine as hardcoded variable
+ CreateProcess( //BUG
+ NULL,
+ (LPWSTR)lpCommandLine,
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessWithTokenW
+ CreateProcessWithTokenW( //BUG
+ h,
+ LOGON_WITH_PROFILE,
+ NULL,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessWithLogonW
+ CreateProcessWithLogonW( //BUG
+ (LPCWSTR)L"UserName",
+ (LPCWSTR)L"CONTOSO",
+ (LPCWSTR)L"",
+ LOGON_WITH_PROFILE,
+ NULL,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUserA
+ CreateProcessAsUserA( //BUG
+ h,
+ NULL,
+ (LPSTR)"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUserW
+ CreateProcessAsUserW( //BUG
+ h,
+ NULL,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUser
+ CreateProcessAsUser( //BUG
+ h,
+ NULL,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcess with a hardcoded variable for application Name (NULL)
+ CreateProcess( //BUG
+ lpApplicationName,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+}
+
+void PositiveTestCasesWithCmdLineParameter(LPWSTR lpCommandLine)
+{
+ // lpCommandLine as variable
+ CreateProcess( //BUG - Depends on the caller
+ NULL,
+ lpCommandLine,
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+}
+
+void PositiveTestCasesWithCmdLineParameter_caller()
+{
+ PositiveTestCasesWithCmdLineParameter((LPWSTR)L"C:\\Program Files\\MyApp");
+}
+
+// NOTE: This function will not be flagged as having a bug by this rule.
+// but as it is, the function can still be misused
+void FalseNegativeTestCasesWithCmdLineParameter(LPWSTR lpCommandLine)
+{
+ // lpCommandLine as variable
+ CreateProcess( //Depends on the caller, this time the caller will quote
+ NULL,
+ lpCommandLine,
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+}
+
+void FalseNegativeTestCasesWithCmdLineParameter_caller()
+{
+ // No bug - escaped command line
+ // But compare with "PositiveTestCasesWithCmdLineParameter"
+ FalseNegativeTestCasesWithCmdLineParameter((LPWSTR)L"\"C:\\Program Files\\MyApp\"");
+}
+
+void PositiveTestCasesWithAppNameParameter(LPWSTR lpApplicationName)
+{
+ HANDLE h = 0;
+
+ CreateProcessWithTokenW( //BUG - Depends on the caller. In this case the caller sends NULL
+ h,
+ LOGON_WITH_PROFILE,
+ lpApplicationName,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+}
+
+void PositiveTestCasesWithAppNameParameter_caller()
+{
+ PositiveTestCasesWithAppNameParameter(NULL);
+}
+
+// NOTE: This function will not be flagged as having a bug by this rule.
+// but as it is, the function can still be misused
+void FalseNegativeTestCasesWithAppNameParameter(LPWSTR lpApplicationName)
+{
+ HANDLE h = 0;
+
+ CreateProcessWithTokenW( // Depends on the caller. In this case the caller sends an ApplicatioName
+ h,
+ LOGON_WITH_PROFILE,
+ lpApplicationName,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+}
+
+void FalseNegativeTestCasesWithAppNameParameter_caller()
+{
+ // No bug - escaped command line
+ // But compare with "PositiveTestCasesWithAppNameParameter"
+ FalseNegativeTestCasesWithAppNameParameter((LPWSTR)L"MyApp.exe");
+}
+
+bool MayReturnFalse()
+{
+ // return ((rand() % 2) == 0);
+ return true;
+}
+
+void TestCaseProbablyBug()
+{
+ LPCWSTR lpApplicationName = NULL;
+
+ if (!MayReturnFalse())
+ {
+ lpApplicationName = (LPCWSTR)L"app.exe";
+ }
+
+ CreateProcessWithLogonW( // BUG (Probably - depends on a condition that may be false)
+ (LPCWSTR)L"UserName",
+ (LPCWSTR)L"CONTOSO",
+ (LPCWSTR)L"",
+ LOGON_WITH_PROFILE,
+ (LPWSTR)lpApplicationName,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+
+ if (lpApplicationName)
+ {
+ delete[] lpApplicationName;
+ }
+}
+
+void negativeTestCases_quotedCommandLine()
+{
+ LPCWSTR lpCommandLine = (LPCWSTR)L"\"C:\\Program Files\\MyApp\" with additional params";
+ HANDLE h = 0;
+ LPWSTR lpApplicationName = NULL;
+
+ // CreatePorcessA
+ CreateProcessA(
+ NULL,
+ (LPSTR)"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcessW
+ CreateProcessW(
+ NULL,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcess
+ CreateProcess(
+ NULL,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // lpCommandLine as hardcoded variable
+ CreateProcess(
+ NULL,
+ (LPWSTR)lpCommandLine,
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessWithTokenW
+ CreateProcessWithTokenW(
+ h,
+ LOGON_WITH_PROFILE,
+ NULL,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessWithLogonW
+ CreateProcessWithLogonW(
+ (LPCWSTR)L"UserName",
+ (LPCWSTR)L"CONTOSO",
+ (LPCWSTR)L"",
+ LOGON_WITH_PROFILE,
+ NULL,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUserA
+ CreateProcessAsUserA(
+ h,
+ NULL,
+ (LPSTR)"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUserW
+ CreateProcessAsUserW(
+ h,
+ NULL,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUser
+ CreateProcessAsUser(
+ h,
+ NULL,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcess with a hardcoded variable for application Name (NULL)
+ CreateProcess(
+ lpApplicationName,
+ (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+}
+
+void negativeTestCases_AppNameSet()
+{
+ LPCWSTR lpCommandLine = (LPCWSTR)L"C:\\Program Files\\MyApp";
+ HANDLE h = 0;
+ LPCWSTR lpApplicationName = (LPCWSTR)L"MyApp.exe";
+
+ // CreatePorcessA
+ CreateProcessA(
+ (LPSTR)"MyApp.exe",
+ (LPSTR)"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcessW
+ CreateProcessW(
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcess
+ CreateProcess(
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // lpCommandLine as hardcoded variable
+ CreateProcess(
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)lpCommandLine,
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessWithTokenW
+ CreateProcessWithTokenW(
+ h,
+ LOGON_WITH_PROFILE,
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessWithLogonW
+ CreateProcessWithLogonW(
+ (LPCWSTR)L"UserName",
+ (LPCWSTR)L"CONTOSO",
+ (LPCWSTR)L"",
+ LOGON_WITH_PROFILE,
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUserA
+ CreateProcessAsUserA(
+ h,
+ (LPSTR)"MyApp.exe",
+ (LPSTR)"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUserW
+ CreateProcessAsUserW(
+ h,
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreateProcessAsUser
+ CreateProcessAsUser(
+ h,
+ (LPWSTR)L"MyApp.exe",
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // CreatePorcess with a hardcoded variable for application Name (NULL)
+ CreateProcess(
+ (LPWSTR)lpApplicationName,
+ (LPWSTR)L"C:\\Program Files\\MyApp",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
new file mode 100644
index 00000000000..1f4519cabb0
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
@@ -0,0 +1,13 @@
+| UnsafeCreateProcessCall.cpp:184:5:184:18 | call to CreateProcessA | call to CreateProcessA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:190:5:190:18 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:196:5:196:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:202:5:202:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:208:5:208:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:216:5:216:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:226:5:226:24 | call to CreateProcessAsUserA | call to CreateProcessAsUserA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:233:5:233:24 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:240:5:240:23 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:247:5:247:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:256:5:256:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:289:5:289:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:338:5:338:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref
new file mode 100644
index 00000000000..f2012f0c678
--- /dev/null
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.qlref
@@ -0,0 +1 @@
+Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
\ No newline at end of file
From 603c3d6a433e022871881b2f0da0fe8c0b1ffeba Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Thu, 4 Oct 2018 15:42:59 +0200
Subject: [PATCH 19/98] C#: Teach null-guards library about pattern matching
---
change-notes/1.19/analysis-csharp.md | 40 ++--
.../Control-Flow/ConstantCondition.ql | 4 +-
.../csharp/commons/StructuralComparison.qll | 4 +
.../code/csharp/controlflow/BasicBlocks.qll | 42 ++--
.../code/csharp/controlflow/Completion.qll | 17 +-
.../csharp/controlflow/ControlFlowGraph.qll | 16 +-
.../semmle/code/csharp/controlflow/Guards.qll | 167 +++++++++++---
.../security/dataflow/ConditionalBypass.qll | 12 +-
.../controlflow/graph/ConditionBlock.expected | 210 ++++++++++++++++++
.../controlflow/graph/ConditionBlock.ql | 5 +-
.../controlflow/guards/GuardedExpr.expected | 5 +
.../controlflow/guards/Guards.cs | 38 ++++
.../guards/MatchingGuardedExpr.expected | 12 +
.../controlflow/guards/MatchingGuardedExpr.ql | 6 +
.../guards/NullGuardedExpr.expected | 5 +
.../guards/NullnessGuardedExpr.expected | 3 +
.../controlflow/guards/NullnessGuardedExpr.ql | 6 +
17 files changed, 507 insertions(+), 85 deletions(-)
create mode 100644 csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.expected
create mode 100644 csharp/ql/test/library-tests/controlflow/guards/MatchingGuardedExpr.ql
create mode 100644 csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.expected
create mode 100644 csharp/ql/test/library-tests/controlflow/guards/NullnessGuardedExpr.ql
diff --git a/change-notes/1.19/analysis-csharp.md b/change-notes/1.19/analysis-csharp.md
index 5d67ff96a0d..3b85b8f367c 100644
--- a/change-notes/1.19/analysis-csharp.md
+++ b/change-notes/1.19/analysis-csharp.md
@@ -1,20 +1,20 @@
-# Improvements to C# analysis
-
-## General improvements
-
-* The control flow graph construction now takes simple Boolean conditions on local scope variables into account. For example, in `if (b) x = 0; if (b) x = 1;`, the control flow graph will reflect that taking the `true` (resp. `false`) branch in the first condition implies taking the same branch in the second condition. In effect, the first assignment to `x` will now be identified as being dead.
-
-## New queries
-
-| **Query** | **Tags** | **Purpose** |
-|-----------------------------|-----------|--------------------------------------------------------------------|
-| *@name of query (Query ID)* | *Tags* |*Aim of the new query and whether it is enabled by default or not* |
-
-## Changes to existing queries
-
-| **Query** | **Expected impact** | **Change** |
-|----------------------------|------------------------|------------------------------------------------------------------|
-| *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* |
-
-
-## Changes to QL libraries
+# Improvements to C# analysis
+
+## General improvements
+
+* The control flow graph construction now takes simple Boolean conditions on local scope variables into account. For example, in `if (b) x = 0; if (b) x = 1;`, the control flow graph will reflect that taking the `true` (resp. `false`) branch in the first condition implies taking the same branch in the second condition. In effect, the first assignment to `x` will now be identified as being dead.
+
+## New queries
+
+| **Query** | **Tags** | **Purpose** |
+|-----------------------------|-----------|--------------------------------------------------------------------|
+| *@name of query (Query ID)* | *Tags* |*Aim of the new query and whether it is enabled by default or not* |
+
+## Changes to existing queries
+
+| **Query** | **Expected impact** | **Change** |
+|----------------------------|------------------------|------------------------------------------------------------------|
+| *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* |
+
+
+## Changes to QL libraries
diff --git a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql
index db38047c4e2..fc6270dce0e 100644
--- a/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql
+++ b/csharp/ql/src/Bad Practices/Control-Flow/ConstantCondition.ql
@@ -79,7 +79,7 @@ class ConstantNullnessCondition extends ConstantCondition {
cfn = this.getAControlFlowNode() |
exists(ControlFlow::SuccessorTypes::NullnessSuccessor t |
exists(cfn.getASuccessorByType(t)) |
- if t.isNull() then b = true else b = false
+ b = t.getValue()
) and
strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1
)
@@ -102,7 +102,7 @@ class ConstantMatchingCondition extends ConstantCondition {
cfn = this.getAControlFlowNode() |
exists(ControlFlow::SuccessorTypes::MatchingSuccessor t |
exists(cfn.getASuccessorByType(t)) |
- if t.isMatch() then b = true else b = false
+ b = t.getValue()
) and
strictcount(ControlFlow::SuccessorType t | exists(cfn.getASuccessorByType(t))) = 1
)
diff --git a/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll b/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll
index deec41643ee..82c4b5cb8b7 100644
--- a/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll
+++ b/csharp/ql/src/semmle/code/csharp/commons/StructuralComparison.qll
@@ -85,6 +85,7 @@ abstract class StructuralComparisonConfiguration extends string {
value = x.getValue()
}
+ pragma [nomagic]
private predicate sameByStructure(Element x, Element y) {
// At least one of `x` and `y` must not have a value, they must have
// the same kind, and the same number of children
@@ -121,6 +122,7 @@ abstract class StructuralComparisonConfiguration extends string {
not (x.(Expr).hasValue() and y.(Expr).hasValue())
}
+ pragma [nomagic]
private predicate sameInternal(Element x, Element y) {
sameByValue(x, y)
or
@@ -210,6 +212,7 @@ module Internal {
value = x.getValue()
}
+ pragma [nomagic]
private predicate sameByStructure(Element x, Element y) {
// At least one of `x` and `y` must not have a value, they must have
// the same kind, and the same number of children
@@ -246,6 +249,7 @@ module Internal {
not (x.(Expr).hasValue() and y.(Expr).hasValue())
}
+ pragma [nomagic]
private predicate sameInternal(Element x, Element y) {
sameByValue(x, y)
or
diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll
index 2bacb951da5..3c6926d8ae9 100644
--- a/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll
+++ b/csharp/ql/src/semmle/code/csharp/controlflow/BasicBlocks.qll
@@ -3,6 +3,7 @@
*/
import csharp
+private import ControlFlow::SuccessorTypes
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
@@ -14,6 +15,11 @@ class BasicBlock extends TBasicBlockStart {
result.getFirstNode() = getLastNode().getASuccessor()
}
+ /** Gets an immediate successor of this basic block of a given flow type, if any. */
+ BasicBlock getASuccessorByType(ControlFlow::SuccessorType t) {
+ result.getFirstNode() = this.getLastNode().getASuccessorByType(t)
+ }
+
/** Gets an immediate predecessor of this basic block, if any. */
BasicBlock getAPredecessor() {
result.getASuccessor() = this
@@ -75,6 +81,7 @@ class BasicBlock extends TBasicBlockStart {
* The node on line 2 is an immediate `null` successor of the node
* `x` on line 1.
*/
+ deprecated
BasicBlock getANullSuccessor() {
result.getFirstNode() = getLastNode().getANullSuccessor()
}
@@ -94,6 +101,7 @@ class BasicBlock extends TBasicBlockStart {
* The node `x?.M()`, representing the call to `M`, is a non-`null` successor
* of the node `x`.
*/
+ deprecated
BasicBlock getANonNullSuccessor() {
result.getFirstNode() = getLastNode().getANonNullSuccessor()
}
@@ -430,7 +438,7 @@ class ConditionBlock extends BasicBlock {
* the callable entry point by going via the true edge (`testIsTrue = true`)
* or false edge (`testIsTrue = false`) out of this basic block.
*/
- predicate controls(BasicBlock controlled, boolean testIsTrue) {
+ predicate controls(BasicBlock controlled, ConditionalSuccessor s) {
/*
* For this block to control the block `controlled` with `testIsTrue` the following must be true:
* Execution must have passed through the test i.e. `this` must strictly dominate `controlled`.
@@ -465,7 +473,7 @@ class ConditionBlock extends BasicBlock {
* directly.
*/
exists(BasicBlock succ |
- isCandidateSuccessor(succ, testIsTrue) |
+ isCandidateSuccessor(succ, s) |
succ.dominates(controlled)
)
}
@@ -476,11 +484,9 @@ class ConditionBlock extends BasicBlock {
* the callable entry point by going via the `null` edge (`isNull = true`)
* or non-`null` edge (`isNull = false`) out of this basic block.
*/
+ deprecated
predicate controlsNullness(BasicBlock controlled, boolean isNull) {
- exists(BasicBlock succ |
- isCandidateSuccessorNullness(succ, isNull) |
- succ.dominates(controlled)
- )
+ this.controls(controlled, any(NullnessSuccessor s | s.getValue() = isNull))
}
/**
@@ -520,29 +526,11 @@ class ConditionBlock extends BasicBlock {
// only `x & y` controls `A` if we do not take sub conditions into account.
predicate controlsSubCond(BasicBlock controlled, boolean testIsTrue, Expr cond, boolean condIsTrue) {
impliesSub(getLastNode().getElement(), cond, testIsTrue, condIsTrue) and
- controls(controlled, testIsTrue)
+ controls(controlled, any(BooleanSuccessor s | s.getValue() = testIsTrue))
}
- private predicate isCandidateSuccessor(BasicBlock succ, boolean testIsTrue) {
- (
- testIsTrue = true and succ = this.getATrueSuccessor()
- or
- testIsTrue = false and succ = this.getAFalseSuccessor()
- )
- and
- forall(BasicBlock pred |
- pred = succ.getAPredecessor() and pred != this |
- succ.dominates(pred)
- )
- }
-
- private predicate isCandidateSuccessorNullness(BasicBlock succ, boolean isNull) {
- (
- isNull = true and succ = this.getANullSuccessor()
- or
- isNull = false and succ = this.getANonNullSuccessor()
- )
- and
+ private predicate isCandidateSuccessor(BasicBlock succ, ConditionalSuccessor s) {
+ succ = this.getASuccessorByType(s) and
forall(BasicBlock pred |
pred = succ.getAPredecessor() and pred != this |
succ.dominates(pred)
diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll
index 4a22bcc739e..7c74326ed1f 100644
--- a/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll
+++ b/csharp/ql/src/semmle/code/csharp/controlflow/Completion.qll
@@ -401,10 +401,21 @@ private predicate inNullnessContext(Expr e, boolean isNullnessCompletionForParen
)
}
+/**
+ * Holds if `cfe` is the element inside case statement `cs`, belonging to `switch`
+ * statement `ss`, that has the matching completion.
+ */
+predicate switchMatching(SwitchStmt ss, CaseStmt cs, ControlFlowElement cfe) {
+ ss.getACase() = cs and
+ (
+ cfe = cs.(ConstCase).getExpr()
+ or
+ cfe = cs.(TypeCase).getTypeAccess() // use type access to represent the type test
+ )
+}
+
private predicate mustHaveMatchingCompletion(SwitchStmt ss, ControlFlowElement cfe) {
- cfe = ss.getAConstCase().getExpr()
- or
- cfe = ss.getATypeCase().getTypeAccess() // use type access to represent the type test
+ switchMatching(ss, _, cfe)
}
/**
diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll
index f35c271adfa..20878834ff6 100644
--- a/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll
+++ b/csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll
@@ -255,6 +255,7 @@ module ControlFlow {
* The node on line 2 is an immediate `null` successor of the node
* `x` on line 1.
*/
+ deprecated
Node getANullSuccessor() {
result = getASuccessorByType(any(NullnessSuccessor t | t.isNull()))
}
@@ -274,6 +275,7 @@ module ControlFlow {
* The node `x?.M()`, representing the call to `M`, is a non-`null` successor
* of the node `x`.
*/
+ deprecated
Node getANonNullSuccessor() {
result = getASuccessorByType(any(NullnessSuccessor t | not t.isNull()))
}
@@ -400,7 +402,10 @@ module ControlFlow {
* a nullness successor (`NullnessSuccessor`), a matching successor (`MatchingSuccessor`),
* or an emptiness successor (`EmptinessSuccessor`).
*/
- abstract class ConditionalSuccessor extends SuccessorType { }
+ abstract class ConditionalSuccessor extends SuccessorType {
+ /** Gets the Boolean value of this successor. */
+ abstract boolean getValue();
+ }
/**
* A Boolean control flow successor.
@@ -429,8 +434,7 @@ module ControlFlow {
* ```
*/
class BooleanSuccessor extends ConditionalSuccessor, TBooleanSuccessor {
- /** Gets the value of this Boolean successor. */
- boolean getValue() { this = TBooleanSuccessor(result) }
+ override boolean getValue() { this = TBooleanSuccessor(result) }
override string toString() { result = getValue().toString() }
@@ -469,6 +473,8 @@ module ControlFlow {
/** Holds if this is a `null` successor. */
predicate isNull() { this = TNullnessSuccessor(true) }
+ override boolean getValue() { this = TNullnessSuccessor(result) }
+
override string toString() {
if this.isNull() then
result = "null"
@@ -520,6 +526,8 @@ module ControlFlow {
/** Holds if this is a match successor. */
predicate isMatch() { this = TMatchingSuccessor(true) }
+ override boolean getValue() { this = TMatchingSuccessor(result) }
+
override string toString() {
if this.isMatch() then
result = "match"
@@ -571,6 +579,8 @@ module ControlFlow {
/** Holds if this is an empty successor. */
predicate isEmpty() { this = TEmptinessSuccessor(true) }
+ override boolean getValue() { this = TEmptinessSuccessor(result) }
+
override string toString() {
if this.isEmpty() then
result = "empty"
diff --git a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll
index 8dcc683f5c3..d96a0abcb62 100644
--- a/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll
+++ b/csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll
@@ -10,14 +10,12 @@ private import semmle.code.csharp.frameworks.System
/** An expression that accesses/calls a declaration. */
class AccessOrCallExpr extends Expr {
- AccessOrCallExpr() {
- exists(getDeclarationTarget(this))
- }
+ private Declaration target;
+
+ AccessOrCallExpr() { target = getDeclarationTarget(this) }
/** Gets the target of this expression. */
- Declaration getTarget() {
- result = getDeclarationTarget(this)
- }
+ Declaration getTarget() { result = target }
/**
* Gets the (non-trivial) SSA definition corresponding to the longest
@@ -35,16 +33,12 @@ class AccessOrCallExpr extends Expr {
* x; // SSA qualifier: SSA definition for `x`
* ```
*/
- Ssa::Definition getSsaQualifier() {
- result = getSsaQualifier(this)
- }
+ Ssa::Definition getSsaQualifier() { result = getSsaQualifier(this) }
/**
* Holds if this expression has an SSA qualifier.
*/
- predicate hasSsaQualifier() {
- exists(getSsaQualifier())
- }
+ predicate hasSsaQualifier() { exists(this.getSsaQualifier()) }
}
private Declaration getDeclarationTarget(Expr e) {
@@ -103,8 +97,12 @@ private AssignableRead getATrackedRead(Ssa::Definition def) {
* definition).
*/
class GuardedExpr extends AccessOrCallExpr {
+ private Expr cond0;
+ private AccessOrCallExpr e0;
+ private boolean b0;
+
GuardedExpr() {
- Internal::isGuardedBy(this, _, _, _)
+ Internal::isGuardedBy(this, cond0, e0, b0)
}
/**
@@ -117,14 +115,16 @@ class GuardedExpr extends AccessOrCallExpr {
* variable).
*/
predicate isGuardedBy(Expr cond, Expr e, boolean b) {
- Internal::isGuardedBy(this, cond, e, b)
+ cond = cond0 and
+ e = e0 and
+ b = b0
}
}
/**
* A nullness guarded expression.
*
- * A nullness guarded expression is an access or a call that is reached only
+ * A nullness guarded expression is an access or a call that is reached only
* when a nullness condition containing a structurally equal expression
* evaluates to one of `null` or non-`null`.
*
@@ -136,8 +136,11 @@ class GuardedExpr extends AccessOrCallExpr {
* ```
*/
class NullnessGuardedExpr extends AccessOrCallExpr {
+ private Expr e0;
+ private boolean isNull0;
+
NullnessGuardedExpr() {
- Internal::isGuardedByNullness(this, _, _)
+ Internal::isGuardedByNullness(this, e0, isNull0)
}
/**
@@ -150,13 +153,64 @@ class NullnessGuardedExpr extends AccessOrCallExpr {
* variable).
*/
predicate isGuardedBy(Expr e, boolean isNull) {
- Internal::isGuardedByNullness(this, e, isNull)
+ e = e0 and
+ isNull = isNull0
+ }
+}
+
+/**
+ * A matching guarded expression.
+ *
+ * A matching guarded expression is an access or a call that is reached only
+ * when a pattern, matching against a structurally equal expression, matches
+ * or non-matches.
+ *
+ * For example, the access to `o` on line 8 is only evaluated when `case null`
+ * does not match.
+ *
+ * ```
+ * string M(object o)
+ * {
+ * switch (o)
+ * {
+ * case null:
+ * return "";
+ * default:
+ * return o.ToString();
+ * }
+ * }
+ * ```
+ */
+class MatchingGuardedExpr extends AccessOrCallExpr {
+ private AccessOrCallExpr e0;
+ private CaseStmt cs0;
+ private boolean isMatch0;
+
+ MatchingGuardedExpr() {
+ Internal::isGuardedByMatching(this, e0, cs0, isMatch0)
+ }
+
+ /**
+ * Holds if this expression is guarded by case statement `cs` matching
+ * (`isMatch = true`) or non-matching (`isMatch = false`). The expression
+ * `e` is structurally equal to this expression being matched against in
+ * `cs`.
+ *
+ * In case this expression or `e` accesses an SSA variable in its
+ * left-most qualifier, then so must the other (accessing the same SSA
+ * variable).
+ */
+ predicate isGuardedBy(AccessOrCallExpr e, CaseStmt cs, boolean isMatch) {
+ e = e0 and
+ cs0 = cs and
+ isMatch0 = isMatch
}
}
/** An expression guarded by a `null` check. */
class NullGuardedExpr extends AccessOrCallExpr {
NullGuardedExpr() {
+ this.getType() instanceof RefType and
exists(Expr cond, Expr sub, boolean b |
this.(GuardedExpr).isGuardedBy(cond, sub, b) |
// Comparison with `null`, for example `x != null`
@@ -182,19 +236,47 @@ class NullGuardedExpr extends AccessOrCallExpr {
)
or
// Call to `string.IsNullOrEmpty()`
- exists(MethodCall mc |
- mc = cond and
+ cond = any(MethodCall mc |
mc.getTarget() = any(SystemStringClass c).getIsNullOrEmptyMethod() and
mc.getArgument(0) = sub and
b = false
)
+ or
+ cond = any(IsExpr ie |
+ ie.getExpr() = sub and
+ if ie.(IsConstantExpr).getConstant() instanceof NullLiteral then
+ // E.g. `x is null`
+ b = false
+ else
+ // E.g. `x is string`
+ b = true
+ )
)
or
this.(NullnessGuardedExpr).isGuardedBy(_, false)
+ or
+ exists(CaseStmt cs, boolean isMatch |
+ this.(MatchingGuardedExpr).isGuardedBy(_, cs, isMatch) |
+ // E.g. `case string`
+ cs instanceof TypeCase and
+ isMatch = true
+ or
+ cs = any(ConstCase cc |
+ if cc.getExpr() instanceof NullLiteral then
+ // `case null`
+ isMatch = false
+ else
+ // E.g. `case ""`
+ isMatch = true
+ )
+ )
}
}
private module Internal {
+ private import semmle.code.csharp.controlflow.Completion
+ private import ControlFlow::SuccessorTypes
+
private cached module Cached {
cached predicate isGuardedBy(AccessOrCallExpr guarded, Expr cond, AccessOrCallExpr e, boolean b) {
exists(BasicBlock bb |
@@ -217,6 +299,17 @@ private module Internal {
guarded.getSsaQualifier() = e.getSsaQualifier()
)
}
+
+ cached predicate isGuardedByMatching(AccessOrCallExpr guarded, AccessOrCallExpr e, CaseStmt cs, boolean isMatch) {
+ exists(BasicBlock bb |
+ controlsMatching(e, cs, bb, isMatch) and
+ bb = guarded.getAControlFlowNode().getBasicBlock() and
+ exists(ConditionOnExprComparisonConfig c | c.same(e, guarded)) |
+ not guarded.hasSsaQualifier() and not e.hasSsaQualifier()
+ or
+ guarded.getSsaQualifier() = e.getSsaQualifier()
+ )
+ }
}
import Cached
@@ -263,12 +356,27 @@ private module Internal {
* (`isNull = true`) or when `e` evaluates to non-`null` (`isNull = false`).
*/
private predicate controlsNullness(Expr e, BasicBlock bb, boolean isNull) {
- exists(ConditionBlock cb |
- cb.controlsNullness(bb, isNull) |
+ exists(ConditionBlock cb, NullnessSuccessor s |
+ cb.controls(bb, s) |
+ isNull = s.getValue() and
e = cb.getLastNode().getElement()
)
}
+ /**
+ * Holds if basic block `bb` only is reached when `e` matches case `cs`
+ * (`isMatch = true`) or when `e` does not match `cs` (`isMatch = false`).
+ */
+ private predicate controlsMatching(Expr e, CaseStmt cs, BasicBlock bb, boolean isMatch) {
+ exists(ConditionBlock cb, SwitchStmt ss, ControlFlowElement cfe, MatchingSuccessor s |
+ cb.controls(bb, s) |
+ cfe = cb.getLastNode().getElement() and
+ switchMatching(ss, cs, cfe) and
+ e = ss.getCondition() and
+ isMatch = s.getValue()
+ )
+ }
+
/**
* A helper class for calculating structurally equal access/call expressions.
*/
@@ -280,7 +388,10 @@ private module Internal {
override predicate candidate(Element x, Element y) {
exists(BasicBlock bb, Declaration d |
candidateAux(x, d, bb) and
- y = any(AccessOrCallExpr e | e.getAControlFlowNode().getBasicBlock() = bb and e.getTarget() = d)
+ y = any(AccessOrCallExpr e |
+ e.getAControlFlowNode().getBasicBlock() = bb and
+ e.getTarget() = d
+ )
)
}
@@ -289,10 +400,16 @@ private module Internal {
* is a sub expression of a condition that controls whether basic block
* `bb` is reached.
*/
- pragma [noinline] // predicate folding for proper join-order
+ pragma [noinline]
private predicate candidateAux(AccessOrCallExpr e, Declaration target, BasicBlock bb) {
- (controls(_, e, bb, _) or controlsNullness(e, bb, _)) and
- target = e.getTarget()
+ target = e.getTarget() and
+ (
+ controls(_, e, bb, _)
+ or
+ controlsNullness(e, bb, _)
+ or
+ controlsMatching(e, _, bb, _)
+ )
}
}
}
diff --git a/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll b/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll
index 5b446c2dc70..e5f0d5f396f 100644
--- a/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll
+++ b/csharp/ql/src/semmle/code/csharp/security/dataflow/ConditionalBypass.qll
@@ -73,14 +73,20 @@ module UserControlledBypassOfSensitiveMethod {
* on the given expression.
*/
predicate conditionControlsMethod(MethodCall call, Expr e) {
- exists (ConditionBlock cb, SensitiveExecutionMethod def, boolean cond |
- cb.controls(call.getAControlFlowNode().getBasicBlock(), cond) and
+ exists(ConditionBlock cb, SensitiveExecutionMethod def, boolean cond |
+ exists(ControlFlow::SuccessorTypes::BooleanSuccessor s |
+ cond = s.getValue() |
+ cb.controls(call.getAControlFlowNode().getBasicBlock(), s)
+ ) and
def = call.getTarget() and
/*
* Exclude this condition if the other branch also contains a call to the same security
* sensitive method.
*/
- not cb.controls(def.getACall().getAControlFlowNode().getBasicBlock(), cond.booleanNot()) and
+ not exists(ControlFlow::SuccessorTypes::BooleanSuccessor s |
+ cond = s.getValue().booleanNot() |
+ cb.controls(def.getACall().getAControlFlowNode().getBasicBlock(), s)
+ ) and
e = cb.getLastNode().getElement()
)
}
diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected
index 28d5589c75a..85d1641124d 100644
--- a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected
+++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.expected
@@ -1,5 +1,12 @@
+| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:7:26:7:28 | String arg | false |
+| BreakInTry.cs:7:13:11:13 | foreach (... ... in ...) ... | BreakInTry.cs:10:21:10:26 | break; | false |
| BreakInTry.cs:9:21:9:31 | ... == ... | BreakInTry.cs:10:21:10:26 | break; | true |
| BreakInTry.cs:15:17:15:28 | ... == ... | BreakInTry.cs:16:17:16:17 | ; | true |
+| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:22:22:22:24 | String arg | false |
+| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:27:21:27:26 | break; | false |
+| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:30:13:33:13 | {...} | false |
+| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:32:21:32:21 | ; | false |
+| BreakInTry.cs:22:9:34:9 | foreach (... ... in ...) ... | BreakInTry.cs:32:21:32:21 | [finally: break] ; | false |
| BreakInTry.cs:26:21:26:31 | ... == ... | BreakInTry.cs:27:21:27:26 | break; | true |
| BreakInTry.cs:26:21:26:31 | ... == ... | BreakInTry.cs:30:13:33:13 | {...} | false |
| BreakInTry.cs:26:21:26:31 | ... == ... | BreakInTry.cs:32:21:32:21 | ; | false |
@@ -15,6 +22,10 @@
| BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:50:21:50:26 | [finally: return] break; | true |
| BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:50:21:50:26 | break; | false |
| BreakInTry.cs:42:17:42:28 | ... == ... | BreakInTry.cs:53:7:53:7 | ; | false |
+| BreakInTry.cs:47:13:51:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:47:26:47:28 | [finally: return] String arg | false |
+| BreakInTry.cs:47:13:51:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:50:21:50:26 | [finally: return] break; | false |
+| BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:47:26:47:28 | String arg | false |
+| BreakInTry.cs:47:13:51:13 | foreach (... ... in ...) ... | BreakInTry.cs:50:21:50:26 | break; | false |
| BreakInTry.cs:49:21:49:31 | ... == ... | BreakInTry.cs:50:21:50:26 | break; | true |
| BreakInTry.cs:49:21:49:31 | [finally: return] ... == ... | BreakInTry.cs:50:21:50:26 | [finally: return] break; | true |
| BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:61:17:61:23 | return ...; | true |
@@ -25,6 +36,10 @@
| BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:65:26:65:28 | [finally: return] String arg | true |
| BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:68:21:68:26 | [finally: return] break; | true |
| BreakInTry.cs:60:17:60:28 | ... == ... | BreakInTry.cs:68:21:68:26 | break; | false |
+| BreakInTry.cs:65:13:69:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:65:26:65:28 | [finally: return] String arg | false |
+| BreakInTry.cs:65:13:69:13 | [finally: return] foreach (... ... in ...) ... | BreakInTry.cs:68:21:68:26 | [finally: return] break; | false |
+| BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:65:26:65:28 | String arg | false |
+| BreakInTry.cs:65:13:69:13 | foreach (... ... in ...) ... | BreakInTry.cs:68:21:68:26 | break; | false |
| BreakInTry.cs:67:21:67:31 | ... == ... | BreakInTry.cs:68:21:68:26 | break; | true |
| BreakInTry.cs:67:21:67:31 | [finally: return] ... == ... | BreakInTry.cs:68:21:68:26 | [finally: return] break; | true |
| CatchInFinally.cs:9:17:9:28 | ... == ... | CatchInFinally.cs:10:17:10:50 | throw ...; | true |
@@ -58,8 +73,16 @@
| CatchInFinally.cs:16:21:16:36 | [finally: exception(ArgumentNullException)] ... == ... | CatchInFinally.cs:17:41:17:43 | [finally: exception(ArgumentNullException)] "1" | true |
| CatchInFinally.cs:16:21:16:36 | [finally: exception(Exception)] ... == ... | CatchInFinally.cs:17:21:17:45 | [finally: exception(Exception)] throw ...; | true |
| CatchInFinally.cs:16:21:16:36 | [finally: exception(Exception)] ... == ... | CatchInFinally.cs:17:41:17:43 | [finally: exception(Exception)] "1" | true |
+| ConditionalAccess.cs:3:26:3:26 | access to parameter i | ConditionalAccess.cs:3:28:3:38 | call to method ToString | false |
+| ConditionalAccess.cs:3:26:3:26 | access to parameter i | ConditionalAccess.cs:3:40:3:49 | call to method ToLower | false |
+| ConditionalAccess.cs:3:28:3:38 | call to method ToString | ConditionalAccess.cs:3:40:3:49 | call to method ToLower | false |
+| ConditionalAccess.cs:5:26:5:26 | access to parameter s | ConditionalAccess.cs:5:28:5:34 | access to property Length | false |
+| ConditionalAccess.cs:7:39:7:40 | access to parameter s1 | ConditionalAccess.cs:7:45:7:46 | access to parameter s2 | true |
+| ConditionalAccess.cs:9:25:9:25 | access to parameter s | ConditionalAccess.cs:9:27:9:33 | access to property Length | false |
+| ConditionalAccess.cs:13:13:13:13 | access to parameter s | ConditionalAccess.cs:13:15:13:21 | access to property Length | false |
| ConditionalAccess.cs:13:13:13:25 | ... > ... | ConditionalAccess.cs:14:20:14:20 | 0 | true |
| ConditionalAccess.cs:13:13:13:25 | ... > ... | ConditionalAccess.cs:16:20:16:20 | 1 | false |
+| ConditionalAccess.cs:19:40:19:41 | access to parameter s1 | ConditionalAccess.cs:19:58:19:59 | access to parameter s2 | false |
| Conditions.cs:5:13:5:15 | access to parameter inc | Conditions.cs:6:13:6:16 | [inc (line 3): true] ...; | true |
| Conditions.cs:5:13:5:15 | access to parameter inc | Conditions.cs:7:9:8:16 | [inc (line 3): false] if (...) ... | false |
| Conditions.cs:14:13:14:13 | access to parameter b | Conditions.cs:15:13:15:16 | [b (line 11): true] ...; | true |
@@ -106,9 +129,23 @@
| Conditions.cs:62:17:62:17 | access to parameter b | Conditions.cs:63:17:63:20 | [b (line 57): true] ...; | true |
| Conditions.cs:62:17:62:17 | access to parameter b | Conditions.cs:65:9:66:16 | [b (line 57): false] if (...) ... | false |
| Conditions.cs:62:17:62:17 | access to parameter b | Conditions.cs:65:9:66:16 | [b (line 57): true] if (...) ... | true |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:75:9:80:9 | {...} | false |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:77:17:77:20 | ...; | false |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:78:13:79:26 | if (...) ... | false |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:79:17:79:26 | ...; | false |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:81:9:82:16 | if (...) ... | true |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:82:13:82:16 | ...; | true |
+| Conditions.cs:74:9:80:9 | foreach (... ... in ...) ... | Conditions.cs:83:16:83:16 | access to local variable x | true |
| Conditions.cs:76:17:76:17 | access to local variable b | Conditions.cs:77:17:77:20 | ...; | true |
| Conditions.cs:78:17:78:21 | ... > ... | Conditions.cs:79:17:79:26 | ...; | true |
| Conditions.cs:81:12:81:12 | access to local variable b | Conditions.cs:82:13:82:16 | ...; | true |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:91:9:98:9 | {...} | false |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:93:17:93:20 | ...; | false |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:94:13:95:26 | if (...) ... | false |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:95:17:95:26 | ...; | false |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:96:13:97:20 | if (...) ... | false |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:97:17:97:20 | ...; | false |
+| Conditions.cs:90:9:98:9 | foreach (... ... in ...) ... | Conditions.cs:99:16:99:16 | access to local variable x | true |
| Conditions.cs:92:17:92:17 | access to local variable b | Conditions.cs:93:17:93:20 | ...; | true |
| Conditions.cs:94:17:94:21 | ... > ... | Conditions.cs:95:17:95:26 | ...; | true |
| Conditions.cs:96:17:96:17 | access to local variable b | Conditions.cs:97:17:97:20 | ...; | true |
@@ -118,6 +155,7 @@
| Conditions.cs:105:13:105:13 | access to parameter b | Conditions.cs:108:13:109:24 | [b (line 102): true] if (...) ... | true |
| Conditions.cs:107:13:107:24 | [b (line 102): false] ... > ... | Conditions.cs:108:13:109:24 | [b (line 102): false] if (...) ... | true |
| Conditions.cs:107:13:107:24 | [b (line 102): true] ... > ... | Conditions.cs:108:13:109:24 | [b (line 102): true] if (...) ... | true |
+| ExitMethods.cs:42:9:45:9 | [exception: Exception] catch (...) {...} | ExitMethods.cs:46:9:49:9 | [exception: Exception] catch (...) {...} | false |
| ExitMethods.cs:54:13:54:13 | access to parameter b | ExitMethods.cs:55:19:55:33 | object creation of type Exception | true |
| ExitMethods.cs:60:13:60:13 | access to parameter b | ExitMethods.cs:61:19:61:33 | object creation of type Exception | true |
| ExitMethods.cs:60:13:60:13 | access to parameter b | ExitMethods.cs:63:41:63:43 | "b" | false |
@@ -125,9 +163,23 @@
| ExitMethods.cs:78:16:78:25 | ... != ... | ExitMethods.cs:78:69:78:75 | "input" | false |
| ExitMethods.cs:83:16:83:30 | call to method Contains | ExitMethods.cs:83:34:83:34 | 0 | true |
| ExitMethods.cs:83:16:83:30 | call to method Contains | ExitMethods.cs:83:38:83:38 | 1 | false |
+| Foreach.cs:8:9:9:13 | foreach (... ... in ...) ... | Foreach.cs:6:10:6:11 | exit M1 | true |
+| Foreach.cs:8:9:9:13 | foreach (... ... in ...) ... | Foreach.cs:8:22:8:24 | String arg | false |
+| Foreach.cs:14:9:15:13 | foreach (... ... in ...) ... | Foreach.cs:12:10:12:11 | exit M2 | true |
+| Foreach.cs:14:9:15:13 | foreach (... ... in ...) ... | Foreach.cs:15:13:15:13 | ; | false |
+| Foreach.cs:20:9:21:11 | foreach (... ... in ...) ... | Foreach.cs:18:10:18:11 | exit M3 | true |
+| Foreach.cs:20:9:21:11 | foreach (... ... in ...) ... | Foreach.cs:20:22:20:22 | String x | false |
+| Foreach.cs:20:27:20:27 | access to parameter e | Foreach.cs:20:29:20:38 | call to method ToArray | false |
+| NullCoalescing.cs:3:23:3:23 | access to parameter i | NullCoalescing.cs:3:28:3:28 | 0 | true |
+| NullCoalescing.cs:5:25:5:25 | access to parameter b | NullCoalescing.cs:5:30:5:34 | false | true |
| NullCoalescing.cs:5:25:5:25 | access to parameter b | NullCoalescing.cs:5:39:5:39 | 0 | true |
+| NullCoalescing.cs:7:40:7:41 | access to parameter s1 | NullCoalescing.cs:7:46:7:53 | ... ?? ... | true |
+| NullCoalescing.cs:7:40:7:41 | access to parameter s1 | NullCoalescing.cs:7:52:7:53 | "" | true |
+| NullCoalescing.cs:7:46:7:47 | access to parameter s2 | NullCoalescing.cs:7:52:7:53 | "" | true |
| NullCoalescing.cs:9:37:9:37 | access to parameter b | NullCoalescing.cs:9:41:9:41 | access to parameter s | true |
| NullCoalescing.cs:9:37:9:37 | access to parameter b | NullCoalescing.cs:9:45:9:45 | access to parameter s | false |
+| NullCoalescing.cs:11:44:11:45 | access to parameter b1 | NullCoalescing.cs:11:51:11:58 | ... && ... | true |
+| NullCoalescing.cs:11:44:11:45 | access to parameter b1 | NullCoalescing.cs:11:57:11:58 | access to parameter b3 | true |
| NullCoalescing.cs:11:51:11:52 | access to parameter b2 | NullCoalescing.cs:11:57:11:58 | access to parameter b3 | true |
| Patterns.cs:8:13:8:23 | ... is ... | Patterns.cs:9:9:11:9 | {...} | true |
| Patterns.cs:8:13:8:23 | ... is ... | Patterns.cs:12:14:18:9 | if (...) ... | false |
@@ -138,16 +190,123 @@
| Patterns.cs:12:18:12:31 | ... is ... | Patterns.cs:16:14:18:9 | if (...) ... | false |
| Patterns.cs:12:18:12:31 | ... is ... | Patterns.cs:17:9:18:9 | {...} | false |
| Patterns.cs:16:18:16:28 | ... is ... | Patterns.cs:17:9:18:9 | {...} | true |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:23:17:23:22 | break; | true |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:24:13:24:36 | case Int32 i2: | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:24:18:24:23 | Int32 i2 | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:25:17:25:52 | ...; | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:27:13:27:24 | case Int32 i3: | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:27:18:27:23 | Int32 i3 | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:30:13:30:27 | case String s2: | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:30:18:30:26 | String s2 | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:33:13:33:24 | case Object v2: | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:33:18:33:23 | Object v2 | false |
+| Patterns.cs:22:18:22:22 | "xyz" | Patterns.cs:35:13:35:20 | default: | false |
+| Patterns.cs:24:18:24:20 | access to type Int32 | Patterns.cs:24:18:24:23 | Int32 i2 | true |
+| Patterns.cs:24:18:24:20 | access to type Int32 | Patterns.cs:25:17:25:52 | ...; | true |
| Patterns.cs:24:30:24:35 | ... > ... | Patterns.cs:25:17:25:52 | ...; | true |
+| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:27:18:27:23 | Int32 i3 | true |
+| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:30:13:30:27 | case String s2: | false |
+| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:30:18:30:26 | String s2 | false |
+| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:33:13:33:24 | case Object v2: | false |
+| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:33:18:33:23 | Object v2 | false |
+| Patterns.cs:27:18:27:20 | access to type Int32 | Patterns.cs:35:13:35:20 | default: | false |
+| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:30:18:30:26 | String s2 | true |
+| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:33:13:33:24 | case Object v2: | false |
+| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:33:18:33:23 | Object v2 | false |
+| Patterns.cs:30:18:30:23 | access to type String | Patterns.cs:35:13:35:20 | default: | false |
+| Patterns.cs:33:18:33:20 | access to type Object | Patterns.cs:33:18:33:23 | Object v2 | true |
+| Patterns.cs:33:18:33:20 | access to type Object | Patterns.cs:35:13:35:20 | default: | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:15:17:15:23 | return ...; | true |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:16:13:16:19 | case ...: | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:17:23:17:37 | object creation of type Exception | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:18:13:18:22 | case ...: | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:19:17:19:29 | goto default; | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:20:13:20:23 | case Int32 i: | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:20:18:20:22 | Int32 i | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:22:21:22:27 | return ...; | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:23:27:23:27 | 0 | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:24:13:24:56 | case String s: | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:24:18:24:25 | String s | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:24:48:24:48 | access to local variable s | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:25:17:25:37 | ...; | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:27:13:27:39 | case Double d: | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:27:18:27:25 | Double d | false |
+| Switch.cs:14:18:14:20 | "a" | Switch.cs:30:13:30:20 | default: | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:18:13:18:22 | case ...: | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:19:17:19:29 | goto default; | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:20:13:20:23 | case Int32 i: | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:20:18:20:22 | Int32 i | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:22:21:22:27 | return ...; | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:23:27:23:27 | 0 | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:24:13:24:56 | case String s: | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:24:18:24:25 | String s | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:24:48:24:48 | access to local variable s | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:25:17:25:37 | ...; | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:27:13:27:39 | case Double d: | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:27:18:27:25 | Double d | false |
+| Switch.cs:16:18:16:18 | 0 | Switch.cs:30:13:30:20 | default: | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:19:17:19:29 | goto default; | true |
+| Switch.cs:18:18:18:21 | null | Switch.cs:20:13:20:23 | case Int32 i: | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:20:18:20:22 | Int32 i | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:22:21:22:27 | return ...; | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:23:27:23:27 | 0 | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:24:13:24:56 | case String s: | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:24:18:24:25 | String s | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:24:48:24:48 | access to local variable s | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:25:17:25:37 | ...; | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:27:13:27:39 | case Double d: | false |
+| Switch.cs:18:18:18:21 | null | Switch.cs:27:18:27:25 | Double d | false |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:20:18:20:22 | Int32 i | true |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:22:21:22:27 | return ...; | true |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:23:27:23:27 | 0 | true |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:24:13:24:56 | case String s: | false |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:24:18:24:25 | String s | false |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:24:48:24:48 | access to local variable s | false |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:25:17:25:37 | ...; | false |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:27:13:27:39 | case Double d: | false |
+| Switch.cs:20:18:20:20 | access to type Int32 | Switch.cs:27:18:27:25 | Double d | false |
| Switch.cs:21:21:21:29 | ... == ... | Switch.cs:22:21:22:27 | return ...; | true |
| Switch.cs:21:21:21:29 | ... == ... | Switch.cs:23:27:23:27 | 0 | false |
+| Switch.cs:24:18:24:23 | access to type String | Switch.cs:24:18:24:25 | String s | true |
+| Switch.cs:24:18:24:23 | access to type String | Switch.cs:24:48:24:48 | access to local variable s | true |
+| Switch.cs:24:18:24:23 | access to type String | Switch.cs:25:17:25:37 | ...; | true |
| Switch.cs:24:32:24:43 | ... > ... | Switch.cs:24:48:24:48 | access to local variable s | true |
| Switch.cs:24:32:24:43 | ... > ... | Switch.cs:25:17:25:37 | ...; | true |
| Switch.cs:24:48:24:55 | ... != ... | Switch.cs:25:17:25:37 | ...; | true |
+| Switch.cs:27:18:27:23 | access to type Double | Switch.cs:27:18:27:25 | Double d | true |
+| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:49:17:49:22 | break; | true |
+| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:50:13:50:39 | case Boolean: | false |
+| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:50:30:50:30 | access to parameter o | false |
+| Switch.cs:48:18:48:20 | access to type Int32 | Switch.cs:51:17:51:22 | break; | false |
+| Switch.cs:50:18:50:21 | access to type Boolean | Switch.cs:50:30:50:30 | access to parameter o | true |
+| Switch.cs:50:18:50:21 | access to type Boolean | Switch.cs:51:17:51:22 | break; | true |
| Switch.cs:50:30:50:38 | ... != ... | Switch.cs:51:17:51:22 | break; | true |
+| Switch.cs:72:18:72:19 | "" | Switch.cs:73:15:73:20 | break; | true |
+| Switch.cs:81:18:81:18 | 1 | Switch.cs:82:22:82:25 | true | true |
+| Switch.cs:81:18:81:18 | 1 | Switch.cs:83:13:83:20 | case ...: | false |
+| Switch.cs:81:18:81:18 | 1 | Switch.cs:84:15:85:22 | if (...) ... | false |
+| Switch.cs:81:18:81:18 | 1 | Switch.cs:85:17:85:22 | break; | false |
+| Switch.cs:81:18:81:18 | 1 | Switch.cs:86:22:86:25 | true | false |
+| Switch.cs:81:18:81:18 | 1 | Switch.cs:88:16:88:20 | false | false |
+| Switch.cs:83:18:83:18 | 2 | Switch.cs:84:15:85:22 | if (...) ... | true |
+| Switch.cs:83:18:83:18 | 2 | Switch.cs:85:17:85:22 | break; | true |
+| Switch.cs:83:18:83:18 | 2 | Switch.cs:86:22:86:25 | true | true |
| Switch.cs:84:19:84:23 | ... > ... | Switch.cs:85:17:85:22 | break; | true |
| Switch.cs:84:19:84:23 | ... > ... | Switch.cs:86:22:86:25 | true | false |
+| Switch.cs:95:18:95:20 | access to type Int32 | Switch.cs:96:22:96:25 | true | true |
+| Switch.cs:95:18:95:20 | access to type Int32 | Switch.cs:98:16:98:20 | false | false |
+| Switch.cs:103:17:103:17 | access to parameter s | Switch.cs:103:19:103:25 | access to property Length | false |
+| Switch.cs:105:18:105:18 | 0 | Switch.cs:105:29:105:29 | 0 | true |
+| Switch.cs:105:18:105:18 | 0 | Switch.cs:106:13:106:20 | case ...: | false |
+| Switch.cs:105:18:105:18 | 0 | Switch.cs:106:29:106:29 | 1 | false |
+| Switch.cs:105:18:105:18 | 0 | Switch.cs:108:17:108:17 | 1 | false |
+| Switch.cs:106:18:106:18 | 1 | Switch.cs:106:29:106:29 | 1 | true |
+| Switch.cs:106:18:106:18 | 1 | Switch.cs:108:17:108:17 | 1 | false |
+| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:25:117:25 | access to parameter s | true |
+| Switch.cs:117:18:117:18 | 3 | Switch.cs:117:43:117:43 | 1 | true |
| Switch.cs:117:25:117:32 | ... == ... | Switch.cs:117:43:117:43 | 1 | true |
+| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:25:118:25 | access to parameter s | true |
+| Switch.cs:118:18:118:18 | 2 | Switch.cs:118:42:118:42 | 2 | true |
| Switch.cs:118:25:118:31 | ... == ... | Switch.cs:118:42:118:42 | 2 | true |
| TypeAccesses.cs:7:13:7:22 | ... is ... | TypeAccesses.cs:7:25:7:25 | ; | true |
| VarDecls.cs:25:20:25:20 | access to parameter b | VarDecls.cs:25:24:25:24 | access to local variable x | true |
@@ -199,6 +358,35 @@
| cflow.cs:28:22:28:31 | ... == ... | cflow.cs:33:17:33:37 | ...; | false |
| cflow.cs:30:22:30:31 | ... == ... | cflow.cs:31:17:31:42 | ...; | true |
| cflow.cs:30:22:30:31 | ... == ... | cflow.cs:33:17:33:37 | ...; | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:37:17:37:22 | exit Switch | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:44:13:44:19 | case ...: | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:47:13:47:19 | case ...: | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:48:17:48:39 | ...; | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:51:9:59:9 | switch (...) {...} | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:54:17:54:48 | ...; | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:56:13:56:20 | default: | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:60:9:66:9 | switch (...) {...} | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:63:17:64:55 | if (...) ... | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:65:17:65:22 | break; | false |
+| cflow.cs:41:18:41:18 | 1 | cflow.cs:67:16:67:16 | access to parameter a | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:37:17:37:22 | exit Switch | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:47:13:47:19 | case ...: | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:48:17:48:39 | ...; | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:51:9:59:9 | switch (...) {...} | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:54:17:54:48 | ...; | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:56:13:56:20 | default: | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:60:9:66:9 | switch (...) {...} | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:63:17:64:55 | if (...) ... | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:65:17:65:22 | break; | false |
+| cflow.cs:44:18:44:18 | 2 | cflow.cs:67:16:67:16 | access to parameter a | false |
+| cflow.cs:47:18:47:18 | 3 | cflow.cs:48:17:48:39 | ...; | true |
+| cflow.cs:53:18:53:19 | 42 | cflow.cs:54:17:54:48 | ...; | true |
+| cflow.cs:53:18:53:19 | 42 | cflow.cs:56:13:56:20 | default: | false |
+| cflow.cs:62:18:62:18 | 0 | cflow.cs:63:17:64:55 | if (...) ... | true |
+| cflow.cs:62:18:62:18 | 0 | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | true |
+| cflow.cs:62:18:62:18 | 0 | cflow.cs:65:17:65:22 | break; | true |
| cflow.cs:63:23:63:33 | ... == ... | cflow.cs:64:27:64:54 | object creation of type NullReferenceException | false |
| cflow.cs:63:23:63:33 | ... == ... | cflow.cs:65:17:65:22 | break; | true |
| cflow.cs:72:13:72:21 | ... == ... | cflow.cs:73:13:73:19 | return ...; | true |
@@ -225,6 +413,15 @@
| cflow.cs:108:13:108:21 | ... != ... | cflow.cs:116:9:116:29 | ...; | false |
| cflow.cs:127:32:127:44 | ... == ... | cflow.cs:127:48:127:49 | "" | true |
| cflow.cs:127:32:127:44 | ... == ... | cflow.cs:127:53:127:57 | this access | false |
+| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:162:38:162:39 | [exception: Exception] IOException ex | true |
+| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:166:9:176:9 | [exception: Exception] catch (...) {...} | false |
+| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:166:41:166:42 | [exception: Exception] ArgumentException ex | false |
+| cflow.cs:162:9:165:9 | [exception: Exception] catch (...) {...} | cflow.cs:177:9:179:9 | [exception: Exception] catch (...) {...} | false |
+| cflow.cs:166:9:176:9 | [exception: Exception] catch (...) {...} | cflow.cs:166:41:166:42 | [exception: Exception] ArgumentException ex | true |
+| cflow.cs:166:9:176:9 | [exception: Exception] catch (...) {...} | cflow.cs:177:9:179:9 | [exception: Exception] catch (...) {...} | false |
+| cflow.cs:194:9:197:9 | [exception: Exception] catch (...) {...} | cflow.cs:194:38:194:39 | [exception: Exception] IOException ex | true |
+| cflow.cs:194:9:197:9 | [exception: Exception] catch (...) {...} | cflow.cs:198:9:200:9 | [exception: Exception] catch (...) {...} | false |
+| cflow.cs:194:9:197:9 | [exception: Exception] catch (...) {...} | cflow.cs:202:9:204:9 | [finally: exception(Exception)] {...} | false |
| cflow.cs:207:16:207:20 | ... > ... | cflow.cs:208:9:230:9 | {...} | true |
| cflow.cs:207:16:207:20 | ... > ... | cflow.cs:212:21:212:27 | return ...; | true |
| cflow.cs:207:16:207:20 | ... > ... | cflow.cs:213:17:214:29 | if (...) ... | true |
@@ -383,6 +580,10 @@
| cflow.cs:315:17:315:32 | ... > ... | cflow.cs:319:13:322:13 | if (...) ... | false |
| cflow.cs:315:17:315:32 | ... > ... | cflow.cs:320:13:322:13 | {...} | false |
| cflow.cs:319:17:319:32 | ... < ... | cflow.cs:320:13:322:13 | {...} | true |
+| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:328:22:328:22 | String x | false |
+| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:332:13:334:13 | {...} | false |
+| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:335:13:338:13 | if (...) ... | false |
+| cflow.cs:328:9:339:9 | foreach (... ... in ...) ... | cflow.cs:336:13:338:13 | {...} | false |
| cflow.cs:331:17:331:32 | ... > ... | cflow.cs:332:13:334:13 | {...} | true |
| cflow.cs:331:17:331:32 | ... > ... | cflow.cs:335:13:338:13 | if (...) ... | false |
| cflow.cs:331:17:331:32 | ... > ... | cflow.cs:336:13:338:13 | {...} | false |
@@ -397,6 +598,15 @@
| cflow.cs:346:13:346:28 | ... > ... | cflow.cs:355:13:355:19 | case ...: | false |
| cflow.cs:346:13:346:28 | ... > ... | cflow.cs:356:17:356:27 | goto ...; | false |
| cflow.cs:346:13:346:28 | ... > ... | cflow.cs:357:13:357:20 | default: | false |
+| cflow.cs:350:18:350:18 | 0 | cflow.cs:351:17:351:29 | goto default; | true |
+| cflow.cs:350:18:350:18 | 0 | cflow.cs:352:13:352:19 | case ...: | false |
+| cflow.cs:350:18:350:18 | 0 | cflow.cs:353:17:353:37 | ...; | false |
+| cflow.cs:350:18:350:18 | 0 | cflow.cs:355:13:355:19 | case ...: | false |
+| cflow.cs:350:18:350:18 | 0 | cflow.cs:356:17:356:27 | goto ...; | false |
+| cflow.cs:352:18:352:18 | 1 | cflow.cs:353:17:353:37 | ...; | true |
+| cflow.cs:352:18:352:18 | 1 | cflow.cs:355:13:355:19 | case ...: | false |
+| cflow.cs:352:18:352:18 | 1 | cflow.cs:356:17:356:27 | goto ...; | false |
+| cflow.cs:355:18:355:18 | 2 | cflow.cs:356:17:356:27 | goto ...; | true |
| cflow.cs:366:25:366:30 | ... < ... | cflow.cs:367:9:369:9 | {...} | true |
| cflow.cs:366:25:366:30 | ... < ... | cflow.cs:370:9:378:9 | try {...} ... | false |
| cflow.cs:419:46:419:50 | ... > ... | cflow.cs:419:56:419:56 | access to parameter s | false |
diff --git a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql
index 7f0035c2ea0..c2dc8a9a103 100644
--- a/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql
+++ b/csharp/ql/test/library-tests/controlflow/graph/ConditionBlock.ql
@@ -1,5 +1,6 @@
import csharp
+import ControlFlow
-from ControlFlow::BasicBlocks::ConditionBlock cb, ControlFlow::BasicBlock controlled, boolean testIsTrue
-where cb.controls(controlled, testIsTrue)
+from BasicBlocks::ConditionBlock cb, BasicBlock controlled, boolean testIsTrue
+where cb.controls(controlled,any(SuccessorTypes::ConditionalSuccessor s | testIsTrue = s.getValue()))
select cb.getLastNode(), controlled.getFirstNode(), testIsTrue
diff --git a/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected b/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected
index d476e65b3f6..e20fd4275d0 100644
--- a/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected
+++ b/csharp/ql/test/library-tests/controlflow/guards/GuardedExpr.expected
@@ -34,3 +34,8 @@
| Guards.cs:106:9:106:9 | access to parameter g | Guards.cs:104:13:104:45 | ... == ... | Guards.cs:104:13:104:13 | access to parameter g | false |
| Guards.cs:107:27:107:27 | access to parameter g | Guards.cs:104:13:104:45 | ... == ... | Guards.cs:104:13:104:13 | access to parameter g | false |
| Guards.cs:108:27:108:27 | access to parameter g | Guards.cs:104:13:104:45 | ... == ... | Guards.cs:104:13:104:13 | access to parameter g | false |
+| Guards.cs:131:20:131:20 | access to parameter s | Guards.cs:130:13:130:21 | ... is ... | Guards.cs:130:13:130:13 | access to parameter s | true |
+| Guards.cs:132:16:132:16 | access to parameter s | Guards.cs:130:13:130:21 | ... is ... | Guards.cs:130:13:130:13 | access to parameter s | false |
+| Guards.cs:138:20:138:20 | access to parameter s | Guards.cs:137:13:137:23 | ... is ... | Guards.cs:137:13:137:13 | access to parameter s | true |
+| Guards.cs:139:16:139:16 | access to parameter s | Guards.cs:137:13:137:23 | ... is ... | Guards.cs:137:13:137:13 | access to parameter s | false |
+| Guards.cs:146:16:146:16 | access to parameter o | Guards.cs:144:13:144:25 | ... is ... | Guards.cs:144:13:144:13 | access to parameter o | false |
diff --git a/csharp/ql/test/library-tests/controlflow/guards/Guards.cs b/csharp/ql/test/library-tests/controlflow/guards/Guards.cs
index a143d8d66a7..7634b26f0cf 100644
--- a/csharp/ql/test/library-tests/controlflow/guards/Guards.cs
+++ b/csharp/ql/test/library-tests/controlflow/guards/Guards.cs
@@ -124,4 +124,42 @@ public class Guards
var b1 = s1.Equals(s2); // not null guarded
var b2 = s1?.Equals(s1); // null guarded
}
+
+ int M11(string s)
+ {
+ if (s is null)
+ return s.Length; // not null guarded
+ return s.Length; // null guarded
+ }
+
+ int M12(string s)
+ {
+ if (s is string)
+ return s.Length; // null guarded
+ return s.Length; // not null guarded
+ }
+
+ string M13(object o)
+ {
+ if (o is string s)
+ return s; // not null (but not a guard)
+ return o.ToString(); // not null guarded
+ }
+
+ string M14(object o)
+ {
+ switch (o)
+ {
+ case Action
If an attacker has access to the file system, it is possible to elevate privileges by creating a file such as "C:\Program.exe" that will be executed instead of the intended application.
From cd5e788aa74fe5acc9a240525490880bdad5d051 Mon Sep 17 00:00:00 2001
From: Raul Garcia <42392023+raulgarciamsft@users.noreply.github.com>
Date: Mon, 15 Oct 2018 13:41:21 -0700
Subject: [PATCH 23/98] Update UnsafeCreateProcessCall.ql
---
cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
index 7ff62a1cd86..ec355ac9c28 100644
--- a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
@@ -1,6 +1,6 @@
/**
* @name NULL application name with an unquoted path in call to CreateProcess
- * @description Calling a function of the CreatePorcess* family of functions, which may result in a security vulnerability if the path contains spaces.
+ * @description Calling a function of the CreateProcess* family of functions, which may result in a security vulnerability if the path contains spaces.
* @id cpp/unsafe-create-process-call
* @kind problem
* @problem.severity error
@@ -132,4 +132,4 @@ where
and quotedConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(cmd))
and msg2 = " and with an unquoted lpCommandLine (" + cmd + ") may result in a security vulnerability if the path contains spaces."
)
-select call, msg1 + " " + msg2
\ No newline at end of file
+select call, msg1 + " " + msg2
From 22d54801e5b73dc3b75f2bf930dc9c753492466c Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Mon, 15 Oct 2018 15:53:02 -0700
Subject: [PATCH 24/98] Removed one false-positive scenario (no space on
lpCommandLine) Improved the query to avoid multiple calls to hasGlobalName
Fixed typos Simplified the test case file
---
.../CWE/CWE-428/UnsafeCreateProcessCall.ql | 76 ++--
.../CWE/CWE-428/UnsafeCreateProcessCall.cpp | 379 +++++++-----------
.../CWE-428/UnsafeCreateProcessCall.expected | 26 +-
3 files changed, 201 insertions(+), 280 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
index ec355ac9c28..de7685bf506 100644
--- a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
@@ -14,55 +14,47 @@ import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.dataflow.DataFlow2
+predicate isCreateProcessFunction(FunctionCall call, int applicationNameIndex, int commandLineIndex) {
+ (
+ call.getTarget().hasGlobalName("CreateProcessA")
+ and applicationNameIndex = 0
+ and commandLineIndex = 1
+ ) or (
+ call.getTarget().hasGlobalName("CreateProcessW")
+ and applicationNameIndex = 0
+ and commandLineIndex = 1
+ ) or (
+ call.getTarget().hasGlobalName("CreateProcessWithTokenW")
+ and applicationNameIndex = 2
+ and commandLineIndex = 3
+ ) or (
+ call.getTarget().hasGlobalName("CreateProcessWithLogonW")
+ and applicationNameIndex = 4
+ and commandLineIndex = 5
+ ) or (
+ call.getTarget().hasGlobalName("CreateProcessAsUserA")
+ and applicationNameIndex = 1
+ and commandLineIndex = 2
+ ) or (
+ call.getTarget().hasGlobalName("CreateProcessAsUserW")
+ and applicationNameIndex = 1
+ and commandLineIndex = 2
+ )
+}
/**
* A function call to CreateProcess (either wide-char or single byte string versions)
*/
class CreateProcessFunctionCall extends FunctionCall {
CreateProcessFunctionCall() {
- (
- this.getTarget().hasGlobalName("CreateProcessA") or
- this.getTarget().hasGlobalName("CreateProcessW") or
- this.getTarget().hasGlobalName("CreateProcessWithTokenW") or
- this.getTarget().hasGlobalName("CreateProcessWithLogonW") or
- this.getTarget().hasGlobalName("CreateProcessAsUserA") or
- this.getTarget().hasGlobalName("CreateProcessAsUserW")
- )
+ isCreateProcessFunction( this, _, _)
}
int getApplicationNameArgumentId() {
- if(
- this.getTarget().hasGlobalName("CreateProcessA") or
- this.getTarget().hasGlobalName("CreateProcessW")
- ) then ( result = 0 )
- else if (
- this.getTarget().hasGlobalName("CreateProcessWithTokenW")
- ) then ( result = 2 )
- else if (
- this.getTarget().hasGlobalName("CreateProcessWithLogonW")
- ) then ( result = 4 )
- else if(
- this.getTarget().hasGlobalName("CreateProcessAsUserA") or
- this.getTarget().hasGlobalName("CreateProcessAsUserW")
- ) then ( result = 1 )
- else (result = -1 )
+ isCreateProcessFunction( this, result, _)
}
int getCommandLineArgumentId() {
- if(
- this.getTarget().hasGlobalName("CreateProcessA") or
- this.getTarget().hasGlobalName("CreateProcessW")
- ) then ( result = 1 )
- else if (
- this.getTarget().hasGlobalName("CreateProcessWithTokenW")
- ) then ( result = 3 )
- else if (
- this.getTarget().hasGlobalName("CreateProcessWithLogonW")
- ) then ( result = 5 )
- else if(
- this.getTarget().hasGlobalName("CreateProcessAsUserA") or
- this.getTarget().hasGlobalName("CreateProcessAsUserW")
- ) then ( result = 2 )
- else (result = -1 )
+ isCreateProcessFunction( this, _, result)
}
}
@@ -99,7 +91,7 @@ class QuotedCommandInCreateProcessFunctionConfiguration extends DataFlow2::Confi
exists( string s |
s = source.asExpr().getValue().toString()
and
- not isQuotedApplicationNameOnCmd(s)
+ not isQuotedOrNoSpaceApplicationNameOnCmd(s)
)
}
@@ -113,8 +105,10 @@ class QuotedCommandInCreateProcessFunctionConfiguration extends DataFlow2::Confi
}
bindingset[s]
-predicate isQuotedApplicationNameOnCmd(string s){
- s.regexpMatch("\"([^\"])*\"(\\s|.)*")
+predicate isQuotedOrNoSpaceApplicationNameOnCmd(string s){
+ s.regexpMatch("\"([^\"])*\"(\\s|.)*") // The first element (path) is quoted
+ or
+ s.regexpMatch("[^\\s]+") // There are no spaces in the string
}
from CreateProcessFunctionCall call, string msg1, string msg2
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
index 20508ee5f1b..6f3f76001b5 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.cpp
@@ -1,207 +1,126 @@
// semmle-extractor-options: --microsoft
#define NULL 0
#define FALSE 0
-#define far
#define LOGON_WITH_PROFILE 0x00000001
-typedef char CHAR;
-typedef unsigned short WCHAR;
-typedef int BOOL;
-#define CONST const
-typedef CHAR *PCHAR, *LPCH, *PCH;
-typedef CONST CHAR *LPCCH, *PCCH;
-typedef CHAR *NPSTR, *LPSTR, *PSTR;
-typedef CONST PSTR *PCZPSTR;
-typedef CONST CHAR *LPCSTR, *PCSTR;
-typedef WCHAR *PWCHAR, *LPWCH, *PWCH;
-typedef CONST WCHAR *LPCWCH, *PCWCH;
-typedef WCHAR *NWPSTR, *LPWSTR, *PWSTR;
-typedef PWSTR *PZPWSTR;
-typedef CONST PWSTR *PCZPWSTR;
-typedef CONST WCHAR *LPCWSTR, *PCWSTR;
-typedef unsigned long DWORD;
-typedef void far *LPVOID;
-typedef unsigned short WORD;
-typedef unsigned char BYTE;
-typedef BYTE far *LPBYTE;
-typedef void *HANDLE;
-
-typedef struct _SECURITY_ATTRIBUTES {
- DWORD nLength;
- LPVOID lpSecurityDescriptor;
- BOOL bInheritHandle;
-} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
-
-typedef struct _PROCESS_INFORMATION {
- HANDLE hProcess;
- HANDLE hThread;
- DWORD dwProcessId;
- DWORD dwThreadId;
-} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
-
-typedef struct _STARTUPINFOA {
- DWORD cb;
- LPSTR lpReserved;
- LPSTR lpDesktop;
- LPSTR lpTitle;
- DWORD dwX;
- DWORD dwY;
- DWORD dwXSize;
- DWORD dwYSize;
- DWORD dwXCountChars;
- DWORD dwYCountChars;
- DWORD dwFillAttribute;
- DWORD dwFlags;
- WORD wShowWindow;
- WORD cbReserved2;
- LPBYTE lpReserved2;
- HANDLE hStdInput;
- HANDLE hStdOutput;
- HANDLE hStdError;
-} STARTUPINFOA, *LPSTARTUPINFOA;
-typedef struct _STARTUPINFOW {
- DWORD cb;
- LPWSTR lpReserved;
- LPWSTR lpDesktop;
- LPWSTR lpTitle;
- DWORD dwX;
- DWORD dwY;
- DWORD dwXSize;
- DWORD dwYSize;
- DWORD dwXCountChars;
- DWORD dwYCountChars;
- DWORD dwFillAttribute;
- DWORD dwFlags;
- WORD wShowWindow;
- WORD cbReserved2;
- LPBYTE lpReserved2;
- HANDLE hStdInput;
- HANDLE hStdOutput;
- HANDLE hStdError;
-} STARTUPINFOW, *LPSTARTUPINFOW;
-
-typedef STARTUPINFOW STARTUPINFO;
-typedef LPSTARTUPINFOW LPSTARTUPINFO;
-
-
-BOOL
+int
CreateProcessA(
- LPCSTR lpApplicationName,
- LPSTR lpCommandLine,
- LPSECURITY_ATTRIBUTES lpProcessAttributes,
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- BOOL bInheritHandles,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCSTR lpCurrentDirectory,
- LPSTARTUPINFOA lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
+ const char* lpApplicationName,
+ char* lpCommandLine,
+ void* lpProcessAttributes,
+ void* lpThreadAttributes,
+ int bInheritHandles,
+ unsigned long dwCreationFlags,
+ void* lpEnvironment,
+ const char* lpCurrentDirectory,
+ void* lpStartupInfo,
+ void* lpProcessInformation
);
-BOOL
+int
CreateProcessW(
- LPCWSTR lpApplicationName,
- LPWSTR lpCommandLine,
- LPSECURITY_ATTRIBUTES lpProcessAttributes,
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- BOOL bInheritHandles,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCWSTR lpCurrentDirectory,
- LPSTARTUPINFOW lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
+ const wchar_t* lpApplicationName,
+ wchar_t* lpCommandLine,
+ void* lpProcessAttributes,
+ void* lpThreadAttributes,
+ int bInheritHandles,
+ unsigned long dwCreationFlags,
+ void* lpEnvironment,
+ const wchar_t* lpCurrentDirectory,
+ void* lpStartupInfo,
+ void* lpProcessInformation
);
#define CreateProcess CreateProcessW
-BOOL
+int
CreateProcessWithTokenW(
- HANDLE hToken,
- DWORD dwLogonFlags,
- LPCWSTR lpApplicationName,
- LPWSTR lpCommandLine,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCWSTR lpCurrentDirectory,
- LPSTARTUPINFOW lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
+ void* hToken,
+ unsigned long dwLogonFlags,
+ const wchar_t* lpApplicationName,
+ wchar_t* lpCommandLine,
+ unsigned long dwCreationFlags,
+ void* lpEnvironment,
+ const wchar_t* lpCurrentDirectory,
+ void* lpStartupInfo,
+ void* lpProcessInformation
);
-BOOL
+int
CreateProcessWithLogonW(
- LPCWSTR lpUsername,
- LPCWSTR lpDomain,
- LPCWSTR lpPassword,
- DWORD dwLogonFlags,
- LPCWSTR lpApplicationName,
- LPWSTR lpCommandLine,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCWSTR lpCurrentDirectory,
- LPSTARTUPINFOW lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
+ const wchar_t* lpUsername,
+ const wchar_t* lpDomain,
+ const wchar_t* lpPassword,
+ unsigned long dwLogonFlags,
+ const wchar_t* lpApplicationName,
+ wchar_t* lpCommandLine,
+ unsigned long dwCreationFlags,
+ void* lpEnvironment,
+ const wchar_t* lpCurrentDirectory,
+ void* lpStartupInfo,
+ void* lpProcessInformation
);
-BOOL
+int
CreateProcessAsUserA(
- HANDLE hToken,
- LPCSTR lpApplicationName,
- LPSTR lpCommandLine,
- LPSECURITY_ATTRIBUTES lpProcessAttributes,
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- BOOL bInheritHandles,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCSTR lpCurrentDirectory,
- LPSTARTUPINFOA lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
+ void* hToken,
+ const char* lpApplicationName,
+ char* lpCommandLine,
+ void* lpProcessAttributes,
+ void* lpThreadAttributes,
+ int bInheritHandles,
+ unsigned long dwCreationFlags,
+ void* lpEnvironment,
+ const char* lpCurrentDirectory,
+ void* lpStartupInfo,
+ void* lpProcessInformation
);
-BOOL
+int
CreateProcessAsUserW(
- HANDLE hToken,
- LPCWSTR lpApplicationName,
- LPWSTR lpCommandLine,
- LPSECURITY_ATTRIBUTES lpProcessAttributes,
- LPSECURITY_ATTRIBUTES lpThreadAttributes,
- BOOL bInheritHandles,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCWSTR lpCurrentDirectory,
- LPSTARTUPINFOW lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
+ void* hToken,
+ const wchar_t* lpApplicationName,
+ wchar_t* lpCommandLine,
+ void* lpProcessAttributes,
+ void* lpThreadAttributes,
+ int bInheritHandles,
+ unsigned long dwCreationFlags,
+ void* lpEnvironment,
+ const wchar_t* lpCurrentDirectory,
+ void* lpStartupInfo,
+ void* lpProcessInformation
);
#define CreateProcessAsUser CreateProcessAsUserW
void positiveTestCases()
{
- LPCWSTR lpCommandLine = (LPCWSTR)L"C:\\Program Files\\MyApp";
- HANDLE h = 0;
- LPWSTR lpApplicationName = NULL;
+ const wchar_t* lpCommandLine = (const wchar_t*)L"C:\\Program Files\\MyApp";
+ void* h = 0;
+ wchar_t* lpApplicationName = NULL;
// CreatePorcessA
CreateProcessA( //BUG
NULL,
- (LPSTR)"C:\\Program Files\\MyApp",
+ (char*)"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcessW
CreateProcessW( //BUG
NULL,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcess
CreateProcess( //BUG
NULL,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// lpCommandLine as hardcoded variable
CreateProcess( //BUG
NULL,
- (LPWSTR)lpCommandLine,
+ (wchar_t*)lpCommandLine,
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessWithTokenW
@@ -209,48 +128,49 @@ void positiveTestCases()
h,
LOGON_WITH_PROFILE,
NULL,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
// CreateProcessWithLogonW
CreateProcessWithLogonW( //BUG
- (LPCWSTR)L"UserName",
- (LPCWSTR)L"CONTOSO",
- (LPCWSTR)L"",
+ (const wchar_t*)L"UserName",
+ (const wchar_t*)L"CONTOSO",
+ (const wchar_t*)L"",
LOGON_WITH_PROFILE,
NULL,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
// CreateProcessAsUserA
CreateProcessAsUserA( //BUG
h,
NULL,
- (LPSTR)"C:\\Program Files\\MyApp",
+ (char*)"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessAsUserW
CreateProcessAsUserW( //BUG
h,
NULL,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessAsUser
CreateProcessAsUser( //BUG
h,
NULL,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcess with a hardcoded variable for application Name (NULL)
+ // Variation: tab instead of space
CreateProcess( //BUG
lpApplicationName,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program\tFiles\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
}
-void PositiveTestCasesWithCmdLineParameter(LPWSTR lpCommandLine)
+void PositiveTestCasesWithCmdLineParameter(wchar_t* lpCommandLine)
{
// lpCommandLine as variable
CreateProcess( //BUG - Depends on the caller
@@ -261,12 +181,12 @@ void PositiveTestCasesWithCmdLineParameter(LPWSTR lpCommandLine)
void PositiveTestCasesWithCmdLineParameter_caller()
{
- PositiveTestCasesWithCmdLineParameter((LPWSTR)L"C:\\Program Files\\MyApp");
+ PositiveTestCasesWithCmdLineParameter((wchar_t*)L"C:\\Program Files\\MyApp");
}
// NOTE: This function will not be flagged as having a bug by this rule.
// but as it is, the function can still be misused
-void FalseNegativeTestCasesWithCmdLineParameter(LPWSTR lpCommandLine)
+void FalseNegativeTestCasesWithCmdLineParameter(wchar_t* lpCommandLine)
{
// lpCommandLine as variable
CreateProcess( //Depends on the caller, this time the caller will quote
@@ -279,18 +199,18 @@ void FalseNegativeTestCasesWithCmdLineParameter_caller()
{
// No bug - escaped command line
// But compare with "PositiveTestCasesWithCmdLineParameter"
- FalseNegativeTestCasesWithCmdLineParameter((LPWSTR)L"\"C:\\Program Files\\MyApp\"");
+ FalseNegativeTestCasesWithCmdLineParameter((wchar_t*)L"\"C:\\Program Files\\MyApp\"");
}
-void PositiveTestCasesWithAppNameParameter(LPWSTR lpApplicationName)
+void PositiveTestCasesWithAppNameParameter(wchar_t* lpApplicationName)
{
- HANDLE h = 0;
+ void* h = 0;
CreateProcessWithTokenW( //BUG - Depends on the caller. In this case the caller sends NULL
h,
LOGON_WITH_PROFILE,
lpApplicationName,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
}
@@ -301,15 +221,15 @@ void PositiveTestCasesWithAppNameParameter_caller()
// NOTE: This function will not be flagged as having a bug by this rule.
// but as it is, the function can still be misused
-void FalseNegativeTestCasesWithAppNameParameter(LPWSTR lpApplicationName)
+void FalseNegativeTestCasesWithAppNameParameter(wchar_t* lpApplicationName)
{
- HANDLE h = 0;
+ void* h = 0;
CreateProcessWithTokenW( // Depends on the caller. In this case the caller sends an ApplicatioName
h,
LOGON_WITH_PROFILE,
lpApplicationName,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
}
@@ -317,10 +237,10 @@ void FalseNegativeTestCasesWithAppNameParameter_caller()
{
// No bug - escaped command line
// But compare with "PositiveTestCasesWithAppNameParameter"
- FalseNegativeTestCasesWithAppNameParameter((LPWSTR)L"MyApp.exe");
+ FalseNegativeTestCasesWithAppNameParameter((wchar_t*)L"MyApp.exe");
}
-bool MayReturnFalse()
+int MayReturnFalse()
{
// return ((rand() % 2) == 0);
return true;
@@ -328,20 +248,20 @@ bool MayReturnFalse()
void TestCaseProbablyBug()
{
- LPCWSTR lpApplicationName = NULL;
+ const wchar_t* lpApplicationName = NULL;
if (!MayReturnFalse())
{
- lpApplicationName = (LPCWSTR)L"app.exe";
+ lpApplicationName = (const wchar_t*)L"app.exe";
}
CreateProcessWithLogonW( // BUG (Probably - depends on a condition that may be false)
- (LPCWSTR)L"UserName",
- (LPCWSTR)L"CONTOSO",
- (LPCWSTR)L"",
+ (const wchar_t*)L"UserName",
+ (const wchar_t*)L"CONTOSO",
+ (const wchar_t*)L"",
LOGON_WITH_PROFILE,
- (LPWSTR)lpApplicationName,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)lpApplicationName,
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
if (lpApplicationName)
@@ -352,32 +272,32 @@ void TestCaseProbablyBug()
void negativeTestCases_quotedCommandLine()
{
- LPCWSTR lpCommandLine = (LPCWSTR)L"\"C:\\Program Files\\MyApp\" with additional params";
- HANDLE h = 0;
- LPWSTR lpApplicationName = NULL;
+ const wchar_t* lpCommandLine = (const wchar_t*)L"\"C:\\Program Files\\MyApp\" with additional params";
+ void* h = 0;
+ wchar_t* lpApplicationName = NULL;
// CreatePorcessA
CreateProcessA(
NULL,
- (LPSTR)"\"C:\\Program Files\\MyApp\"",
+ (char*)"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcessW
CreateProcessW(
NULL,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcess
CreateProcess(
NULL,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// lpCommandLine as hardcoded variable
CreateProcess(
NULL,
- (LPWSTR)lpCommandLine,
+ (wchar_t*)lpCommandLine,
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessWithTokenW
@@ -385,119 +305,126 @@ void negativeTestCases_quotedCommandLine()
h,
LOGON_WITH_PROFILE,
NULL,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
0, NULL, NULL, NULL, NULL);
// CreateProcessWithLogonW
CreateProcessWithLogonW(
- (LPCWSTR)L"UserName",
- (LPCWSTR)L"CONTOSO",
- (LPCWSTR)L"",
+ (const wchar_t*)L"UserName",
+ (const wchar_t*)L"CONTOSO",
+ (const wchar_t*)L"",
LOGON_WITH_PROFILE,
NULL,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
0, NULL, NULL, NULL, NULL);
// CreateProcessAsUserA
CreateProcessAsUserA(
h,
NULL,
- (LPSTR)"\"C:\\Program Files\\MyApp\"",
+ (char*)"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessAsUserW
CreateProcessAsUserW(
h,
NULL,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessAsUser
CreateProcessAsUser(
h,
NULL,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcess with a hardcoded variable for application Name (NULL)
CreateProcess(
lpApplicationName,
- (LPWSTR)L"\"C:\\Program Files\\MyApp\"",
+ (wchar_t*)L"\"C:\\Program Files\\MyApp\"",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
+ // Null AppName, but lpComamndLine has no spaces/tabs
+ CreateProcessA(
+ NULL,
+ (char*)"C:\\MyFolder\\MyApp.exe",
+ NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
+
}
void negativeTestCases_AppNameSet()
{
- LPCWSTR lpCommandLine = (LPCWSTR)L"C:\\Program Files\\MyApp";
- HANDLE h = 0;
- LPCWSTR lpApplicationName = (LPCWSTR)L"MyApp.exe";
+ const wchar_t* lpCommandLine = (const wchar_t*)L"C:\\Program Files\\MyApp";
+ void* h = 0;
+ const wchar_t* lpApplicationName = (const wchar_t*)L"MyApp.exe";
// CreatePorcessA
CreateProcessA(
- (LPSTR)"MyApp.exe",
- (LPSTR)"C:\\Program Files\\MyApp",
+ (char*)"MyApp.exe",
+ (char*)"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcessW
CreateProcessW(
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcess
CreateProcess(
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// lpCommandLine as hardcoded variable
CreateProcess(
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)lpCommandLine,
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)lpCommandLine,
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessWithTokenW
CreateProcessWithTokenW(
h,
LOGON_WITH_PROFILE,
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
// CreateProcessWithLogonW
CreateProcessWithLogonW(
- (LPCWSTR)L"UserName",
- (LPCWSTR)L"CONTOSO",
- (LPCWSTR)L"",
+ (const wchar_t*)L"UserName",
+ (const wchar_t*)L"CONTOSO",
+ (const wchar_t*)L"",
LOGON_WITH_PROFILE,
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
0, NULL, NULL, NULL, NULL);
// CreateProcessAsUserA
CreateProcessAsUserA(
h,
- (LPSTR)"MyApp.exe",
- (LPSTR)"C:\\Program Files\\MyApp",
+ (char*)"MyApp.exe",
+ (char*)"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessAsUserW
CreateProcessAsUserW(
h,
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreateProcessAsUser
CreateProcessAsUser(
h,
- (LPWSTR)L"MyApp.exe",
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)L"MyApp.exe",
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
// CreatePorcess with a hardcoded variable for application Name (NULL)
CreateProcess(
- (LPWSTR)lpApplicationName,
- (LPWSTR)L"C:\\Program Files\\MyApp",
+ (wchar_t*)lpApplicationName,
+ (wchar_t*)L"C:\\Program Files\\MyApp",
NULL, NULL, FALSE, 0, NULL, NULL, NULL, NULL);
}
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
index 1f4519cabb0..652d92d19f5 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
@@ -1,13 +1,13 @@
-| UnsafeCreateProcessCall.cpp:184:5:184:18 | call to CreateProcessA | call to CreateProcessA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:190:5:190:18 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:196:5:196:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:202:5:202:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:208:5:208:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:216:5:216:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:226:5:226:24 | call to CreateProcessAsUserA | call to CreateProcessAsUserA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:233:5:233:24 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:240:5:240:23 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:247:5:247:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:256:5:256:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:289:5:289:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:338:5:338:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:103:5:103:18 | call to CreateProcessA | call to CreateProcessA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:109:5:109:18 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:115:5:115:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:121:5:121:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:127:5:127:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:135:5:135:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:145:5:145:24 | call to CreateProcessAsUserA | call to CreateProcessAsUserA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:152:5:152:24 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:159:5:159:23 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:167:5:167:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program\tFiles\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:176:5:176:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:209:5:209:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:258:5:258:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
From 26009065af84d08c105abfc9de637f5a67f4d465 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Tue, 16 Oct 2018 11:29:15 +0200
Subject: [PATCH 25/98] Java: Fix regression.
---
.../code/java/dataflow/RangeAnalysis.qll | 26 ++++++++++++++++---
java/ql/test/query-tests/RangeAnalysis/A.java | 11 ++++++++
2 files changed, 34 insertions(+), 3 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
index d5acafa47ce..694989d1a8e 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
@@ -133,21 +133,41 @@ private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int
)
}
+private predicate gcdInput(int x, int y) {
+ exists(ComparisonExpr comp, Bound b |
+ exprModulus(comp.getLesserOperand(), b, _, x) and
+ exprModulus(comp.getGreaterOperand(), b, _, y)
+ ) or
+ exists(int x0, int y0 |
+ gcdInput(x0, y0) and
+ x = y0 and
+ y = x0 % y0
+ )
+}
+
+private int gcd(int x, int y) {
+ result = x.abs() and y = 0 and gcdInput(x, y)
+ or
+ result = gcd(y, x % y) and y != 0 and gcdInput(x, y)
+}
+
/**
* Holds if `comp` is a comparison between `x` and `y` for which `y - x` has a
* fixed value modulo some `mod > 1`, such that the comparison can be
* strengthened by `strengthen` when evaluating to `testIsTrue`.
*/
private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) {
- exists(Bound b, int v1, int v2, int mod, boolean resultIsStrict, int d, int k |
+ exists(Bound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k |
// If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
// `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
// `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
// strict then the strengthening amount is instead `k - 1` modulo `mod`:
// `x < y` means `0 <= y - x - 1 =(mod) k - 1` so `k - 1 <= y - x - 1` and
// thus `k - 1 < y - x` with `0 <= k - 1 < mod`.
- exprModulus(comp.getLesserOperand(), b, v1, mod) and
- exprModulus(comp.getGreaterOperand(), b, v2, mod) and
+ exprModulus(comp.getLesserOperand(), b, v1, mod1) and
+ exprModulus(comp.getGreaterOperand(), b, v2, mod2) and
+ mod = gcd(mod1, mod2) and
+ mod != 1 and
(testIsTrue = true or testIsTrue = false) and
(if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
(resultIsStrict = true and d = 1 or resultIsStrict = false and d = 0) and
diff --git a/java/ql/test/query-tests/RangeAnalysis/A.java b/java/ql/test/query-tests/RangeAnalysis/A.java
index d18d09d2ccd..1a3a3377286 100644
--- a/java/ql/test/query-tests/RangeAnalysis/A.java
+++ b/java/ql/test/query-tests/RangeAnalysis/A.java
@@ -151,4 +151,15 @@ public class A {
}
}
+ void m12() {
+ int[] a = new int[] { 1, 2, 3, 4, 5, 6 };
+ int sum = 0;
+ for (int i = 0; i < a.length; i += 2) {
+ sum += a[i] + a[i + 1]; // OK
+ }
+ int[] b = new int[8];
+ for (int i = 2; i < 8; i += 2) {
+ sum += b[i] + b[i + 1]; // OK
+ }
+ }
}
From 7ab723ae7950e69629321058090c79bf12b79bbc Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Tue, 16 Oct 2018 10:00:51 -0700
Subject: [PATCH 26/98] Fixing typos & incorporating feedback. (MSFT feedback)
Adding a new tag in the header @msrc.severity important
---
.../CWE/CWE-428/UnsafeCreateProcessCall.qhelp | 6 ++---
.../CWE/CWE-428/UnsafeCreateProcessCall.ql | 5 ++--
.../CWE-428/UnsafeCreateProcessCall.expected | 26 +++++++++----------
3 files changed, 19 insertions(+), 18 deletions(-)
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp
index 550355e8026..d4691284c3c 100644
--- a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.qhelp
@@ -4,7 +4,7 @@
-
This query indicates that there is a call to a function of the CreateProcess* family of functions, which may result in a security vulnerability if the path contains spaces.
+
This query indicates that there is a call to a function of the CreateProcess* family of functions, which introduces a security vulnerability.
@@ -13,9 +13,9 @@
-
In the following example, CreateProcessW is called with a NULL value for lpApplicationName,
+
In the following example, CreateProcessW is called with a NULL value for lpApplicationName,
and the value for lpCommandLine that represent the application path is not quoted and has spaces in it.
-
If an attacker has access to the file system, it is possible to elevate privileges by creating a file such as "C:\Program.exe" that will be executed instead of the intended application.
+
If an attacker has access to the file system, they can elevate privileges by creating a file such as C:\Program.exe that will be executed instead of the intended application.
To fix this issue, specify a valid string for lpApplicationName, or quote the path for lpCommandLine. For example:
diff --git a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
index de7685bf506..1518c6c1f0f 100644
--- a/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
+++ b/cpp/ql/src/Security/CWE/CWE-428/UnsafeCreateProcessCall.ql
@@ -1,10 +1,11 @@
/**
* @name NULL application name with an unquoted path in call to CreateProcess
- * @description Calling a function of the CreateProcess* family of functions, which may result in a security vulnerability if the path contains spaces.
+ * @description Calling a function of the CreateProcess* family of functions, where the path contains spaces, introduces a security vulnerability.
* @id cpp/unsafe-create-process-call
* @kind problem
* @problem.severity error
* @precision medium
+ * @msrc.severity important
* @tags security
* external/cwe/cwe-428
* external/microsoft/C6277
@@ -124,6 +125,6 @@ where
QuotedCommandInCreateProcessFunctionConfiguration quotedConfig |
cmd = call.getArgument(call.getCommandLineArgumentId())
and quotedConfig.hasFlow(DataFlow2::exprNode(source), DataFlow2::exprNode(cmd))
- and msg2 = " and with an unquoted lpCommandLine (" + cmd + ") may result in a security vulnerability if the path contains spaces."
+ and msg2 = " and with an unquoted lpCommandLine (" + cmd + ") introduces a security vulnerability if the path contains spaces."
)
select call, msg1 + " " + msg2
diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
index 652d92d19f5..7295f1197b8 100644
--- a/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
+++ b/cpp/ql/test/query-tests/Security/CWE/CWE-428/UnsafeCreateProcessCall.expected
@@ -1,13 +1,13 @@
-| UnsafeCreateProcessCall.cpp:103:5:103:18 | call to CreateProcessA | call to CreateProcessA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:109:5:109:18 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:115:5:115:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:121:5:121:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:127:5:127:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:135:5:135:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:145:5:145:24 | call to CreateProcessAsUserA | call to CreateProcessAsUserA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:152:5:152:24 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:159:5:159:23 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:167:5:167:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program\tFiles\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:176:5:176:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:209:5:209:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
-| UnsafeCreateProcessCall.cpp:258:5:258:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) may result in a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:103:5:103:18 | call to CreateProcessA | call to CreateProcessA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:109:5:109:18 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:115:5:115:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:121:5:121:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:127:5:127:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:135:5:135:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:145:5:145:24 | call to CreateProcessAsUserA | call to CreateProcessAsUserA with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:152:5:152:24 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:159:5:159:23 | call to CreateProcessAsUserW | call to CreateProcessAsUserW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:167:5:167:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program\tFiles\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:176:5:176:17 | call to CreateProcessW | call to CreateProcessW with lpApplicationName == NULL (0) and with an unquoted lpCommandLine (lpCommandLine) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:209:5:209:27 | call to CreateProcessWithTokenW | call to CreateProcessWithTokenW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
+| UnsafeCreateProcessCall.cpp:258:5:258:27 | call to CreateProcessWithLogonW | call to CreateProcessWithLogonW with lpApplicationName == NULL (lpApplicationName) and with an unquoted lpCommandLine (C:\\Program Files\\MyApp) introduces a security vulnerability if the path contains spaces. |
From 29f655b0dcb09125193c448e0af9a86fc6c2b48d Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Tue, 16 Oct 2018 20:40:35 +0200
Subject: [PATCH 27/98] Add `.lgtm.yml` file
---
.lgtm.yml | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100755 .lgtm.yml
diff --git a/.lgtm.yml b/.lgtm.yml
new file mode 100755
index 00000000000..4098d8b1ce6
--- /dev/null
+++ b/.lgtm.yml
@@ -0,0 +1,17 @@
+path_classifiers:
+ library:
+ - javascript/externs
+
+ test:
+ - csharp/ql/src
+ - csharp/ql/test
+ - javascript/ql/src
+ - javascript/ql/test
+
+queries:
+ - include: "*"
+
+extraction:
+ python:
+ python_setup:
+ version: 3
From 73cae5390e449bcb98060aa055fa0793d1b540e9 Mon Sep 17 00:00:00 2001
From: Robert Marsh
Date: Tue, 16 Oct 2018 15:37:06 -0700
Subject: [PATCH 28/98] C++: new query for dead code after goto or break
---
cpp/ql/src/Critical/DeadCodeGoto.cpp | 3 ++
cpp/ql/src/Critical/DeadCodeGoto.qhelp | 28 ++++++++++
cpp/ql/src/Critical/DeadCodeGoto.ql | 31 +++++++++++
.../DeadCodeGoto/DeadCodeGoto.expected | 3 ++
.../Critical/DeadCodeGoto/DeadCodeGoto.qlref | 1 +
.../Critical/DeadCodeGoto/test.cpp | 52 +++++++++++++++++++
6 files changed, 118 insertions(+)
create mode 100644 cpp/ql/src/Critical/DeadCodeGoto.cpp
create mode 100644 cpp/ql/src/Critical/DeadCodeGoto.qhelp
create mode 100644 cpp/ql/src/Critical/DeadCodeGoto.ql
create mode 100644 cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected
create mode 100644 cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref
create mode 100644 cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.cpp b/cpp/ql/src/Critical/DeadCodeGoto.cpp
new file mode 100644
index 00000000000..8ac0cc703b0
--- /dev/null
+++ b/cpp/ql/src/Critical/DeadCodeGoto.cpp
@@ -0,0 +1,3 @@
+goto err;
+free(pointer);
+err: return -1;
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.qhelp b/cpp/ql/src/Critical/DeadCodeGoto.qhelp
new file mode 100644
index 00000000000..25a5414f43a
--- /dev/null
+++ b/cpp/ql/src/Critical/DeadCodeGoto.qhelp
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+Code following a goto or break statement will not be executed, unless there is a label or switch
+case. When the code is necessary, this leads to logical errors or resource leaks. If the code is
+unnecessary, it may confuse readers.
+
+
+
+
+If the unreachable code is necessary, move the goto or break statement to after the code.
+Otherwise, delete the unreachable code.
+
+
+
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.ql b/cpp/ql/src/Critical/DeadCodeGoto.ql
new file mode 100644
index 00000000000..4889425c017
--- /dev/null
+++ b/cpp/ql/src/Critical/DeadCodeGoto.ql
@@ -0,0 +1,31 @@
+/**
+ * @name Dead code due to goto or break statement
+ * @description A goto or break statement is followed by unreachable code.
+ * @kind problem
+ * @problem.severity warning
+ * @id cpp/dead-code-goto
+ * @tags maintainability
+ * external/cwe/cwe-561
+ */
+
+import cpp
+
+Stmt getNextRealStmt(Block b, int i) {
+ result = b.getStmt(i + 1) and
+ not result instanceof EmptyStmt
+ or
+ b.getStmt(i + 1) instanceof EmptyStmt and
+ result = getNextRealStmt(b, i + 1)
+}
+
+from JumpStmt js, Block b, int i, Stmt s
+where b.getStmt(i) = js
+ and s = getNextRealStmt(b, i)
+ // the next statement isn't jumped to
+ and not s instanceof LabelStmt
+ and not s instanceof SwitchCase
+ // the next statement isn't breaking out of a switch
+ and not s.(BreakStmt).getBreakable() instanceof SwitchStmt
+ // the jump isn't a goto into the body of the next statement
+ and not exists (LabelStmt ls | s.(Loop).getStmt().getAChild*() = ls | ls.getName() = js.(GotoStmt).getName())
+select js, "This statement makes $@ dead.", s, s.toString()
diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected
new file mode 100644
index 00000000000..ca69006645e
--- /dev/null
+++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected
@@ -0,0 +1,3 @@
+| test.cpp:2:2:2:12 | goto ... | This statement makes $@ dead. | test.cpp:3:2:3:5 | ExprStmt | ExprStmt |
+| test.cpp:9:3:9:8 | break; | This statement makes $@ dead. | test.cpp:10:3:10:6 | ExprStmt | ExprStmt |
+| test.cpp:37:3:37:8 | break; | This statement makes $@ dead. | test.cpp:38:3:38:11 | return ... | return ... |
diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref
new file mode 100644
index 00000000000..0786047da5f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.qlref
@@ -0,0 +1 @@
+Critical/DeadCodeGoto.ql
diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp
new file mode 100644
index 00000000000..11e7c18a294
--- /dev/null
+++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp
@@ -0,0 +1,52 @@
+int test1(int x) {
+ goto label; // BAD
+ x++;
+ label: return x;
+}
+
+int test2(int x) {
+ do {
+ break; // BAD
+ x++;
+ } while(false);
+ return x;
+}
+
+int test3(int x) {
+ goto label; // GOOD
+ label: x++;
+ return x;
+}
+
+int test4(int x) {
+ goto label; // GOOD
+ do {
+ label: x++;
+ } while(false);
+ return x;
+}
+
+int test5(int x, int y) {
+ switch(y) {
+ case 0:
+ break; // GOOD
+ case 1:
+ goto label; // GOOD
+ break;
+ case 2:
+ break; // BAD
+ return x;
+ case 3:
+ return x;
+ break; // GOOD
+ case 4:
+ goto label; // GOOD
+ case 5:
+ goto label;; // GOOD
+ default:
+ x++;
+ }
+ label:
+ return x;
+}
+
From 61f338449cff0a58136f92587f9078501df41eb9 Mon Sep 17 00:00:00 2001
From: Robert Marsh
Date: Tue, 16 Oct 2018 15:40:59 -0700
Subject: [PATCH 29/98] C++: Change note and precision for DeadCodeGoto.ql
---
change-notes/1.19/analysis-cpp.md | 1 +
cpp/ql/src/Critical/DeadCodeGoto.ql | 1 +
2 files changed, 2 insertions(+)
diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md
index aa74fddba1b..e219f40fc27 100644
--- a/change-notes/1.19/analysis-cpp.md
+++ b/change-notes/1.19/analysis-cpp.md
@@ -9,6 +9,7 @@
| Cast between HRESULT and a Boolean type (`cpp/hresult-boolean-conversion`) | external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. |
| Setting a DACL to `NULL` in a `SECURITY_DESCRIPTOR` (`cpp/unsafe-dacl-security-descriptor`) | external/cwe/cwe-732 | This query finds code that creates world-writable objects on Windows by setting their DACL to `NULL`. Enabled by default. |
| Cast from char* to wchar_t* | security, external/cwe/cwe-704 | Detects potentially dangerous casts from char* to wchar_t*. Enabled by default on LGTM. |
+| Dead code due to goto or break statement (`cpp/dead-code-goto`) | maintainability, external/cwe/cwe-561 | Detects dead code following a goto or break statement. Enabled by default on LGTM. |
## Changes to existing queries
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.ql b/cpp/ql/src/Critical/DeadCodeGoto.ql
index 4889425c017..0fd4be5ccb0 100644
--- a/cpp/ql/src/Critical/DeadCodeGoto.ql
+++ b/cpp/ql/src/Critical/DeadCodeGoto.ql
@@ -3,6 +3,7 @@
* @description A goto or break statement is followed by unreachable code.
* @kind problem
* @problem.severity warning
+ * @precision high
* @id cpp/dead-code-goto
* @tags maintainability
* external/cwe/cwe-561
From 8158d456f39a15c36c5edbe7f0ed7a13615e9f93 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 17 Oct 2018 10:25:05 +0200
Subject: [PATCH 30/98] C#: Use hashing to detect duplicate trap files
---
.../extractor/Semmle.Extraction/TrapWriter.cs | 30 ++++++++++++++-----
1 file changed, 23 insertions(+), 7 deletions(-)
diff --git a/csharp/extractor/Semmle.Extraction/TrapWriter.cs b/csharp/extractor/Semmle.Extraction/TrapWriter.cs
index 3e57e9e7918..5ead984b5d5 100644
--- a/csharp/extractor/Semmle.Extraction/TrapWriter.cs
+++ b/csharp/extractor/Semmle.Extraction/TrapWriter.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.IO.Compression;
+using System.Security.Cryptography;
using System.Text;
using Semmle.Util;
using Semmle.Util.Logging;
@@ -170,21 +171,23 @@ namespace Semmle.Extraction
WriterLazy.Value.Close();
if (TryMove(tmpFile, TrapFile))
return;
- else if (discardDuplicates)
+
+ if (discardDuplicates)
{
FileUtils.TryDelete(tmpFile);
return;
}
- string root = TrapFile.Substring(0, TrapFile.Length - 8); // Remove trailing ".trap.gz"
-
- // Loop until we find an available trap filename.
- for (int n = 0; n < 100; ++n)
+ var existingHash = ComputeHash(TrapFile);
+ var hash = ComputeHash(tmpFile);
+ if (existingHash != hash)
{
- if (TryMove(tmpFile, string.Format("{0}.{1}.trap.gz", root, n)))
+ var root = TrapFile.Substring(0, TrapFile.Length - 8); // Remove trailing ".trap.gz"
+ if (TryMove(tmpFile, $"{root}-{hash}.trap.gz"))
return;
}
- Logger.Log(Severity.Error, "Failed to move the trap file from {0} to {1}", tmpFile, TrapFile);
+ Logger.Log(Severity.Info, "Identical trap file for {0} already exists", TrapFile);
+ FileUtils.TryDelete(tmpFile);
}
}
catch (Exception ex)
@@ -203,6 +206,19 @@ namespace Semmle.Extraction
//#################### PRIVATE METHODS ####################
#region
+ ///
+ /// Computes the hash of .
+ ///
+ static string ComputeHash(string filePath)
+ {
+ using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+ using (var shaAlg = new SHA256Managed())
+ {
+ var sha = shaAlg.ComputeHash(fileStream);
+ return Convert.ToBase64String(sha);
+ }
+ }
+
class TrapBuilder : ITrapBuilder
{
readonly StreamWriter StreamWriter;
From 976e5ed80fc12d5700cda46a9e040798fad9b16a Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Wed, 17 Oct 2018 10:25:53 +0200
Subject: [PATCH 31/98] C#: Pass `--compiler` flag to extractor on Linux
---
.../tracer/linux/csharp-compiler-settings | 2 +
csharp/config/tracer/linux/extract-csharp.sh | 2 +-
.../Semmle.Extraction.CSharp/Analyser.cs | 14 ++++--
.../CompilerVersion.cs | 48 +++++++++++++++----
.../Semmle.Extraction.CSharp/Extractor.cs | 26 ++--------
csharp/extractor/Semmle.Extraction/Context.cs | 4 ++
6 files changed, 60 insertions(+), 36 deletions(-)
diff --git a/csharp/config/tracer/linux/csharp-compiler-settings b/csharp/config/tracer/linux/csharp-compiler-settings
index 96bfd151998..bf6ceede082 100644
--- a/csharp/config/tracer/linux/csharp-compiler-settings
+++ b/csharp/config/tracer/linux/csharp-compiler-settings
@@ -1,6 +1,8 @@
**/mcs.exe:
**/csc.exe:
invoke ${env.SEMMLE_PLATFORM_TOOLS}/csharp/Semmle.Extraction.CSharp.Driver
+ prepend --compiler
+ prepend "${compiler}"
prepend --cil
**/bin/mono*:
**/dotnet:
diff --git a/csharp/config/tracer/linux/extract-csharp.sh b/csharp/config/tracer/linux/extract-csharp.sh
index 9118fa5533b..00bf654d792 100755
--- a/csharp/config/tracer/linux/extract-csharp.sh
+++ b/csharp/config/tracer/linux/extract-csharp.sh
@@ -9,7 +9,7 @@ do
if [[ `basename -- "$i"` =~ csc.exe|mcs.exe|csc.dll ]]
then
echo extract-csharp.sh: exec $extractor --cil $@
- exec "$extractor" --cil $@
+ exec "$extractor" --compiler $i --cil $@
fi
done
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
index 7b05a7bca0d..df2d709dc5e 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Analyser.cs
@@ -46,10 +46,12 @@ namespace Semmle.Extraction.CSharp
/// Arguments passed to csc.
/// The Roslyn compilation.
/// Extractor options.
+ /// The arguments passed to Roslyn.
public void Initialize(
CSharpCommandLineArguments commandLineArguments,
CSharpCompilation compilationIn,
- Options options)
+ Options options,
+ string[] roslynArgs)
{
compilation = compilationIn;
@@ -58,7 +60,7 @@ namespace Semmle.Extraction.CSharp
extractor = new Extraction.Extractor(false, GetOutputName(compilation, commandLineArguments), Logger);
- LogDiagnostics();
+ LogDiagnostics(roslynArgs);
SetReferencePaths();
CompilationErrors += FilteredDiagnostics.Count();
@@ -113,7 +115,7 @@ namespace Semmle.Extraction.CSharp
layout = new Layout();
extractor = new Extraction.Extractor(true, null, Logger);
this.options = options;
- LogDiagnostics();
+ LogDiagnostics(null);
SetReferencePaths();
}
@@ -409,7 +411,8 @@ namespace Semmle.Extraction.CSharp
/// Logs detailed information about this invocation,
/// in the event that errors were detected.
///
- public void LogDiagnostics()
+ /// The arguments passed to Roslyn.
+ public void LogDiagnostics(string[] roslynArgs)
{
Logger.Log(Severity.Info, " Current working directory: {0}", Directory.GetCurrentDirectory());
Logger.Log(Severity.Info, " Extractor: {0}", Environment.GetCommandLineArgs().First());
@@ -438,6 +441,9 @@ namespace Semmle.Extraction.CSharp
}
Logger.Log(Severity.Info, sb.ToString());
+ if (roslynArgs != null)
+ Logger.Log(Severity.Info, $" Arguments to Roslyn: {string.Join(' ', roslynArgs)}");
+
foreach (var error in FilteredDiagnostics)
{
Logger.Log(Severity.Error, " Compilation error: {0}", error);
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs b/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs
index ee1fca5bc2d..cec120ca6e7 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs
@@ -1,5 +1,7 @@
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Runtime.InteropServices;
namespace Semmle.Extraction.CSharp
@@ -53,24 +55,34 @@ namespace Semmle.Extraction.CSharp
var versionInfo = FileVersionInfo.GetVersionInfo(SpecifiedCompiler);
var compilerDir = Path.GetDirectoryName(SpecifiedCompiler);
- bool known_compiler_name = versionInfo.OriginalFilename == "csc.exe" || versionInfo.OriginalFilename == "csc2.exe";
- bool copyright_microsoft = versionInfo.LegalCopyright != null && versionInfo.LegalCopyright.Contains("Microsoft");
- bool mscorlib_exists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll"));
+ var known_compiler_names = new Dictionary
+ {
+ { "csc.exe", "Microsoft" },
+ { "csc2.exe", "Microsoft" },
+ { "csc.dll", "Microsoft" },
+ { "mcs.exe", "Novell" }
+ };
+ var mscorlib_exists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll"));
if (specifiedFramework == null && mscorlib_exists)
{
specifiedFramework = compilerDir;
}
- if (!known_compiler_name)
+ if (!known_compiler_names.TryGetValue(versionInfo.OriginalFilename, out var vendor))
{
- SkipExtractionBecause("the exe name is not recognised");
+ SkipExtractionBecause("the compiler name is not recognised");
+ return;
}
- else if (!copyright_microsoft)
+
+ if (versionInfo.LegalCopyright == null || !versionInfo.LegalCopyright.Contains(vendor))
{
- SkipExtractionBecause("the exe isn't copyright Microsoft");
+ SkipExtractionBecause($"the compiler isn't copyright {vendor}, but instead {versionInfo.LegalCopyright ?? ""}");
+ return;
}
}
+
+ ArgsWithResponse = AddDefaultResponse(CscRsp, options.CompilerArguments).ToArray();
}
void SkipExtractionBecause(string reason)
@@ -87,7 +99,7 @@ namespace Semmle.Extraction.CSharp
///
/// The file csc.rsp.
///
- public string CscRsp => Path.Combine(FrameworkPath, csc_rsp);
+ string CscRsp => Path.Combine(FrameworkPath, csc_rsp);
///
/// Should we skip extraction?
@@ -103,5 +115,25 @@ namespace Semmle.Extraction.CSharp
/// Gets additional reference directories - the compiler directory.
///
public string AdditionalReferenceDirectories => SpecifiedCompiler != null ? Path.GetDirectoryName(SpecifiedCompiler) : null;
+
+ ///
+ /// Adds @csc.rsp to the argument list to mimic csc.exe.
+ ///
+ /// The full pathname of csc.rsp.
+ /// The other command line arguments.
+ /// Modified list of arguments.
+ static IEnumerable AddDefaultResponse(string responseFile, IEnumerable args)
+ {
+ return SuppressDefaultResponseFile(args) && File.Exists(responseFile) ?
+ args :
+ new[] { "@" + responseFile }.Concat(args);
+ }
+
+ static bool SuppressDefaultResponseFile(IEnumerable args)
+ {
+ return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant()));
+ }
+
+ public readonly string[] ArgsWithResponse;
}
}
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs b/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs
index 10a145d9e2f..83f2d39eda9 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/Extractor.cs
@@ -87,11 +87,9 @@ namespace Semmle.Extraction.CSharp
return ExitCode.Ok;
}
- var argsWithResponse = AddDefaultResponse(compilerVersion.CscRsp, commandLineArguments.CompilerArguments);
-
var cwd = Directory.GetCurrentDirectory();
var compilerArguments = CSharpCommandLineParser.Default.Parse(
- argsWithResponse,
+ compilerVersion.ArgsWithResponse,
cwd,
compilerVersion.FrameworkPath,
compilerVersion.AdditionalReferenceDirectories
@@ -128,7 +126,7 @@ namespace Semmle.Extraction.CSharp
{
logger.Log(Severity.Error, " No source files");
++analyser.CompilationErrors;
- analyser.LogDiagnostics();
+ analyser.LogDiagnostics(compilerVersion.ArgsWithResponse);
return ExitCode.Failed;
}
@@ -146,7 +144,7 @@ namespace Semmle.Extraction.CSharp
// already.
);
- analyser.Initialize(compilerArguments, compilation, commandLineArguments);
+ analyser.Initialize(compilerArguments, compilation, commandLineArguments, compilerVersion.ArgsWithResponse);
analyser.AnalyseReferences();
foreach (var tree in compilation.SyntaxTrees)
@@ -172,24 +170,6 @@ namespace Semmle.Extraction.CSharp
}
}
- internal static bool SuppressDefaultResponseFile(IEnumerable args)
- {
- return args.Any(arg => new[] { "/noconfig", "-noconfig" }.Contains(arg.ToLowerInvariant()));
- }
-
- ///
- /// Adds @csc.rsp to the argument list to mimic csc.exe.
- ///
- /// The full pathname of csc.rsp.
- /// The other command line arguments.
- /// Modified list of arguments.
- static IEnumerable AddDefaultResponse(string responseFile, IEnumerable args)
- {
- return SuppressDefaultResponseFile(args) && File.Exists(responseFile) ?
- args :
- new[] { "@" + responseFile }.Concat(args);
- }
-
///
/// Gets the complete list of locations to locate references.
///
diff --git a/csharp/extractor/Semmle.Extraction/Context.cs b/csharp/extractor/Semmle.Extraction/Context.cs
index b66370e896b..b2b3c42765f 100644
--- a/csharp/extractor/Semmle.Extraction/Context.cs
+++ b/csharp/extractor/Semmle.Extraction/Context.cs
@@ -171,6 +171,10 @@ namespace Semmle.Extraction
{
populateQueue.Dequeue()();
}
+ catch (InternalError e)
+ {
+ Extractor.Message(e.ExtractionMessage);
+ }
catch (Exception e)
{
Extractor.Message(new Message { severity = Severity.Error, exception = e, message = "Uncaught exception" });
From d57e93d5c63b803386f46965ba11a643cd76904b Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Mon, 1 Oct 2018 08:04:45 +0100
Subject: [PATCH 32/98] JavaScript: Fix typo in query help.
(cherry picked from commit 1ab943c16b81784a22c31a3ca7277bddc1952174)
---
javascript/ql/src/Expressions/ImplicitOperandConversion.qhelp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/ql/src/Expressions/ImplicitOperandConversion.qhelp b/javascript/ql/src/Expressions/ImplicitOperandConversion.qhelp
index 7313c503e78..667db45513e 100644
--- a/javascript/ql/src/Expressions/ImplicitOperandConversion.qhelp
+++ b/javascript/ql/src/Expressions/ImplicitOperandConversion.qhelp
@@ -39,7 +39,7 @@ property of the name stored in variable member:
However, this test is ineffective as written: the operator ! binds more
-tighly than in, so it is applied first. Applying ! to a
+tightly than in, so it is applied first. Applying ! to a
non-empty string yields false, so the in operator actually
ends up checking whether obj contains a property called "false".
From 6a75ebbae26163efb0ca7b4d1311369f69f8c31a Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Mon, 1 Oct 2018 08:50:53 +0100
Subject: [PATCH 33/98] JavaScript: Update model of `DOMException`.
cf. https://developer.mozilla.org/en-US/docs/Web/API/DOMException/DOMException
(cherry picked from commit 8cc7f5c24259d7a96efc6d1a99f3d25f60f3e7be)
---
javascript/externs/web/w3c_dom1.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/javascript/externs/web/w3c_dom1.js b/javascript/externs/web/w3c_dom1.js
index 6f43c00a3ef..bf85793fec6 100644
--- a/javascript/externs/web/w3c_dom1.js
+++ b/javascript/externs/web/w3c_dom1.js
@@ -25,9 +25,11 @@
/**
* @constructor
+ * @param {string=} message
+ * @param {string=} message
* @see http://www.w3.org/TR/1998/REC-DOM-Level-1-19981001/level-one-core.html#ID-17189187
*/
-function DOMException() {}
+function DOMException(message, name) {}
/**
* @type {number}
From f85889d052b364508317b268ade886ba7c15972a Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 16 Oct 2018 16:08:55 +0100
Subject: [PATCH 34/98] CPP: Fix the example code.
---
cpp/ql/src/Critical/OverflowDestination.cpp | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/cpp/ql/src/Critical/OverflowDestination.cpp b/cpp/ql/src/Critical/OverflowDestination.cpp
index 1a758430bf4..02c5281d0b6 100644
--- a/cpp/ql/src/Critical/OverflowDestination.cpp
+++ b/cpp/ql/src/Critical/OverflowDestination.cpp
@@ -1,13 +1,13 @@
int main(int argc, char* argv[]) {
- char param[SIZE];
+ char param[20];
+ char *arg1;
- char arg1[10];
- char arg2[20];
+ arg1 = argv[1];
//wrong: only uses the size of the source (argv[1]) when using strncpy
- strncpy(param, argv[1], strlen(arg1));
+ strncpy(param, arg1, strlen(arg1));
//correct: uses the size of the destination array as well
- strncpy(param, argv[1], min(strlen(arg1, sizeof(param) -1)));
+ strncpy(param, arg1, min(strlen(arg1), sizeof(param) -1));
}
From 939a836393957a0a6a5a3de66f9974de077e9e40 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 16 Oct 2018 13:41:19 +0100
Subject: [PATCH 35/98] CPP: Add some comments.
---
cpp/ql/src/Critical/OverflowDestination.ql | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/cpp/ql/src/Critical/OverflowDestination.ql b/cpp/ql/src/Critical/OverflowDestination.ql
index ea7e759bd9d..78ec2cb52e3 100644
--- a/cpp/ql/src/Critical/OverflowDestination.ql
+++ b/cpp/ql/src/Critical/OverflowDestination.ql
@@ -13,6 +13,13 @@
import cpp
import semmle.code.cpp.pointsto.PointsTo
+/**
+ * Holds if `fc` is a call to a copy operation where the size argument contains
+ * a reference to the source argument. For example:
+ * ```
+ * memcpy(dest, src, sizeof(src));
+ * ```
+ */
predicate sourceSized(FunctionCall fc)
{
exists(string name |
@@ -22,9 +29,13 @@ predicate sourceSized(FunctionCall fc)
exists(Expr dest, Expr src, Expr size, Variable v |
fc.getArgument(0) = dest and fc.getArgument(1) = src and fc.getArgument(2) = size and
src = v.getAnAccess() and size.getAChild+() = v.getAnAccess() and
+
+ // exception: `dest` is also referenced in the size argument
not exists(Variable other |
dest = other.getAnAccess() and size.getAChild+() = other.getAnAccess())
and
+
+ // exception: `src` and `dest` are both arrays of the same type and size
not exists(ArrayType srctype, ArrayType desttype |
dest.getType().getUnderlyingType() = desttype and
src.getType().getUnderlyingType() = srctype and
From e77f3eb5b809a34e2783692d42440143e305f4c9 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 16 Oct 2018 14:01:19 +0100
Subject: [PATCH 36/98] CPP: Simplify slightly.
---
cpp/ql/src/Critical/OverflowDestination.ql | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/cpp/ql/src/Critical/OverflowDestination.ql b/cpp/ql/src/Critical/OverflowDestination.ql
index 78ec2cb52e3..dcda2b05e18 100644
--- a/cpp/ql/src/Critical/OverflowDestination.ql
+++ b/cpp/ql/src/Critical/OverflowDestination.ql
@@ -20,13 +20,13 @@ import semmle.code.cpp.pointsto.PointsTo
* memcpy(dest, src, sizeof(src));
* ```
*/
-predicate sourceSized(FunctionCall fc)
+predicate sourceSized(FunctionCall fc, Expr src)
{
exists(string name |
(name = "strncpy" or name = "strncat" or name = "memcpy" or name = "memmove") and
fc.getTarget().hasQualifiedName(name))
and
- exists(Expr dest, Expr src, Expr size, Variable v |
+ exists(Expr dest, Expr size, Variable v |
fc.getArgument(0) = dest and fc.getArgument(1) = src and fc.getArgument(2) = size and
src = v.getAnAccess() and size.getAChild+() = v.getAnAccess() and
@@ -45,8 +45,8 @@ predicate sourceSized(FunctionCall fc)
class VulnerableArgument extends PointsToExpr
{
- VulnerableArgument() { sourceSized(this.getParent()) }
- override predicate interesting() { sourceSized(this.getParent()) }
+ VulnerableArgument() { sourceSized(_, this) }
+ override predicate interesting() { sourceSized(_, this) }
}
predicate taintingFunction(Function f, int buf)
@@ -83,8 +83,7 @@ class TaintedArgument extends PointsToExpr
}
from FunctionCall fc, VulnerableArgument vuln, TaintedArgument tainted
-where sourceSized(fc)
- and fc.getArgument(1) = vuln
+where sourceSized(fc, vuln)
and vuln.pointsTo() = tainted.pointsTo()
and vuln.confidence() > 0.01
select fc, "To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size."
From 99374301b81997b6d053059eb493f5f9d9e401c2 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 16 Oct 2018 14:13:19 +0100
Subject: [PATCH 37/98] CPP: Use taint library.
---
cpp/ql/src/Critical/OverflowDestination.ql | 38 ++--------------------
1 file changed, 3 insertions(+), 35 deletions(-)
diff --git a/cpp/ql/src/Critical/OverflowDestination.ql b/cpp/ql/src/Critical/OverflowDestination.ql
index dcda2b05e18..9e845882242 100644
--- a/cpp/ql/src/Critical/OverflowDestination.ql
+++ b/cpp/ql/src/Critical/OverflowDestination.ql
@@ -11,6 +11,7 @@
* external/cwe/cwe-131
*/
import cpp
+import semmle.code.cpp.security.TaintTracking
import semmle.code.cpp.pointsto.PointsTo
/**
@@ -49,41 +50,8 @@ class VulnerableArgument extends PointsToExpr
override predicate interesting() { sourceSized(_, this) }
}
-predicate taintingFunction(Function f, int buf)
-{
- (f.hasQualifiedName("read") and buf = 1) or
- (f.hasQualifiedName("fgets") and buf = 0) or
- (f.hasQualifiedName("fread") and buf = 0)
-}
-
-// Taint `argv[i]`, for all i, but also `*argv`, etc.
-predicate commandLineArg(Expr e)
-{
- exists(Function f, Parameter argv, VariableAccess access |
- f.hasQualifiedName("main") and f.getParameter(1) = argv and
- argv.getAnAccess() = access and access.isRValue() and
- pointer(access, e))
-}
-
-predicate tainted(Expr e)
-{
- exists(FunctionCall fc, int arg |
- taintingFunction(fc.getTarget(), arg) and
- e = fc.getArgument(arg))
- or
- e.(FunctionCall).getTarget().hasQualifiedName("getenv")
- or
- commandLineArg(e)
-}
-
-class TaintedArgument extends PointsToExpr
-{
- TaintedArgument() { tainted(this) }
- override predicate interesting() { tainted(this) }
-}
-
-from FunctionCall fc, VulnerableArgument vuln, TaintedArgument tainted
+from FunctionCall fc, VulnerableArgument vuln, Expr taintSource
where sourceSized(fc, vuln)
- and vuln.pointsTo() = tainted.pointsTo()
+ and tainted(taintSource, vuln.pointsTo())
and vuln.confidence() > 0.01
select fc, "To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size."
From 48c56cf7447204832baff08f6a60a6d022c30916 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 17 Oct 2018 10:24:56 +0100
Subject: [PATCH 38/98] CPP: Remove PointsTo.
---
cpp/ql/src/Critical/OverflowDestination.ql | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/cpp/ql/src/Critical/OverflowDestination.ql b/cpp/ql/src/Critical/OverflowDestination.ql
index 9e845882242..d0a677ed2ca 100644
--- a/cpp/ql/src/Critical/OverflowDestination.ql
+++ b/cpp/ql/src/Critical/OverflowDestination.ql
@@ -12,7 +12,6 @@
*/
import cpp
import semmle.code.cpp.security.TaintTracking
-import semmle.code.cpp.pointsto.PointsTo
/**
* Holds if `fc` is a call to a copy operation where the size argument contains
@@ -44,14 +43,7 @@ predicate sourceSized(FunctionCall fc, Expr src)
desttype.getArraySize() = srctype.getArraySize()))
}
-class VulnerableArgument extends PointsToExpr
-{
- VulnerableArgument() { sourceSized(_, this) }
- override predicate interesting() { sourceSized(_, this) }
-}
-
-from FunctionCall fc, VulnerableArgument vuln, Expr taintSource
+from FunctionCall fc, Expr vuln, Expr taintSource
where sourceSized(fc, vuln)
- and tainted(taintSource, vuln.pointsTo())
- and vuln.confidence() > 0.01
+ and tainted(taintSource, vuln)
select fc, "To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size."
From 757107660fb26e63a736f079eca35ab1485f257d Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 17 Oct 2018 11:47:35 +0100
Subject: [PATCH 39/98] CPP: Give the query a precision.
---
cpp/ql/src/Critical/OverflowDestination.ql | 1 +
1 file changed, 1 insertion(+)
diff --git a/cpp/ql/src/Critical/OverflowDestination.ql b/cpp/ql/src/Critical/OverflowDestination.ql
index d0a677ed2ca..9f03146e366 100644
--- a/cpp/ql/src/Critical/OverflowDestination.ql
+++ b/cpp/ql/src/Critical/OverflowDestination.ql
@@ -5,6 +5,7 @@
* @kind problem
* @id cpp/overflow-destination
* @problem.severity warning
+ * @precision low
* @tags reliability
* security
* external/cwe/cwe-119
From 3dc9071a4406908b957a358d7091513e40f69680 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Wed, 17 Oct 2018 15:59:52 +0200
Subject: [PATCH 40/98] Java: Add missing word in deprecation comments.
---
java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
index 9014b08650a..f5a28a509e5 100644
--- a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
@@ -1,5 +1,5 @@
/**
- * DEPRECATED: semmle.code.java.dataflow.ModulusAnalysis instead.
+ * DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead.
*
* Parity Analysis.
*
@@ -225,7 +225,7 @@ Parity getExprParity(Expr e) {
}
/**
- * DEPRECATED: semmle.code.java.dataflow.ModulusAnalysis instead.
+ * DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead.
*
* Holds if the parity can be determined for both sides of `comp`. The boolean
* `eqparity` indicates whether the two sides have equal or opposite parity.
From b8d7292b46bc5d0fb45ac5d43b40da144bfe52d8 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 17 Oct 2018 14:59:30 +0100
Subject: [PATCH 41/98] CPP: Speed up startsWithIfndef.
---
cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll b/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll
index d259d1ef805..299998647c1 100644
--- a/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll
+++ b/cpp/ql/src/semmle/code/cpp/headers/MultipleInclusion.qll
@@ -114,8 +114,11 @@ pragma[noopt] predicate correctIncludeGuard(HeaderFile hf, PreprocessorDirective
*/
predicate startsWithIfndef(HeaderFile hf, PreprocessorDirective ifndef, string macroName) {
ifndefDirective(ifndef, macroName) and
- ifndef.getFile() = hf and
- ifndef.getLocation().getStartLine() = min(int l | includeGuardRelevantLine(hf, l))
+ exists(Location loc |
+ loc = ifndef.getLocation() and
+ loc.getFile() = hf and
+ loc.getStartLine() = min(int l | includeGuardRelevantLine(hf, l))
+ )
}
private predicate endifLocation(PreprocessorEndif endif, File f, int line) {
From 17537bb88b579bc09d00255531125f67132815c1 Mon Sep 17 00:00:00 2001
From: Robert Marsh
Date: Wed, 17 Oct 2018 11:57:54 -0700
Subject: [PATCH 42/98] C++: respond to doc comments
---
change-notes/1.19/analysis-cpp.md | 6 +++---
cpp/ql/src/Critical/DeadCodeGoto.qhelp | 12 ++++++------
cpp/ql/src/Critical/DeadCodeGoto.ql | 2 +-
.../Critical/DeadCodeGoto/DeadCodeGoto.expected | 6 +++---
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md
index e219f40fc27..bdcb02a9f09 100644
--- a/change-notes/1.19/analysis-cpp.md
+++ b/change-notes/1.19/analysis-cpp.md
@@ -6,10 +6,10 @@
| **Query** | **Tags** | **Purpose** |
|-----------------------------|-----------|--------------------------------------------------------------------|
-| Cast between HRESULT and a Boolean type (`cpp/hresult-boolean-conversion`) | external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. |
+| Cast between `HRESULT` and a Boolean type (`cpp/hresult-boolean-conversion`) | external/cwe/cwe-253 | Finds logic errors caused by mistakenly treating the Windows `HRESULT` type as a Boolean instead of testing it with the appropriate macros. Enabled by default. |
| Setting a DACL to `NULL` in a `SECURITY_DESCRIPTOR` (`cpp/unsafe-dacl-security-descriptor`) | external/cwe/cwe-732 | This query finds code that creates world-writable objects on Windows by setting their DACL to `NULL`. Enabled by default. |
-| Cast from char* to wchar_t* | security, external/cwe/cwe-704 | Detects potentially dangerous casts from char* to wchar_t*. Enabled by default on LGTM. |
-| Dead code due to goto or break statement (`cpp/dead-code-goto`) | maintainability, external/cwe/cwe-561 | Detects dead code following a goto or break statement. Enabled by default on LGTM. |
+| Cast from `char*` to `wchar_t*` | security, external/cwe/cwe-704 | Detects potentially dangerous casts from `char*` to `wchar_t*`. Enabled by default on LGTM. |
+| Dead code due to `goto` or `break` statement (`cpp/dead-code-goto`) | maintainability, external/cwe/cwe-561 | Detects dead code following a goto or break statement. Enabled by default on LGTM. |
## Changes to existing queries
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.qhelp b/cpp/ql/src/Critical/DeadCodeGoto.qhelp
index 25a5414f43a..8b0dacae65b 100644
--- a/cpp/ql/src/Critical/DeadCodeGoto.qhelp
+++ b/cpp/ql/src/Critical/DeadCodeGoto.qhelp
@@ -6,15 +6,15 @@
-Code following a goto or break statement will not be executed, unless there is a label or switch
-case. When the code is necessary, this leads to logical errors or resource leaks. If the code is
-unnecessary, it may confuse readers.
+Code immediately following a goto or break statement will not be executed,
+unless there is a label or switch case. When the code is necessary, this leads to logical errors or
+resource leaks. If the code is unnecessary, it may confuse readers.
-If the unreachable code is necessary, move the goto or break statement to after the code.
-Otherwise, delete the unreachable code.
+If the unreachable code is necessary, move the goto or break statement to
+after the code. Otherwise, delete the unreachable code.
@@ -22,7 +22,7 @@ Otherwise, delete the unreachable code.
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.ql b/cpp/ql/src/Critical/DeadCodeGoto.ql
index 0fd4be5ccb0..d6f619f80e8 100644
--- a/cpp/ql/src/Critical/DeadCodeGoto.ql
+++ b/cpp/ql/src/Critical/DeadCodeGoto.ql
@@ -29,4 +29,4 @@ where b.getStmt(i) = js
and not s.(BreakStmt).getBreakable() instanceof SwitchStmt
// the jump isn't a goto into the body of the next statement
and not exists (LabelStmt ls | s.(Loop).getStmt().getAChild*() = ls | ls.getName() = js.(GotoStmt).getName())
-select js, "This statement makes $@ dead.", s, s.toString()
+select js, "This statement makes $@ unreachable.", s, s.toString()
diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected
index ca69006645e..5c239ca92ad 100644
--- a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected
+++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/DeadCodeGoto.expected
@@ -1,3 +1,3 @@
-| test.cpp:2:2:2:12 | goto ... | This statement makes $@ dead. | test.cpp:3:2:3:5 | ExprStmt | ExprStmt |
-| test.cpp:9:3:9:8 | break; | This statement makes $@ dead. | test.cpp:10:3:10:6 | ExprStmt | ExprStmt |
-| test.cpp:37:3:37:8 | break; | This statement makes $@ dead. | test.cpp:38:3:38:11 | return ... | return ... |
+| test.cpp:2:2:2:12 | goto ... | This statement makes $@ unreachable. | test.cpp:3:2:3:5 | ExprStmt | ExprStmt |
+| test.cpp:9:3:9:8 | break; | This statement makes $@ unreachable. | test.cpp:10:3:10:6 | ExprStmt | ExprStmt |
+| test.cpp:37:3:37:8 | break; | This statement makes $@ unreachable. | test.cpp:38:3:38:11 | return ... | return ... |
From b40219bb01a62bb9d82676e14e8561e5c868175d Mon Sep 17 00:00:00 2001
From: Robert Marsh
Date: Wed, 17 Oct 2018 11:58:51 -0700
Subject: [PATCH 43/98] C++: add good example for DeadCodeGoto
---
cpp/ql/src/Critical/DeadCodeGoto.cpp | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.cpp b/cpp/ql/src/Critical/DeadCodeGoto.cpp
index 8ac0cc703b0..3dd2be64e0e 100644
--- a/cpp/ql/src/Critical/DeadCodeGoto.cpp
+++ b/cpp/ql/src/Critical/DeadCodeGoto.cpp
@@ -1,3 +1,7 @@
-goto err;
-free(pointer);
-err: return -1;
+goto err1;
+free(pointer); // BAD: this line is unreachable
+err1: return -1;
+
+free(pointer); // GOOD: this line is reachable
+goto err2;
+err2: return -1;
From 739804acb218010820d57303b3ae5af4ef91f836 Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Wed, 17 Oct 2018 16:24:34 -0700
Subject: [PATCH 44/98] CPP : Ill-defined for-loop (C6293) Superset of C6293,
it looks for a mismatch between the initialization statement && condition and
the direction of the iteration expression in a for loop.
---
.gitignore | 3 +
.../Likely Typos/illDefinedForLoop.c | 7 +
.../Likely Typos/illDefinedForLoop.qhelp | 27 ++++
.../Likely Typos/illDefinedForLoop.ql | 102 ++++++++++++++
.../illDefinedForLoop/illDefinedForLoop.c | 89 ++++++++++++
.../illDefinedForLoop/illDefinedForLoop.cpp | 128 ++++++++++++++++++
.../illDefinedForLoop.expected | 16 +++
.../illDefinedForLoop/illDefinedForLoop.qlref | 1 +
8 files changed, 373 insertions(+)
create mode 100644 cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.c
create mode 100644 cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
create mode 100644 cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.c
create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref
diff --git a/.gitignore b/.gitignore
index 7e82b2f488c..db96e43670f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,6 @@
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
+/.vs/slnx.sqlite-journal
+/.vs/ql_6293a/v15/Browse.VC.opendb
+/.vs/ql_6293a/v15/Browse.VC.db
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.c b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.c
new file mode 100644
index 00000000000..e68abdc2203
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.c
@@ -0,0 +1,7 @@
+void f()
+{
+ for (signed char i = 0; i < 100; i--)
+ {
+ // code ...
+ }
+}
\ No newline at end of file
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
new file mode 100644
index 00000000000..8e99fc88660
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
@@ -0,0 +1,27 @@
+
+
+
+
+
A for-loop iteration expression goes backwards with respect of the initialization statement and condition expression.
+
This warning indicates that a for-loop might not function as intended.
+
+
+
+
Verify the iteration expression on the for-loop and make sure the direction of the iteration expression is correct.
+
+
+
+
In the following example, the initialization statement (i = 0) and the condition expression (i < 100) indicate that the intended iteration expression should have been incrementing, but instead a postfix decrement operator is used (i--).
+
+
+
To fix this issue, change the iteration expression to match the direction of the initialization statement and the condition expression: i++.
+
+
+
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
new file mode 100644
index 00000000000..06df0141d27
--- /dev/null
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
@@ -0,0 +1,102 @@
+/**
+ * @name Ill-defined for loop
+ * @description A for-loop iteration expressions goes backward with respect of the initialization statement and condition expression.
+ * @id cpp/ill-defined-for-loop
+ * @kind problem
+ * @problem.severity warning
+ * @precision high
+ * @tags external/microsoft/6293
+ * @msrc.severity important
+ */
+import cpp
+import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+
+predicate illDefinedDecrForStmt( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition ) {
+ v.getAnAssignedValue() = initialCondition
+ and
+ exists(
+ RelationalOperation rel, Expr e |
+ rel = forstmt.getCondition() |
+ e = rel.getGreaterOperand()
+ and v.getAnAccess() = rel.getLesserOperand()
+ and terminalCondition = e
+ )
+ and
+ (
+ exists(
+ PostfixDecrExpr pdec |
+ pdec = forstmt.getUpdate().(PostfixDecrExpr)
+ and pdec.getAnOperand() = v.getAnAccess()
+ ) or
+ exists(
+ PrefixDecrExpr pdec |
+ pdec = forstmt.getUpdate().(PrefixDecrExpr)
+ and pdec.getAnOperand() = v.getAnAccess()
+ )
+ )
+ and
+ upperBound(initialCondition) < lowerBound(terminalCondition)
+}
+
+predicate illDefinedIncrForStmt( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition ) {
+ v.getAnAssignedValue() = initialCondition
+ and
+ exists(
+ RelationalOperation rel, Expr e |
+ rel = forstmt.getCondition() |
+ e = rel.getLesserOperand()
+ and v.getAnAccess() = rel.getGreaterOperand()
+ and terminalCondition = e
+ )
+ and
+ ( exists( PostfixIncrExpr pincr |
+ pincr = forstmt.getUpdate().(PostfixIncrExpr)
+ and
+ pincr.getAnOperand() = v.getAnAccess()
+ ) or
+ exists( PrefixIncrExpr pincr |
+ pincr = forstmt.getUpdate().(PrefixIncrExpr)
+ and
+ pincr.getAnOperand() = v.getAnAccess()
+ )
+ )
+ and
+ upperBound(terminalCondition) < lowerBound(initialCondition)
+}
+
+predicate illDefinedForStmtWrongDirection( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition
+ , boolean isIncr ) {
+ ( illDefinedDecrForStmt( forstmt, v, initialCondition, terminalCondition) and isIncr = false )
+ or
+ ( illDefinedIncrForStmt( forstmt, v, initialCondition, terminalCondition) and isIncr = true)
+}
+
+bindingset[b]
+private string forLoopdirection(boolean b){
+ if( b = true ) then result = "upward"
+ else result = "downward"
+}
+
+bindingset[b]
+private string forLoopTerminalConditionRelationship(boolean b){
+ if( b = true ) then result = "lower"
+ else result = "higher"
+}
+
+ predicate illDefinedForStmt( ForStmt for, string message ) {
+ exists(
+ boolean isIncr,
+ Variable v,
+ Expr initialCondition,
+ Expr terminalCondition |
+ illDefinedForStmtWrongDirection(for, v, initialCondition, terminalCondition, isIncr)
+ and
+ message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts "
+ + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is "
+ + forLoopTerminalConditionRelationship(isIncr) + " (" + terminalCondition + ")."
+ )
+}
+
+from ForStmt forstmt, string message
+ where illDefinedForStmt(forstmt, message)
+select forstmt, message
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.c b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.c
new file mode 100644
index 00000000000..d66e027bdc1
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.c
@@ -0,0 +1,89 @@
+void Signed()
+{
+ signed char i;
+
+ for (i = 0; i < 100; i--) //BUG
+ {
+ }
+
+ for (i = 0; i < 100; i++)
+ {
+ }
+
+ for (i = 100; i >= 0; i++) //BUG
+ {
+ }
+
+ for (i = 100; i >= 0; i--)
+ {
+ }
+
+}
+
+void Unsigned()
+{
+ unsigned long i;
+
+ for (i = 0; i < 100; i--) //BUG
+ {
+ }
+
+ for (i = 0; i < 100; i++)
+ {
+ }
+
+ for (i = 100; i >= 0; i++) //BUG
+ {
+ }
+
+ for (i = 100; i >= 0; i--)
+ {
+ }
+}
+
+void InitializationOutsideLoop()
+{
+ signed char i = 0;
+
+ for (; i < 100; i--) //BUG
+ {
+ }
+
+ i = 0;
+ for (; i < 100; i++)
+ {
+ }
+
+ i = 100;
+ for (; i >= 0; i++) //BUG
+ {
+ }
+
+ i = 100;
+ for (; i >= 0; i--)
+ {
+ }
+}
+
+void NegativeTestCase()
+{
+ int i;
+ for (i = 0; (200 - i) < 100; i--)
+ {
+ // code ...
+ }
+}
+
+void NegativeTestCaseNested()
+{
+ int k;
+ int i;
+
+ for (k = 200; k < 300; k++)
+ {
+ for (i = 0; (k - i) < 100; i--)
+ {
+ // code ...
+ }
+ }
+}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
new file mode 100644
index 00000000000..db6fc509055
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
@@ -0,0 +1,128 @@
+void Signed()
+{
+ signed char i;
+
+ for (i = 0; i < 100; i--) //BUG
+ {
+ }
+
+ for (i = 0; i < 100; i++)
+ {
+ }
+
+ for (i = 100; i >= 0; i++) //BUG
+ {
+ }
+
+ for (i = 100; i >= 0; i--)
+ {
+ }
+
+}
+
+void Unsigned()
+{
+ unsigned long i;
+
+ for (i = 0; i < 100; i--) //BUG
+ {
+ }
+
+ for (i = 0; i < 100; i++)
+ {
+ }
+
+ for (i = 100; i >= 0; i++) //BUG
+ {
+ }
+
+ for (i = 100; i >= 0; i--)
+ {
+ }
+}
+
+void DeclarationInLoop()
+{
+ for (signed char i = 0; i < 100; i--) //BUG
+ {
+ }
+
+ for (signed char i = 0; i < 100; i++)
+ {
+ }
+
+ for (unsigned char i = 100; i >= 0; i++) //BUG
+ {
+ }
+
+ for (unsigned char i = 100; i >= 0; i--)
+ {
+ }
+}
+
+void SignedWithVariables()
+{
+ signed char i;
+ signed char min = 0;
+ signed char max = 100;
+
+ for (i = min; i < max; i--) //BUG
+ {
+ }
+
+ for (i = min; i < max; i++)
+ {
+ }
+
+ for (i = max; i >= min; i++) //BUG
+ {
+ }
+
+ for (i = max; i >= min; i--)
+ {
+ }
+
+}
+
+void InitializationOutsideLoop()
+{
+ signed char i = 0;
+
+ for (; i < 100; i--) //BUG
+ {
+ }
+
+ i = 0;
+ for (; i < 100; i++)
+ {
+ }
+
+ i = 100;
+ for (; i >= 0; i++) //BUG
+ {
+ }
+
+ i = 100;
+ for (; i >= 0; i--)
+ {
+ }
+}
+
+void NegativeTestCase()
+{
+ for (int i = 0; (200 - i) < 100; i--)
+ {
+ // code ...
+ }
+}
+
+void NegativeTestCaseNested()
+{
+ for (int k = 200; k < 300; k++)
+ {
+ for (int i = 0; (k - i) < 100; i--)
+ {
+ // code ...
+ }
+ }
+}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
new file mode 100644
index 00000000000..2079abcbe2f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
@@ -0,0 +1,16 @@
+| illDefinedForLoop.c:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.c:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.c:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.c:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.c:48:5:50:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.c:58:5:60:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.cpp:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.cpp:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.cpp:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.cpp:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.cpp:46:5:48:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.cpp:54:5:56:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.cpp:69:5:71:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (min), but the terminal condition is higher (max). |
+| illDefinedForLoop.cpp:77:5:79:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (max), but the terminal condition is lower (min). |
+| illDefinedForLoop.cpp:91:5:93:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| illDefinedForLoop.cpp:101:5:103:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref
new file mode 100644
index 00000000000..cc5ed35c3b4
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref
@@ -0,0 +1 @@
+Likely Bugs/Likely Typos/illDefinedForLoop.ql
\ No newline at end of file
From f99756c07f2362967b326b1949d310007b160715 Mon Sep 17 00:00:00 2001
From: Raul Garcia <42392023+raulgarciamsft@users.noreply.github.com>
Date: Wed, 17 Oct 2018 16:27:42 -0700
Subject: [PATCH 45/98] Update .gitignore
---
.gitignore | 4 ----
1 file changed, 4 deletions(-)
diff --git a/.gitignore b/.gitignore
index db96e43670f..4b055e55a09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,7 +12,3 @@
/.vs/ql/v15/Browse.VC.opendb
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
-
-/.vs/slnx.sqlite-journal
-/.vs/ql_6293a/v15/Browse.VC.opendb
-/.vs/ql_6293a/v15/Browse.VC.db
From 8eaba03506492d1546950fa417c73008c5cb1735 Mon Sep 17 00:00:00 2001
From: Raul Garcia <42392023+raulgarciamsft@users.noreply.github.com>
Date: Wed, 17 Oct 2018 16:28:01 -0700
Subject: [PATCH 46/98] Update .gitignore
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 4b055e55a09..7e82b2f488c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@
/.vs/ql/v15/Browse.VC.opendb
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
+
From 0c37ea876d61b4c745ce491ebe0df0f597542379 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Mon, 15 Oct 2018 12:37:29 +0200
Subject: [PATCH 47/98] Java: Fix FPs for concurrent modification checks.
---
change-notes/1.19/analysis-java.md | 1 +
.../Comparison/UselessComparisonTest.ql | 15 ++++++++++++---
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/change-notes/1.19/analysis-java.md b/change-notes/1.19/analysis-java.md
index 3d5e09b5746..f2d0d5adf97 100644
--- a/change-notes/1.19/analysis-java.md
+++ b/change-notes/1.19/analysis-java.md
@@ -13,6 +13,7 @@
|----------------------------|------------------------|------------------------------------------------------------------|
| Array index out of bounds (`java/index-out-of-bounds`) | Fewer false positive results | False positives involving arrays with a length evenly divisible by 3 or some greater number and an index being increased with a similar stride length are no longer reported. |
| Unreachable catch clause (`java/unreachable-catch-clause`) | Fewer false positive results | This rule now accounts for calls to generic methods that throw generic exceptions. |
+| Useless comparison test (`java/constant-comparison`) | Fewer false positive results | Constant comparisons guarding `java.util.ConcurrentModificationException` are no longer reported, as they are intended to always be false in the absence of API misuse. |
## Changes to QL libraries
diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql
index 818ba676241..d6c1e205668 100644
--- a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql
+++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql
@@ -167,6 +167,15 @@ predicate overFlowTest(ComparisonExpr comp) {
comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0
}
+predicate concurrentModificationTest(BinaryExpr test) {
+ exists(IfStmt ifstmt, ThrowStmt throw, RefType exc |
+ ifstmt.getCondition() = test and
+ (ifstmt.getThen() = throw or ifstmt.getThen().(SingletonBlock).getStmt() = throw) and
+ throw.getExpr().(ClassInstanceExpr).getConstructedType() = exc and
+ exc.hasQualifiedName("java.util", "ConcurrentModificationException")
+ )
+}
+
/**
* Holds if `test` and `guard` are equality tests of the same integral variable v with constants `c1` and `c2`.
*/
@@ -202,13 +211,13 @@ where
)
else
if constCondSimple(test, _)
- then (
- constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test // dummy reason element
- ) else
+ then constCondSimple(test, testIsTrue) and reason = "" and reasonElem = test // dummy reason element
+ else
exists(CondReason r |
constCond(test, testIsTrue, r) and reason = ", because of $@" and reasonElem = r.getCond()
)
) and
not overFlowTest(test) and
+ not concurrentModificationTest(test) and
not exists(AssertStmt assert | assert.getExpr() = test.getParent*())
select test, "Test is always " + testIsTrue + reason + ".", reasonElem, "this condition"
From 187918396cda3a7199d0e8227d4f2444ab9b19e8 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Thu, 18 Oct 2018 10:03:08 +0200
Subject: [PATCH 48/98] Java: Autoformat the last 5 files (RangeAnalysis).
---
.../src/semmle/code/java/dataflow/Bound.qll | 18 +-
.../code/java/dataflow/ModulusAnalysis.qll | 100 ++--
.../code/java/dataflow/ParityAnalysis.qll | 305 ++++++----
.../code/java/dataflow/RangeAnalysis.qll | 526 ++++++++++++------
.../semmle/code/java/dataflow/RangeUtils.qll | 103 ++--
5 files changed, 710 insertions(+), 342 deletions(-)
diff --git a/java/ql/src/semmle/code/java/dataflow/Bound.qll b/java/ql/src/semmle/code/java/dataflow/Bound.qll
index f607444908a..1742e1dc4fe 100644
--- a/java/ql/src/semmle/code/java/dataflow/Bound.qll
+++ b/java/ql/src/semmle/code/java/dataflow/Bound.qll
@@ -5,19 +5,23 @@ private import RangeUtils
private newtype TBound =
TBoundZero() or
TBoundSsa(SsaVariable v) { v.getSourceVariable().getType() instanceof IntegralType } or
- TBoundExpr(Expr e) { e.(FieldRead).getField() instanceof ArrayLengthField and not exists(SsaVariable v | e = v.getAUse()) }
+ TBoundExpr(Expr e) {
+ e.(FieldRead).getField() instanceof ArrayLengthField and
+ not exists(SsaVariable v | e = v.getAUse())
+ }
/**
* A bound that may be inferred for an expression plus/minus an integer delta.
*/
abstract class Bound extends TBound {
abstract string toString();
+
/** Gets an expression that equals this bound plus `delta`. */
abstract Expr getExpr(int delta);
+
/** Gets an expression that equals this bound. */
- Expr getExpr() {
- result = getExpr(0)
- }
+ Expr getExpr() { result = getExpr(0) }
+
predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}
@@ -29,6 +33,7 @@ abstract class Bound extends TBound {
*/
class ZeroBound extends Bound, TBoundZero {
override string toString() { result = "0" }
+
override Expr getExpr(int delta) { result.(ConstantIntegerExpr).getIntValue() = delta }
}
@@ -38,8 +43,11 @@ class ZeroBound extends Bound, TBoundZero {
class SsaBound extends Bound, TBoundSsa {
/** Gets the SSA variable that equals this bound. */
SsaVariable getSsa() { this = TBoundSsa(result) }
+
override string toString() { result = getSsa().toString() }
+
override Expr getExpr(int delta) { result = getSsa().getAUse() and delta = 0 }
+
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getSsa().getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
@@ -51,7 +59,9 @@ class SsaBound extends Bound, TBoundSsa {
*/
class ExprBound extends Bound, TBoundExpr {
override string toString() { result = getExpr().toString() }
+
override Expr getExpr(int delta) { this = TBoundExpr(result) and delta = 0 }
+
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
getExpr().hasLocationInfo(path, sl, sc, el, ec)
}
diff --git a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll
index 32133d785ff..e0e3b7ea4ba 100644
--- a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll
@@ -32,7 +32,8 @@ private predicate nonConstAddition(Expr add, Expr larg, Expr rarg) {
exists(AddExpr a | a = add |
larg = a.getLeftOperand() and
rarg = a.getRightOperand()
- ) or
+ )
+ or
exists(AssignAddExpr a | a = add |
larg = a.getDest() and
rarg = a.getRhs()
@@ -51,7 +52,8 @@ private predicate nonConstSubtraction(Expr sub, Expr larg, Expr rarg) {
exists(SubExpr s | s = sub |
larg = s.getLeftOperand() and
rarg = s.getRightOperand()
- ) or
+ )
+ or
exists(AssignSubExpr s | s = sub |
larg = s.getDest() and
rarg = s.getRhs()
@@ -67,12 +69,14 @@ private Expr modExpr(Expr arg, int mod) {
arg = rem.getLeftOperand() and
rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = mod and
mod >= 2
- ) or
+ )
+ or
exists(CompileTimeConstantExpr c |
- mod = 2.pow([1..30]) and
+ mod = 2.pow([1 .. 30]) and
c.getIntValue() = mod - 1 and
result.(AndBitwiseExpr).hasOperands(arg, c)
- ) or
+ )
+ or
result.(ParExpr).getExpr() = modExpr(arg, mod)
}
@@ -88,7 +92,9 @@ private Guard moduloCheck(SsaVariable v, int val, int mod, boolean testIsTrue) {
(
testIsTrue = polarity and val = r
or
- testIsTrue = polarity.booleanNot() and mod = 2 and val = 1 - r and
+ testIsTrue = polarity.booleanNot() and
+ mod = 2 and
+ val = 1 - r and
(r = 0 or r = 1)
)
)
@@ -109,17 +115,22 @@ private predicate moduloGuardedRead(SsaVariable v, SsaReadPosition pos, int val,
bindingset[mask]
private predicate andmaskFactor(int mask, int factor) {
mask % factor = 0 and
- factor = 2.pow([1..30])
+ factor = 2.pow([1 .. 30])
}
/** Holds if `e` is evenly divisible by `factor`. */
private predicate evenlyDivisibleExpr(Expr e, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() |
- e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2 or
- e.(AssignMulExpr).getSource() = c and factor = k.abs() and factor >= 2 or
- e.(LShiftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0 or
- e.(AssignLShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0 or
- e.(AndBitwiseExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f)) or
+ e.(MulExpr).getAnOperand() = c and factor = k.abs() and factor >= 2
+ or
+ e.(AssignMulExpr).getSource() = c and factor = k.abs() and factor >= 2
+ or
+ e.(LShiftExpr).getRightOperand() = c and factor = 2.pow(k) and k > 0
+ or
+ e.(AssignLShiftExpr).getRhs() = c and factor = 2.pow(k) and k > 0
+ or
+ e.(AndBitwiseExpr).getAnOperand() = c and factor = max(int f | andmaskFactor(k, f))
+ or
e.(AssignAndExpr).getSource() = c and factor = max(int f | andmaskFactor(k, f))
)
}
@@ -134,9 +145,17 @@ private int getId(BasicBlock bb) { idOf(bb, result) }
* Holds if `inp` is an input to `phi` along `edge` and this input has index `r`
* in an arbitrary 1-based numbering of the input edges to `phi`.
*/
-private predicate rankedPhiInput(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int r) {
+private predicate rankedPhiInput(
+ SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int r
+) {
edge.phiInput(phi, inp) and
- edge = rank[r](SsaReadPositionPhiInputEdge e | e.phiInput(phi, _) | e order by getId(e.getOrigBlock()))
+ edge = rank[r](SsaReadPositionPhiInputEdge e |
+ e.phiInput(phi, _)
+ |
+ e
+ order by
+ getId(e.getOrigBlock())
+ )
}
/**
@@ -154,9 +173,11 @@ private int gcdLim() { result = 128 }
*/
private int gcd(int x, int y) {
result != 1 and
- result = x.abs() and y = 0 and x in [-gcdLim()..gcdLim()]
+ result = x.abs() and
+ y = 0 and
+ x in [-gcdLim() .. gcdLim()]
or
- result = gcd(y, x % y) and y != 0 and x in [-gcdLim()..gcdLim()]
+ result = gcd(y, x % y) and y != 0 and x in [-gcdLim() .. gcdLim()]
}
/**
@@ -167,14 +188,17 @@ private int gcd(int x, int y) {
*/
bindingset[val, mod]
private int remainder(int val, int mod) {
- mod = 0 and result = val or
+ mod = 0 and result = val
+ or
mod > 1 and result = ((val % mod) + mod) % mod
}
/**
* Holds if `inp` is an input to `phi` and equals `phi` modulo `mod` along `edge`.
*/
-private predicate phiSelfModulus(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod) {
+private predicate phiSelfModulus(
+ SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int mod
+) {
exists(SsaBound phibound, int v, int m |
edge.phiInput(phi, inp) and
phibound.getSsa() = phi and
@@ -204,7 +228,7 @@ private predicate phiModulusRankStep(SsaPhiNode phi, Bound b, int val, int mod,
exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge, int v1, int m1 |
mod != 1 and
val = remainder(v1, mod)
- |
+ |
exists(int v2, int m2 |
rankedPhiInput(phi, inp, edge, rix) and
phiModulusRankStep(phi, b, v1, m1, rix - 1) and
@@ -257,18 +281,22 @@ private predicate ssaModulus(SsaVariable v, SsaReadPosition pos, Bound b, int va
*/
cached
predicate exprModulus(Expr e, Bound b, int val, int mod) {
- e = b.getExpr(val) and mod = 0 or
- evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound or
+ e = b.getExpr(val) and mod = 0
+ or
+ evenlyDivisibleExpr(e, mod) and val = 0 and b instanceof ZeroBound
+ or
exists(SsaVariable v, SsaReadPositionBlock bb |
ssaModulus(v, bb, b, val, mod) and
e = v.getAUse() and
bb.getBlock() = e.getBasicBlock()
- ) or
+ )
+ or
exists(Expr mid, int val0, int delta |
exprModulus(mid, b, val0, mod) and
valueFlowStep(e, mid, delta) and
val = remainder(val0 + delta, mod)
- ) or
+ )
+ or
exists(ConditionalExpr cond, int v1, int v2, int m1, int m2 |
cond = e and
condExprBranchModulus(cond, true, b, v1, m1) and
@@ -276,17 +304,20 @@ predicate exprModulus(Expr e, Bound b, int val, int mod) {
mod = gcd(gcd(m1, m2), v1 - v2) and
mod != 1 and
val = remainder(v1, mod)
- ) or
+ )
+ or
exists(Bound b1, Bound b2, int v1, int v2, int m1, int m2 |
addModulus(e, true, b1, v1, m1) and
addModulus(e, false, b2, v2, m2) and
mod = gcd(m1, m2) and
mod != 1 and
val = remainder(v1 + v2, mod)
- |
- b = b1 and b2 instanceof ZeroBound or
+ |
+ b = b1 and b2 instanceof ZeroBound
+ or
b = b2 and b1 instanceof ZeroBound
- ) or
+ )
+ or
exists(int v1, int v2, int m1, int m2 |
subModulus(e, true, b, v1, m1) and
subModulus(e, false, any(ZeroBound zb), v2, m2) and
@@ -296,15 +327,16 @@ predicate exprModulus(Expr e, Bound b, int val, int mod) {
)
}
-private predicate condExprBranchModulus(ConditionalExpr cond, boolean branch, Bound b, int val, int mod) {
- exprModulus(cond.getTrueExpr(), b, val, mod) and branch = true or
+private predicate condExprBranchModulus(
+ ConditionalExpr cond, boolean branch, Bound b, int val, int mod
+) {
+ exprModulus(cond.getTrueExpr(), b, val, mod) and branch = true
+ or
exprModulus(cond.getFalseExpr(), b, val, mod) and branch = false
}
private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod) {
- exists(Expr larg, Expr rarg |
- nonConstAddition(add, larg, rarg)
- |
+ exists(Expr larg, Expr rarg | nonConstAddition(add, larg, rarg) |
exprModulus(larg, b, val, mod) and isLeft = true
or
exprModulus(rarg, b, val, mod) and isLeft = false
@@ -312,9 +344,7 @@ private predicate addModulus(Expr add, boolean isLeft, Bound b, int val, int mod
}
private predicate subModulus(Expr sub, boolean isLeft, Bound b, int val, int mod) {
- exists(Expr larg, Expr rarg |
- nonConstSubtraction(sub, larg, rarg)
- |
+ exists(Expr larg, Expr rarg | nonConstSubtraction(sub, larg, rarg) |
exprModulus(larg, b, val, mod) and isLeft = true
or
exprModulus(rarg, b, val, mod) and isLeft = false
diff --git a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
index f5a28a509e5..31b7766f0bd 100644
--- a/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/ParityAnalysis.qll
@@ -6,6 +6,7 @@
* The analysis is implemented as an abstract interpretation over the
* two-valued domain `{even, odd}`.
*/
+
import java
private import SSA
private import RangeUtils
@@ -14,38 +15,35 @@ private import SignAnalysis
private import semmle.code.java.Reflection
/** Gets an expression that is the remainder modulo 2 of `arg`. */
-deprecated
-private Expr mod2(Expr arg) {
+deprecated private Expr mod2(Expr arg) {
exists(RemExpr rem |
result = rem and
arg = rem.getLeftOperand() and
rem.getRightOperand().(CompileTimeConstantExpr).getIntValue() = 2
- ) or
- result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1)) or
+ )
+ or
+ result.(AndBitwiseExpr).hasOperands(arg, any(CompileTimeConstantExpr c | c.getIntValue() = 1))
+ or
result.(ParExpr).getExpr() = mod2(arg)
}
/** An expression that calculates remainder modulo 2. */
-deprecated
-private class Mod2 extends Expr {
- Mod2() {
- this = mod2(_)
- }
+deprecated private class Mod2 extends Expr {
+ Mod2() { this = mod2(_) }
/** Gets the argument of this remainder operation. */
- Expr getArg() {
- this = mod2(result)
- }
+ Expr getArg() { this = mod2(result) }
}
/**
* Parity represented as booleans. Even corresponds to `false` and odd
* corresponds to `true`.
*/
-deprecated
-class Parity extends boolean {
+deprecated class Parity extends boolean {
Parity() { this = true or this = false }
+
predicate isEven() { this = false }
+
predicate isOdd() { this = true }
}
@@ -53,8 +51,7 @@ class Parity extends boolean {
* Gets a condition that performs a parity check on `v`, such that `v` has
* the given parity if the condition evaluates to `testIsTrue`.
*/
-deprecated
-private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
+deprecated private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
exists(Mod2 rem, CompileTimeConstantExpr c, int r, boolean polarity |
result.isEquality(rem, c, polarity) and
c.getIntValue() = r and
@@ -62,7 +59,8 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
rem.getArg() = v.getAUse() and
(testIsTrue = true or testIsTrue = false) and
(
- r = 0 and parity = testIsTrue.booleanXor(polarity) or
+ r = 0 and parity = testIsTrue.booleanXor(polarity)
+ or
r = 1 and parity = testIsTrue.booleanXor(polarity).booleanNot()
)
)
@@ -71,26 +69,36 @@ private Guard parityCheck(SsaVariable v, Parity parity, boolean testIsTrue) {
/**
* Gets the parity of `e` if it can be directly determined.
*/
-deprecated
-private Parity certainExprParity(Expr e) {
+deprecated private Parity certainExprParity(Expr e) {
exists(int i | e.(ConstantIntegerExpr).getIntValue() = i |
if i % 2 = 0 then result.isEven() else result.isOdd()
- ) or
- e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven() or
- e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd() or
+ )
+ or
+ e.(LongLiteral).getValue().regexpMatch(".*[02468]") and result.isEven()
+ or
+ e.(LongLiteral).getValue().regexpMatch(".*[13579]") and result.isOdd()
+ or
not exists(e.(ConstantIntegerExpr).getIntValue()) and
(
- result = certainExprParity(e.(ParExpr).getExpr()) or
+ result = certainExprParity(e.(ParExpr).getExpr())
+ or
exists(Guard guard, SsaVariable v, boolean testIsTrue |
guard = parityCheck(v, result, testIsTrue) and
e = v.getAUse() and
guardControls_v2(guard, e.getBasicBlock(), testIsTrue)
- ) or
+ )
+ or
exists(SsaVariable arr, int arrlen, FieldAccess len |
e = len and
len.getField() instanceof ArrayLengthField and
len.getQualifier() = arr.getAUse() and
- arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getFirstDimensionSize() = arrlen and
+ arr
+ .(SsaExplicitUpdate)
+ .getDefiningExpr()
+ .(VariableAssign)
+ .getSource()
+ .(ArrayCreationExpr)
+ .getFirstDimensionSize() = arrlen and
if arrlen % 2 = 0 then result.isEven() else result.isOdd()
)
)
@@ -99,119 +107,211 @@ private Parity certainExprParity(Expr e) {
/**
* Gets the expression that defines the array length that equals `len`, if any.
*/
-deprecated
-private Expr arrLenDef(FieldAccess len) {
+deprecated private Expr arrLenDef(FieldAccess len) {
exists(SsaVariable arr |
len.getField() instanceof ArrayLengthField and
len.getQualifier() = arr.getAUse() and
- arr.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = result
+ arr
+ .(SsaExplicitUpdate)
+ .getDefiningExpr()
+ .(VariableAssign)
+ .getSource()
+ .(ArrayCreationExpr)
+ .getDimension(0) = result
)
}
/** Gets a possible parity for `v`. */
-deprecated
-private Parity ssaParity(SsaVariable v) {
+deprecated private Parity ssaParity(SsaVariable v) {
exists(VariableUpdate def | def = v.(SsaExplicitUpdate).getDefiningExpr() |
- result = exprParity(def.(VariableAssign).getSource()) or
- exists(EnhancedForStmt for | def = for.getVariable()) and (result = true or result = false) or
- result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot() or
+ result = exprParity(def.(VariableAssign).getSource())
+ or
+ exists(EnhancedForStmt for | def = for.getVariable()) and
+ (result = true or result = false)
+ or
+ result = exprParity(def.(UnaryAssignExpr).getExpr()).booleanNot()
+ or
exists(AssignOp a | a = def and result = exprParity(a))
- ) or
- result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable()) or
- result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable()) or
- exists(Parameter p | v.(SsaImplicitInit).isParameterDefinition(p) and (result = true or result = false)) or
+ )
+ or
+ result = fieldParity(v.(SsaImplicitUpdate).getSourceVariable().getVariable())
+ or
+ result = fieldParity(v.(SsaImplicitInit).getSourceVariable().getVariable())
+ or
+ exists(Parameter p |
+ v.(SsaImplicitInit).isParameterDefinition(p) and
+ (result = true or result = false)
+ )
+ or
result = ssaParity(v.(SsaPhiNode).getAPhiInput())
}
/** Gets a possible parity for `f`. */
-deprecated
-private Parity fieldParity(Field f) {
- result = exprParity(f.getAnAssignedValue()) or
- exists(UnaryAssignExpr u | u.getExpr() = f.getAnAccess() and (result = true or result = false)) or
- exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a)) or
- exists(ReflectiveFieldAccess rfa | rfa.inferAccessedField() = f and (result = true or result = false))
+deprecated private Parity fieldParity(Field f) {
+ result = exprParity(f.getAnAssignedValue())
or
- if f.fromSource() then
- not exists(f.getInitializer()) and result.isEven()
- else
+ exists(UnaryAssignExpr u |
+ u.getExpr() = f.getAnAccess() and
(result = true or result = false)
+ )
+ or
+ exists(AssignOp a | a.getDest() = f.getAnAccess() | result = exprParity(a))
+ or
+ exists(ReflectiveFieldAccess rfa |
+ rfa.inferAccessedField() = f and
+ (result = true or result = false)
+ )
+ or
+ if f.fromSource()
+ then not exists(f.getInitializer()) and result.isEven()
+ else (
+ result = true or result = false
+ )
}
/** Holds if the parity of `e` is too complicated to determine. */
-deprecated
-private predicate unknownParity(Expr e) {
- e instanceof AssignDivExpr or
- e instanceof AssignRShiftExpr or
- e instanceof AssignURShiftExpr or
- e instanceof DivExpr or
- e instanceof RShiftExpr or
- e instanceof URShiftExpr or
- exists(Type fromtyp | e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType) or
- e instanceof ArrayAccess and e.getType() instanceof IntegralType or
- e instanceof MethodAccess and e.getType() instanceof IntegralType or
- e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType or
- e.getType() instanceof FloatingPointType or
+deprecated private predicate unknownParity(Expr e) {
+ e instanceof AssignDivExpr
+ or
+ e instanceof AssignRShiftExpr
+ or
+ e instanceof AssignURShiftExpr
+ or
+ e instanceof DivExpr
+ or
+ e instanceof RShiftExpr
+ or
+ e instanceof URShiftExpr
+ or
+ exists(Type fromtyp |
+ e.(CastExpr).getExpr().getType() = fromtyp and not fromtyp instanceof IntegralType
+ )
+ or
+ e instanceof ArrayAccess and e.getType() instanceof IntegralType
+ or
+ e instanceof MethodAccess and e.getType() instanceof IntegralType
+ or
+ e instanceof ClassInstanceExpr and e.getType() instanceof IntegralType
+ or
+ e.getType() instanceof FloatingPointType
+ or
e.getType() instanceof CharacterType
}
/** Gets a possible parity for `e`. */
-deprecated
-private Parity exprParity(Expr e) {
- result = certainExprParity(e) or
+deprecated private Parity exprParity(Expr e) {
+ result = certainExprParity(e)
+ or
not exists(certainExprParity(e)) and
(
- result = exprParity(e.(ParExpr).getExpr()) or
- result = exprParity(arrLenDef(e)) or
- exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and not exists(arrLenDef(e)) or
+ result = exprParity(e.(ParExpr).getExpr())
+ or
+ result = exprParity(arrLenDef(e))
+ or
+ exists(SsaVariable v | v.getAUse() = e | result = ssaParity(v)) and
+ not exists(arrLenDef(e))
+ or
exists(FieldAccess fa | fa = e |
not exists(SsaVariable v | v.getAUse() = fa) and
not exists(arrLenDef(e)) and
result = fieldParity(fa.getField())
- ) or
+ )
+ or
exists(VarAccess va | va = e |
not exists(SsaVariable v | v.getAUse() = va) and
not va instanceof FieldAccess and
(result = true or result = false)
- ) or
- result = exprParity(e.(AssignExpr).getSource()) or
- result = exprParity(e.(PlusExpr).getExpr()) or
- result = exprParity(e.(PostIncExpr).getExpr()) or
- result = exprParity(e.(PostDecExpr).getExpr()) or
- result = exprParity(e.(PreIncExpr).getExpr()).booleanNot() or
- result = exprParity(e.(PreDecExpr).getExpr()).booleanNot() or
- result = exprParity(e.(MinusExpr).getExpr()) or
- result = exprParity(e.(BitNotExpr).getExpr()).booleanNot() or
- unknownParity(e) and (result = true or result = false) or
+ )
+ or
+ result = exprParity(e.(AssignExpr).getSource())
+ or
+ result = exprParity(e.(PlusExpr).getExpr())
+ or
+ result = exprParity(e.(PostIncExpr).getExpr())
+ or
+ result = exprParity(e.(PostDecExpr).getExpr())
+ or
+ result = exprParity(e.(PreIncExpr).getExpr()).booleanNot()
+ or
+ result = exprParity(e.(PreDecExpr).getExpr()).booleanNot()
+ or
+ result = exprParity(e.(MinusExpr).getExpr())
+ or
+ result = exprParity(e.(BitNotExpr).getExpr()).booleanNot()
+ or
+ unknownParity(e) and
+ (result = true or result = false)
+ or
exists(Parity p1, Parity p2, AssignOp a |
a = e and
p1 = exprParity(a.getDest()) and
p2 = exprParity(a.getRhs())
- |
- a instanceof AssignAddExpr and result = p1.booleanXor(p2) or
- a instanceof AssignSubExpr and result = p1.booleanXor(p2) or
- a instanceof AssignMulExpr and result = p1.booleanAnd(p2) or
- a instanceof AssignRemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or
- a instanceof AssignAndExpr and result = p1.booleanAnd(p2) or
- a instanceof AssignOrExpr and result = p1.booleanOr(p2) or
- a instanceof AssignXorExpr and result = p1.booleanXor(p2) or
- a instanceof AssignLShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(a.getRhs()))
- ) or
+ |
+ a instanceof AssignAddExpr and result = p1.booleanXor(p2)
+ or
+ a instanceof AssignSubExpr and result = p1.booleanXor(p2)
+ or
+ a instanceof AssignMulExpr and result = p1.booleanAnd(p2)
+ or
+ a instanceof AssignRemExpr and
+ (
+ p2.isEven() and result = p1
+ or
+ p2.isOdd() and
+ (result = true or result = false)
+ )
+ or
+ a instanceof AssignAndExpr and result = p1.booleanAnd(p2)
+ or
+ a instanceof AssignOrExpr and result = p1.booleanOr(p2)
+ or
+ a instanceof AssignXorExpr and result = p1.booleanXor(p2)
+ or
+ a instanceof AssignLShiftExpr and
+ (
+ result.isEven()
+ or
+ result = p1 and not strictlyPositive(a.getRhs())
+ )
+ )
+ or
exists(Parity p1, Parity p2, BinaryExpr bin |
bin = e and
p1 = exprParity(bin.getLeftOperand()) and
p2 = exprParity(bin.getRightOperand())
- |
- bin instanceof AddExpr and result = p1.booleanXor(p2) or
- bin instanceof SubExpr and result = p1.booleanXor(p2) or
- bin instanceof MulExpr and result = p1.booleanAnd(p2) or
- bin instanceof RemExpr and (p2.isEven() and result = p1 or p2.isOdd() and (result = true or result = false)) or
- bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2) or
- bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2) or
- bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2) or
- bin instanceof LShiftExpr and (result.isEven() or result = p1 and not strictlyPositive(bin.getRightOperand()))
- ) or
- result = exprParity(e.(ConditionalExpr).getTrueExpr()) or
- result = exprParity(e.(ConditionalExpr).getFalseExpr()) or
+ |
+ bin instanceof AddExpr and result = p1.booleanXor(p2)
+ or
+ bin instanceof SubExpr and result = p1.booleanXor(p2)
+ or
+ bin instanceof MulExpr and result = p1.booleanAnd(p2)
+ or
+ bin instanceof RemExpr and
+ (
+ p2.isEven() and result = p1
+ or
+ p2.isOdd() and
+ (result = true or result = false)
+ )
+ or
+ bin instanceof AndBitwiseExpr and result = p1.booleanAnd(p2)
+ or
+ bin instanceof OrBitwiseExpr and result = p1.booleanOr(p2)
+ or
+ bin instanceof XorBitwiseExpr and result = p1.booleanXor(p2)
+ or
+ bin instanceof LShiftExpr and
+ (
+ result.isEven()
+ or
+ result = p1 and not strictlyPositive(bin.getRightOperand())
+ )
+ )
+ or
+ result = exprParity(e.(ConditionalExpr).getTrueExpr())
+ or
+ result = exprParity(e.(ConditionalExpr).getFalseExpr())
+ or
result = exprParity(e.(CastExpr).getExpr())
)
}
@@ -219,10 +319,7 @@ private Parity exprParity(Expr e) {
/**
* Gets the parity of `e` if it can be uniquely determined.
*/
-deprecated
-Parity getExprParity(Expr e) {
- result = exprParity(e) and 1 = count(exprParity(e))
-}
+deprecated Parity getExprParity(Expr e) { result = exprParity(e) and 1 = count(exprParity(e)) }
/**
* DEPRECATED: Use semmle.code.java.dataflow.ModulusAnalysis instead.
@@ -230,8 +327,7 @@ Parity getExprParity(Expr e) {
* Holds if the parity can be determined for both sides of `comp`. The boolean
* `eqparity` indicates whether the two sides have equal or opposite parity.
*/
-deprecated
-predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
+deprecated predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
exists(Expr left, Expr right, boolean lpar, boolean rpar |
comp.getLeftOperand() = left and
comp.getRightOperand() = right and
@@ -240,4 +336,3 @@ predicate parityComparison(ComparisonExpr comp, boolean eqparity) {
eqparity = lpar.booleanXor(rpar).booleanNot()
)
}
-
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
index 694989d1a8e..066bd0fa925 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll
@@ -74,9 +74,10 @@ private import semmle.code.java.Collections
private import semmle.code.java.Maps
import Bound
-cached private module RangeAnalysisCache {
-
- cached module RangeAnalysisPublic {
+cached
+private module RangeAnalysisCache {
+ cached
+ module RangeAnalysisPublic {
/**
* Holds if `b + delta` is a valid bound for `e`.
* - `upper = true` : `e <= b + delta`
@@ -86,7 +87,8 @@ cached private module RangeAnalysisCache {
* or `NoReason` if the bound was proven directly without the use of a bounding
* condition.
*/
- cached predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) {
+ cached
+ predicate bounded(Expr e, Bound b, int delta, boolean upper, Reason reason) {
bounded(e, b, delta, upper, _, _, reason)
}
}
@@ -94,8 +96,10 @@ cached private module RangeAnalysisCache {
/**
* Holds if `guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)`.
*/
- cached predicate possibleReason(Guard guard) { guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _) }
-
+ cached
+ predicate possibleReason(Guard guard) {
+ guard = boundFlowCond(_, _, _, _, _) or guard = eqFlowCond(_, _, _, _, _)
+ }
}
private import RangeAnalysisCache
import RangeAnalysisPublic
@@ -105,31 +109,45 @@ import RangeAnalysisPublic
* - `upper = true` : `v <= e + delta` or `v < e + delta`
* - `upper = false` : `v >= e + delta` or `v > e + delta`
*/
-private predicate boundCondition(ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper) {
+private predicate boundCondition(
+ ComparisonExpr comp, SsaVariable v, Expr e, int delta, boolean upper
+) {
comp.getLesserOperand() = ssaRead(v, delta) and e = comp.getGreaterOperand() and upper = true
or
comp.getGreaterOperand() = ssaRead(v, delta) and e = comp.getLesserOperand() and upper = false
or
exists(SubExpr sub, ConstantIntegerExpr c, int d |
// (v - d) - e < c
- comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and
- sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and
- upper = true and delta = d + c.getIntValue()
+ comp.getLesserOperand().getProperExpr() = sub and
+ comp.getGreaterOperand() = c and
+ sub.getLeftOperand() = ssaRead(v, d) and
+ sub.getRightOperand() = e and
+ upper = true and
+ delta = d + c.getIntValue()
or
// (v - d) - e > c
- comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and
- sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and
- upper = false and delta = d + c.getIntValue()
+ comp.getGreaterOperand().getProperExpr() = sub and
+ comp.getLesserOperand() = c and
+ sub.getLeftOperand() = ssaRead(v, d) and
+ sub.getRightOperand() = e and
+ upper = false and
+ delta = d + c.getIntValue()
or
// e - (v - d) < c
- comp.getLesserOperand().getProperExpr() = sub and comp.getGreaterOperand() = c and
- sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and
- upper = false and delta = d - c.getIntValue()
+ comp.getLesserOperand().getProperExpr() = sub and
+ comp.getGreaterOperand() = c and
+ sub.getLeftOperand() = e and
+ sub.getRightOperand() = ssaRead(v, d) and
+ upper = false and
+ delta = d - c.getIntValue()
or
// e - (v - d) > c
- comp.getGreaterOperand().getProperExpr() = sub and comp.getLesserOperand() = c and
- sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and
- upper = true and delta = d - c.getIntValue()
+ comp.getGreaterOperand().getProperExpr() = sub and
+ comp.getLesserOperand() = c and
+ sub.getLeftOperand() = e and
+ sub.getRightOperand() = ssaRead(v, d) and
+ upper = true and
+ delta = d - c.getIntValue()
)
}
@@ -137,7 +155,8 @@ private predicate gcdInput(int x, int y) {
exists(ComparisonExpr comp, Bound b |
exprModulus(comp.getLesserOperand(), b, _, x) and
exprModulus(comp.getGreaterOperand(), b, _, y)
- ) or
+ )
+ or
exists(int x0, int y0 |
gcdInput(x0, y0) and
x = y0 and
@@ -157,7 +176,9 @@ private int gcd(int x, int y) {
* strengthened by `strengthen` when evaluating to `testIsTrue`.
*/
private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int strengthen) {
- exists(Bound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k |
+ exists(
+ Bound b, int v1, int v2, int mod1, int mod2, int mod, boolean resultIsStrict, int d, int k
+ |
// If `x <= y` and `x =(mod) b + v1` and `y =(mod) b + v2` then
// `0 <= y - x =(mod) v2 - v1`. By choosing `k =(mod) v2 - v1` with
// `0 <= k < mod` we get `k <= y - x`. If the resulting comparison is
@@ -169,9 +190,21 @@ private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int
mod = gcd(mod1, mod2) and
mod != 1 and
(testIsTrue = true or testIsTrue = false) and
- (if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
- (resultIsStrict = true and d = 1 or resultIsStrict = false and d = 0) and
- (testIsTrue = true and k = v2 - v1 or testIsTrue = false and k = v1 - v2) and
+ (
+ if comp.isStrict()
+ then resultIsStrict = testIsTrue
+ else resultIsStrict = testIsTrue.booleanNot()
+ ) and
+ (
+ resultIsStrict = true and d = 1
+ or
+ resultIsStrict = false and d = 0
+ ) and
+ (
+ testIsTrue = true and k = v2 - v1
+ or
+ testIsTrue = false and k = v1 - v2
+ ) and
strengthen = (((k - d) % mod) + mod) % mod
)
}
@@ -184,27 +217,47 @@ private predicate modulusComparison(ComparisonExpr comp, boolean testIsTrue, int
* - `upper = false` : `v >= e + delta`
*/
private Guard boundFlowCond(SsaVariable v, Expr e, int delta, boolean upper, boolean testIsTrue) {
- exists(ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper, boolean resultIsStrict |
+ exists(
+ ComparisonExpr comp, int d1, int d2, int d3, int strengthen, boolean compIsUpper,
+ boolean resultIsStrict
+ |
comp = result and
boundCondition(comp, v, e, d1, compIsUpper) and
(testIsTrue = true or testIsTrue = false) and
upper = compIsUpper.booleanXor(testIsTrue.booleanNot()) and
- (if comp.isStrict() then resultIsStrict = testIsTrue else resultIsStrict = testIsTrue.booleanNot()) and
- (if v.getSourceVariable().getType() instanceof IntegralType then
- (upper = true and strengthen = -1 or
- upper = false and strengthen = 1)
- else
- strengthen = 0) and
(
- exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k) or
+ if comp.isStrict()
+ then resultIsStrict = testIsTrue
+ else resultIsStrict = testIsTrue.booleanNot()
+ ) and
+ (
+ if v.getSourceVariable().getType() instanceof IntegralType
+ then (
+ upper = true and strengthen = -1
+ or
+ upper = false and strengthen = 1
+ ) else strengthen = 0
+ ) and
+ (
+ exists(int k | modulusComparison(comp, testIsTrue, k) and d2 = strengthen * k)
+ or
not modulusComparison(comp, testIsTrue, _) and d2 = 0
) and
// A strict inequality `x < y` can be strengthened to `x <= y - 1`.
- (resultIsStrict = true and d3 = strengthen or resultIsStrict = false and d3 = 0) and
+ (
+ resultIsStrict = true and d3 = strengthen
+ or
+ resultIsStrict = false and d3 = 0
+ ) and
delta = d1 + d2 + d3
- ) or
- exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)) or
- result = eqFlowCond(v, e, delta, true, testIsTrue) and (upper = true or upper = false)
+ )
+ or
+ exists(boolean testIsTrue0 |
+ implies_v2(result, testIsTrue, boundFlowCond(v, e, delta, upper, testIsTrue0), testIsTrue0)
+ )
+ or
+ result = eqFlowCond(v, e, delta, true, testIsTrue) and
+ (upper = true or upper = false)
}
private newtype TReason =
@@ -216,14 +269,13 @@ private newtype TReason =
* is due to a specific condition, or `NoReason` if the bound is inferred
* without going through a bounding condition.
*/
-abstract class Reason extends TReason {
- abstract string toString();
-}
-class NoReason extends Reason, TNoReason {
- override string toString() { result = "NoReason" }
-}
+abstract class Reason extends TReason { abstract string toString(); }
+
+class NoReason extends Reason, TNoReason { override string toString() { result = "NoReason" } }
+
class CondReason extends Reason, TCondReason {
Guard getCond() { this = TCondReason(result) }
+
override string toString() { result = getCond().toString() }
}
@@ -232,8 +284,13 @@ class CondReason extends Reason, TCondReason {
* - `upper = true` : `v <= e + delta`
* - `upper = false` : `v >= e + delta`
*/
-private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason) {
- ssaUpdateStep(v, e, delta) and pos.hasReadOfVar(v) and (upper = true or upper = false) and reason = TNoReason()
+private predicate boundFlowStepSsa(
+ SsaVariable v, SsaReadPosition pos, Expr e, int delta, boolean upper, Reason reason
+) {
+ ssaUpdateStep(v, e, delta) and
+ pos.hasReadOfVar(v) and
+ (upper = true or upper = false) and
+ reason = TNoReason()
or
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
@@ -244,7 +301,9 @@ private predicate boundFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, i
}
/** Holds if `v != e + delta` at `pos`. */
-private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason) {
+private predicate unequalFlowStepSsa(
+ SsaVariable v, SsaReadPosition pos, Expr e, int delta, Reason reason
+) {
exists(Guard guard, boolean testIsTrue |
pos.hasReadOfVar(v) and
guard = eqFlowCond(v, e, delta, false, testIsTrue) and
@@ -259,16 +318,25 @@ private predicate unequalFlowStepSsa(SsaVariable v, SsaReadPosition pos, Expr e,
*/
private predicate safeCast(Type fromtyp, Type totyp) {
exists(PrimitiveType pfrom, PrimitiveType pto | pfrom = fromtyp and pto = totyp |
- pfrom = pto or
- pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double") or
- pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double") or
- pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double") or
- pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double") or
- pfrom.hasName("long") and pto.getName().regexpMatch("float|double") or
- pfrom.hasName("float") and pto.hasName("double") or
+ pfrom = pto
+ or
+ pfrom.hasName("char") and pto.getName().regexpMatch("int|long|float|double")
+ or
+ pfrom.hasName("byte") and pto.getName().regexpMatch("short|int|long|float|double")
+ or
+ pfrom.hasName("short") and pto.getName().regexpMatch("int|long|float|double")
+ or
+ pfrom.hasName("int") and pto.getName().regexpMatch("long|float|double")
+ or
+ pfrom.hasName("long") and pto.getName().regexpMatch("float|double")
+ or
+ pfrom.hasName("float") and pto.hasName("double")
+ or
pfrom.hasName("double") and pto.hasName("float")
- ) or
- safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp) or
+ )
+ or
+ safeCast(fromtyp.(BoxedType).getPrimitiveType(), totyp)
+ or
safeCast(fromtyp, totyp.(BoxedType).getPrimitiveType())
}
@@ -276,18 +344,19 @@ private predicate safeCast(Type fromtyp, Type totyp) {
* A cast that can be ignored for the purpose of range analysis.
*/
private class SafeCastExpr extends CastExpr {
- SafeCastExpr() {
- safeCast(getExpr().getType(), getType())
- }
+ SafeCastExpr() { safeCast(getExpr().getType(), getType()) }
}
/**
* Holds if `typ` is a small integral type with the given lower and upper bounds.
*/
private predicate typeBound(Type typ, int lowerbound, int upperbound) {
- typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127 or
- typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767 or
- typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535 or
+ typ.(PrimitiveType).hasName("byte") and lowerbound = -128 and upperbound = 127
+ or
+ typ.(PrimitiveType).hasName("short") and lowerbound = -32768 and upperbound = 32767
+ or
+ typ.(PrimitiveType).hasName("char") and lowerbound = 0 and upperbound = 65535
+ or
typeBound(typ.(BoxedType).getPrimitiveType(), lowerbound, upperbound)
}
@@ -299,8 +368,10 @@ private class NarrowingCastExpr extends CastExpr {
not this instanceof SafeCastExpr and
typeBound(getType(), _, _)
}
+
/** Gets the lower bound of the resulting type. */
int getLowerBound() { typeBound(getType(), result, _) }
+
/** Gets the upper bound of the resulting type. */
int getUpperBound() { typeBound(getType(), _, result) }
}
@@ -311,62 +382,84 @@ private class NarrowingCastExpr extends CastExpr {
* - `upper = false` : `e2 >= e1 + delta`
*/
private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
- valueFlowStep(e2, e1, delta) and (upper = true or upper = false) or
- e2.(SafeCastExpr).getExpr() = e1 and delta = 0 and (upper = true or upper = false) or
+ valueFlowStep(e2, e1, delta) and
+ (upper = true or upper = false)
+ or
+ e2.(SafeCastExpr).getExpr() = e1 and
+ delta = 0 and
+ (upper = true or upper = false)
+ or
exists(Expr x |
- e2.(AddExpr).hasOperands(e1, x) or
+ e2.(AddExpr).hasOperands(e1, x)
+ or
exists(AssignAddExpr add | add = e2 |
- add.getDest() = e1 and add.getRhs() = x or
+ add.getDest() = e1 and add.getRhs() = x
+ or
add.getDest() = x and add.getRhs() = e1
)
- |
+ |
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
not e1 instanceof ConstantIntegerExpr and
- if strictlyPositive(x) then
- (upper = false and delta = 1)
- else if positive(x) then
- (upper = false and delta = 0)
- else if strictlyNegative(x) then
- (upper = true and delta = -1)
- else if negative(x) then
- (upper = true and delta = 0)
- else
- none()
- ) or
+ if strictlyPositive(x)
+ then (
+ upper = false and delta = 1
+ ) else
+ if positive(x)
+ then (
+ upper = false and delta = 0
+ ) else
+ if strictlyNegative(x)
+ then (
+ upper = true and delta = -1
+ ) else if negative(x) then (upper = true and delta = 0) else none()
+ )
+ or
exists(Expr x |
exists(SubExpr sub |
e2 = sub and
sub.getLeftOperand() = e1 and
sub.getRightOperand() = x
- ) or
+ )
+ or
exists(AssignSubExpr sub |
e2 = sub and
sub.getDest() = e1 and
sub.getRhs() = x
)
- |
+ |
// `x instanceof ConstantIntegerExpr` is covered by valueFlowStep
not x instanceof ConstantIntegerExpr and
- if strictlyPositive(x) then
- (upper = true and delta = -1)
- else if positive(x) then
- (upper = true and delta = 0)
- else if strictlyNegative(x) then
- (upper = false and delta = 1)
- else if negative(x) then
- (upper = false and delta = 0)
- else
- none()
- ) or
- e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true or
- e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true or
- e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true or
- e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true or
- e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true or
- e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true or
- e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false or
- e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false or
+ if strictlyPositive(x)
+ then (
+ upper = true and delta = -1
+ ) else
+ if positive(x)
+ then (
+ upper = true and delta = 0
+ ) else
+ if strictlyNegative(x)
+ then (
+ upper = false and delta = 1
+ ) else if negative(x) then (upper = false and delta = 0) else none()
+ )
+ or
+ e2.(RemExpr).getRightOperand() = e1 and positive(e1) and delta = -1 and upper = true
+ or
+ e2.(RemExpr).getLeftOperand() = e1 and positive(e1) and delta = 0 and upper = true
+ or
+ e2.(AssignRemExpr).getRhs() = e1 and positive(e1) and delta = -1 and upper = true
+ or
+ e2.(AssignRemExpr).getDest() = e1 and positive(e1) and delta = 0 and upper = true
+ or
+ e2.(AndBitwiseExpr).getAnOperand() = e1 and positive(e1) and delta = 0 and upper = true
+ or
+ e2.(AssignAndExpr).getSource() = e1 and positive(e1) and delta = 0 and upper = true
+ or
+ e2.(OrBitwiseExpr).getAnOperand() = e1 and positive(e2) and delta = 0 and upper = false
+ or
+ e2.(AssignOrExpr).getSource() = e1 and positive(e2) and delta = 0 and upper = false
+ or
exists(MethodAccess ma, Method m |
e2 = ma and
ma.getMethod() = m and
@@ -375,11 +468,16 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
e1 = ma.getAnArgument() and
delta = -1 and
upper = true
- ) or
+ )
+ or
exists(MethodAccess ma, Method m |
e2 = ma and
ma.getMethod() = m and
- (m.hasName("max") and upper = false or m.hasName("min") and upper = true) and
+ (
+ m.hasName("max") and upper = false
+ or
+ m.hasName("min") and upper = true
+ ) and
m.getDeclaringType().hasQualifiedName("java.lang", "Math") and
e1 = ma.getAnArgument() and
delta = 0
@@ -389,11 +487,19 @@ private predicate boundFlowStep(Expr e2, Expr e1, int delta, boolean upper) {
/** Holds if `e2 = e1 * factor` and `factor > 0`. */
private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
- e2.(MulExpr).hasOperands(e1, c) and factor = k or
- exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or
- exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k) or
- exists(LShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
- exists(AssignLShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k))
+ e2.(MulExpr).hasOperands(e1, c) and factor = k
+ or
+ exists(AssignMulExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k)
+ or
+ exists(AssignMulExpr e | e = e2 and e.getDest() = c and e.getRhs() = e1 and factor = k)
+ or
+ exists(LShiftExpr e |
+ e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
+ )
+ or
+ exists(AssignLShiftExpr e |
+ e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
+ )
)
}
@@ -405,12 +511,27 @@ private predicate boundFlowStepMul(Expr e2, Expr e1, int factor) {
*/
private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) {
exists(ConstantIntegerExpr c, int k | k = c.getIntValue() and k > 0 |
- exists(DivExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k) or
- exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k) or
- exists(RShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
- exists(AssignRShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)) or
- exists(URShiftExpr e | e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)) or
- exists(AssignURShiftExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k))
+ exists(DivExpr e |
+ e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = k
+ )
+ or
+ exists(AssignDivExpr e | e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = k)
+ or
+ exists(RShiftExpr e |
+ e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
+ )
+ or
+ exists(AssignRShiftExpr e |
+ e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
+ )
+ or
+ exists(URShiftExpr e |
+ e = e2 and e.getLeftOperand() = e1 and e.getRightOperand() = c and factor = 2.pow(k)
+ )
+ or
+ exists(AssignURShiftExpr e |
+ e = e2 and e.getDest() = e1 and e.getRhs() = c and factor = 2.pow(k)
+ )
)
}
@@ -419,7 +540,10 @@ private predicate boundFlowStepDiv(Expr e2, Expr e1, int factor) {
* - `upper = true` : `v <= b + delta`
* - `upper = false` : `v >= b + delta`
*/
-private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+private predicate boundedSsa(
+ SsaVariable v, SsaReadPosition pos, Bound b, int delta, boolean upper, boolean fromBackEdge,
+ int origdelta, Reason reason
+) {
exists(Expr mid, int d1, int d2, Reason r1, Reason r2 |
boundFlowStepSsa(v, pos, mid, d1, upper, r1) and
bounded(mid, b, d2, upper, fromBackEdge, origdelta, r2) and
@@ -427,14 +551,23 @@ private predicate boundedSsa(SsaVariable v, SsaReadPosition pos, Bound b, int de
// upper = false: v >= mid + d1 >= b + d1 + d2 = b + delta
delta = d1 + d2 and
(if r1 instanceof NoReason then reason = r2 else reason = r1)
- ) or
+ )
+ or
exists(int d, Reason r1, Reason r2 |
boundedSsa(v, pos, b, d, upper, fromBackEdge, origdelta, r2) or
boundedPhi(v, b, d, upper, fromBackEdge, origdelta, r2)
- |
+ |
unequalSsa(v, pos, b, d, r1) and
- (upper = true and delta = d - 1 or upper = false and delta = d + 1) and
- (reason = r1 or reason = r2 and not r2 instanceof NoReason)
+ (
+ upper = true and delta = d - 1
+ or
+ upper = false and delta = d + 1
+ ) and
+ (
+ reason = r1
+ or
+ reason = r2 and not r2 instanceof NoReason
+ )
)
}
@@ -456,14 +589,19 @@ private predicate unequalSsa(SsaVariable v, SsaReadPosition pos, Bound b, int de
private predicate backEdge(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
edge.phiInput(phi, inp) and
// Conservatively assume that every edge is a back edge if we don't have dominance information.
- (phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or not hasDominanceInformation(edge.getOrigBlock()))
+ (
+ phi.getBasicBlock().bbDominates(edge.getOrigBlock()) or
+ not hasDominanceInformation(edge.getOrigBlock())
+ )
}
/** Weakens a delta to lie in the range `[-1..1]`. */
bindingset[delta, upper]
private int weakenDelta(boolean upper, int delta) {
- delta in [-1..1] and result = delta or
- upper = true and result = -1 and delta < -1 or
+ delta in [-1 .. 1] and result = delta
+ or
+ upper = true and result = -1 and delta < -1
+ or
upper = false and result = 1 and delta > 1
}
@@ -473,27 +611,43 @@ private int weakenDelta(boolean upper, int delta) {
* - `upper = true` : `inp <= b + delta`
* - `upper = false` : `inp >= b + delta`
*/
-private predicate boundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+private predicate boundedPhiInp(
+ SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, Bound b, int delta,
+ boolean upper, boolean fromBackEdge, int origdelta, Reason reason
+) {
edge.phiInput(phi, inp) and
exists(int d, boolean fromBackEdge0 |
- boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason) or
- boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason) or
- b.(SsaBound).getSsa() = inp and d = 0 and (upper = true or upper = false) and fromBackEdge0 = false and origdelta = 0 and reason = TNoReason()
- |
- if backEdge(phi, inp, edge) then
+ boundedSsa(inp, edge, b, d, upper, fromBackEdge0, origdelta, reason)
+ or
+ boundedPhi(inp, b, d, upper, fromBackEdge0, origdelta, reason)
+ or
+ b.(SsaBound).getSsa() = inp and
+ d = 0 and
+ (upper = true or upper = false) and
+ fromBackEdge0 = false and
+ origdelta = 0 and
+ reason = TNoReason()
+ |
+ if backEdge(phi, inp, edge)
+ then
fromBackEdge = true and
(
- fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta or
+ fromBackEdge0 = true and delta = weakenDelta(upper, d - origdelta) + origdelta
+ or
fromBackEdge0 = false and delta = d
)
- else
- (delta = d and fromBackEdge = fromBackEdge0)
+ else (
+ delta = d and fromBackEdge = fromBackEdge0
+ )
)
}
/** Holds if `boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)`. */
pragma[noinline]
-private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge, int delta) {
+private predicate boundedPhiInp1(
+ SsaPhiNode phi, Bound b, boolean upper, SsaVariable inp, SsaReadPositionPhiInputEdge edge,
+ int delta
+) {
boundedPhiInp(phi, inp, edge, b, delta, upper, _, _, _)
}
@@ -503,11 +657,17 @@ private predicate boundedPhiInp1(SsaPhiNode phi, Bound b, boolean upper, SsaVari
* - `upper = true` : `inp <= phi`
* - `upper = false` : `inp >= phi`
*/
-private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper) {
+private predicate selfBoundedPhiInp(
+ SsaPhiNode phi, SsaVariable inp, SsaReadPositionPhiInputEdge edge, boolean upper
+) {
exists(int d, SsaBound phibound |
phibound.getSsa() = phi and
boundedPhiInp(phi, inp, edge, phibound, d, upper, _, _, _) and
- (upper = true and d <= 0 or upper = false and d >= 0)
+ (
+ upper = true and d <= 0
+ or
+ upper = false and d >= 0
+ )
)
}
@@ -518,19 +678,29 @@ private predicate selfBoundedPhiInp(SsaPhiNode phi, SsaVariable inp, SsaReadPosi
* - `upper = false` : `inp >= b + delta`
*/
pragma[noinline]
-private predicate boundedPhiCand(SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta, Reason reason) {
- exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge | boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason))
+private predicate boundedPhiCand(
+ SsaPhiNode phi, boolean upper, Bound b, int delta, boolean fromBackEdge, int origdelta,
+ Reason reason
+) {
+ exists(SsaVariable inp, SsaReadPositionPhiInputEdge edge |
+ boundedPhiInp(phi, inp, edge, b, delta, upper, fromBackEdge, origdelta, reason)
+ )
}
/**
* Holds if the candidate bound `b + delta` for `phi` is valid for the phi input
* `inp` along `edge`.
*/
-private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge) {
+private predicate boundedPhiCandValidForEdge(
+ SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
+ Reason reason, SsaVariable inp, SsaReadPositionPhiInputEdge edge
+) {
boundedPhiCand(phi, upper, b, delta, fromBackEdge, origdelta, reason) and
(
- exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta) or
- exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta) or
+ exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = true and d <= delta)
+ or
+ exists(int d | boundedPhiInp1(phi, b, upper, inp, edge, d) | upper = false and d >= delta)
+ or
selfBoundedPhiInp(phi, inp, edge, upper)
)
}
@@ -540,7 +710,10 @@ private predicate boundedPhiCandValidForEdge(SsaPhiNode phi, Bound b, int delta,
* - `upper = true` : `phi <= b + delta`
* - `upper = false` : `phi >= b + delta`
*/
-private predicate boundedPhi(SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+private predicate boundedPhi(
+ SsaPhiNode phi, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
+ Reason reason
+) {
forex(SsaVariable inp, SsaReadPositionPhiInputEdge edge | edge.phiInput(phi, inp) |
boundedPhiCandValidForEdge(phi, b, delta, upper, fromBackEdge, origdelta, reason, inp, edge)
)
@@ -562,14 +735,16 @@ private predicate lowerBoundZero(Expr e) {
* (for `upper = false`) bound of `b`.
*/
private predicate baseBound(Expr e, int b, boolean upper) {
- lowerBoundZero(e) and b = 0 and upper = false or
+ lowerBoundZero(e) and b = 0 and upper = false
+ or
exists(Method read |
e.(MethodAccess).getMethod().overrides*(read) and
read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and
read.hasName("read") and
read.getNumberOfParameters() = 0
- |
- upper = true and b = 255 or
+ |
+ upper = true and b = 255
+ or
upper = false and b = -1
)
}
@@ -581,16 +756,18 @@ private predicate baseBound(Expr e, int b, boolean upper) {
* `upper = false` this means that the cast will not underflow.
*/
private predicate safeNarrowingCast(NarrowingCastExpr cast, boolean upper) {
- exists(int bound |
- bounded(cast.getExpr(), any(ZeroBound zb), bound, upper, _, _, _)
- |
- upper = true and bound <= cast.getUpperBound() or
+ exists(int bound | bounded(cast.getExpr(), any(ZeroBound zb), bound, upper, _, _, _) |
+ upper = true and bound <= cast.getUpperBound()
+ or
upper = false and bound >= cast.getLowerBound()
)
}
pragma[noinline]
-private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
+private predicate boundedCastExpr(
+ NarrowingCastExpr cast, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta,
+ Reason reason
+) {
bounded(cast.getExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
}
@@ -599,14 +776,27 @@ private predicate boundedCastExpr(NarrowingCastExpr cast, Bound b, int delta, bo
* - `upper = true` : `e <= b + delta`
* - `upper = false` : `e >= b + delta`
*/
-private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason) {
- e = b.getExpr(delta) and (upper = true or upper = false) and fromBackEdge = false and origdelta = delta and reason = TNoReason() or
- baseBound(e, delta, upper) and b instanceof ZeroBound and fromBackEdge = false and origdelta = delta and reason = TNoReason() or
+private predicate bounded(
+ Expr e, Bound b, int delta, boolean upper, boolean fromBackEdge, int origdelta, Reason reason
+) {
+ e = b.getExpr(delta) and
+ (upper = true or upper = false) and
+ fromBackEdge = false and
+ origdelta = delta and
+ reason = TNoReason()
+ or
+ baseBound(e, delta, upper) and
+ b instanceof ZeroBound and
+ fromBackEdge = false and
+ origdelta = delta and
+ reason = TNoReason()
+ or
exists(SsaVariable v, SsaReadPositionBlock bb |
boundedSsa(v, bb, b, delta, upper, fromBackEdge, origdelta, reason) and
e = v.getAUse() and
bb.getBlock() = e.getBasicBlock()
- ) or
+ )
+ or
exists(Expr mid, int d1, int d2 |
boundFlowStep(e, mid, d1, upper) and
// Constants have easy, base-case bounds, so let's not infer any recursive bounds.
@@ -615,18 +805,21 @@ private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fro
// upper = true: e <= mid + d1 <= b + d1 + d2 = b + delta
// upper = false: e >= mid + d1 >= b + d1 + d2 = b + delta
delta = d1 + d2
- ) or
+ )
+ or
exists(SsaPhiNode phi |
boundedPhi(phi, b, delta, upper, fromBackEdge, origdelta, reason) and
e = phi.getAUse()
- ) or
+ )
+ or
exists(Expr mid, int factor, int d |
boundFlowStepMul(e, mid, factor) and
not e instanceof ConstantIntegerExpr and
bounded(mid, b, d, upper, fromBackEdge, origdelta, reason) and
b instanceof ZeroBound and
delta = d * factor
- ) or
+ )
+ or
exists(Expr mid, int factor, int d |
boundFlowStepDiv(e, mid, factor) and
not e instanceof ConstantIntegerExpr and
@@ -634,25 +827,38 @@ private predicate bounded(Expr e, Bound b, int delta, boolean upper, boolean fro
b instanceof ZeroBound and
d >= 0 and
delta = d / factor
- ) or
+ )
+ or
exists(NarrowingCastExpr cast |
cast = e and
safeNarrowingCast(cast, upper.booleanNot()) and
boundedCastExpr(cast, b, delta, upper, fromBackEdge, origdelta, reason)
- ) or
- exists(ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1, Reason r2 |
+ )
+ or
+ exists(
+ ConditionalExpr cond, int d1, int d2, boolean fbe1, boolean fbe2, int od1, int od2, Reason r1,
+ Reason r2
+ |
cond = e and
boundedConditionalExpr(cond, b, upper, true, d1, fbe1, od1, r1) and
boundedConditionalExpr(cond, b, upper, false, d2, fbe2, od2, r2) and
- (delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1 or
- delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2)
- |
- upper = true and delta = d1.maximum(d2) or
+ (
+ delta = d1 and fromBackEdge = fbe1 and origdelta = od1 and reason = r1
+ or
+ delta = d2 and fromBackEdge = fbe2 and origdelta = od2 and reason = r2
+ )
+ |
+ upper = true and delta = d1.maximum(d2)
+ or
upper = false and delta = d1.minimum(d2)
)
}
-private predicate boundedConditionalExpr(ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge, int origdelta, Reason reason) {
- branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason) or
+private predicate boundedConditionalExpr(
+ ConditionalExpr cond, Bound b, boolean upper, boolean branch, int delta, boolean fromBackEdge,
+ int origdelta, Reason reason
+) {
+ branch = true and bounded(cond.getTrueExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
+ or
branch = false and bounded(cond.getFalseExpr(), b, delta, upper, fromBackEdge, origdelta, reason)
}
diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
index 131d62ccdaf..4cd953e573f 100644
--- a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
+++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll
@@ -9,7 +9,8 @@ private import semmle.code.java.controlflow.internal.GuardsLogic
/** An expression that always has the same integer value. */
pragma[nomagic]
private predicate constantIntegerExpr(Expr e, int val) {
- e.(CompileTimeConstantExpr).getIntValue() = val or
+ e.(CompileTimeConstantExpr).getIntValue() = val
+ or
exists(SsaExplicitUpdate v, Expr src |
e = v.getAUse() and
src = v.getDefiningExpr().(VariableAssign).getSource() and
@@ -26,37 +27,42 @@ private predicate constantIntegerExpr(Expr e, int val) {
/** An expression that always has the same integer value. */
class ConstantIntegerExpr extends Expr {
- ConstantIntegerExpr() {
- constantIntegerExpr(this, _)
- }
+ ConstantIntegerExpr() { constantIntegerExpr(this, _) }
/** Gets the integer value of this expression. */
- int getIntValue() {
- constantIntegerExpr(this, result)
- }
+ int getIntValue() { constantIntegerExpr(this, result) }
}
/**
* Gets an expression that equals `v - d`.
*/
Expr ssaRead(SsaVariable v, int delta) {
- result = v.getAUse() and delta = 0 or
- result.(ParExpr).getExpr() = ssaRead(v, delta) or
+ result = v.getAUse() and delta = 0
+ or
+ result.(ParExpr).getExpr() = ssaRead(v, delta)
+ or
exists(int d1, ConstantIntegerExpr c |
result.(AddExpr).hasOperands(ssaRead(v, d1), c) and
delta = d1 - c.getIntValue()
- ) or
+ )
+ or
exists(SubExpr sub, int d1, ConstantIntegerExpr c |
result = sub and
sub.getLeftOperand() = ssaRead(v, d1) and
sub.getRightOperand() = c and
delta = d1 + c.getIntValue()
- ) or
- v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0 or
- v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0 or
- v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 or // x++ === ++x - 1
- v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 or // x-- === --x + 1
- v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0 or
+ )
+ or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PreIncExpr) = result and delta = 0
+ or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PreDecExpr) = result and delta = 0
+ or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PostIncExpr) = result and delta = 1 // x++ === ++x - 1
+ or
+ v.(SsaExplicitUpdate).getDefiningExpr().(PostDecExpr) = result and delta = -1 // x-- === --x + 1
+ or
+ v.(SsaExplicitUpdate).getDefiningExpr().(Assignment) = result and delta = 0
+ or
result.(AssignExpr).getSource() = ssaRead(v, delta)
}
@@ -121,7 +127,8 @@ class SsaReadPositionPhiInputEdge extends SsaReadPosition, TSsaReadPositionPhiIn
* value `testIsTrue`.
*/
predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
- guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue) or
+ guard.directlyControls(controlled.(SsaReadPositionBlock).getBlock(), testIsTrue)
+ or
exists(SsaReadPositionPhiInputEdge controlledEdge | controlledEdge = controlled |
guard.directlyControls(controlledEdge.getOrigBlock(), testIsTrue) or
guard.hasBranchEdge(controlledEdge.getOrigBlock(), controlledEdge.getPhiBlock(), testIsTrue)
@@ -132,7 +139,8 @@ predicate guardDirectlyControlsSsaRead(Guard guard, SsaReadPosition controlled,
* Holds if `guard` controls the position `controlled` with the value `testIsTrue`.
*/
predicate guardControlsSsaRead(Guard guard, SsaReadPosition controlled, boolean testIsTrue) {
- guardDirectlyControlsSsaRead(guard, controlled, testIsTrue) or
+ guardDirectlyControlsSsaRead(guard, controlled, testIsTrue)
+ or
exists(Guard guard0, boolean testIsTrue0 |
implies_v2(guard0, testIsTrue0, guard, testIsTrue) and
guardControlsSsaRead(guard0, controlled, testIsTrue0)
@@ -153,18 +161,25 @@ Guard eqFlowCond(SsaVariable v, Expr e, int delta, boolean isEq, boolean testIsT
eqpolarity.booleanXor(testIsTrue).booleanNot() = isEq
)
or
- exists(boolean testIsTrue0 | implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0))
+ exists(boolean testIsTrue0 |
+ implies_v2(result, testIsTrue, eqFlowCond(v, e, delta, isEq, testIsTrue0), testIsTrue0)
+ )
}
/**
* Holds if `v` is an `SsaExplicitUpdate` that equals `e + delta`.
*/
predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) {
- v.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0 or
- v.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1 or
- v.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1 or
- v.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1 or
- v.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1 or
+ v.getDefiningExpr().(VariableAssign).getSource() = e and delta = 0
+ or
+ v.getDefiningExpr().(PostIncExpr).getExpr() = e and delta = 1
+ or
+ v.getDefiningExpr().(PreIncExpr).getExpr() = e and delta = 1
+ or
+ v.getDefiningExpr().(PostDecExpr).getExpr() = e and delta = -1
+ or
+ v.getDefiningExpr().(PreDecExpr).getExpr() = e and delta = -1
+ or
v.getDefiningExpr().(AssignOp) = e and delta = 0
}
@@ -172,41 +187,53 @@ predicate ssaUpdateStep(SsaExplicitUpdate v, Expr e, int delta) {
* Holds if `e1 + delta` equals `e2`.
*/
predicate valueFlowStep(Expr e2, Expr e1, int delta) {
- e2.(ParExpr).getExpr() = e1 and delta = 0 or
- e2.(AssignExpr).getSource() = e1 and delta = 0 or
- e2.(PlusExpr).getExpr() = e1 and delta = 0 or
- e2.(PostIncExpr).getExpr() = e1 and delta = 0 or
- e2.(PostDecExpr).getExpr() = e1 and delta = 0 or
- e2.(PreIncExpr).getExpr() = e1 and delta = 1 or
- e2.(PreDecExpr).getExpr() = e1 and delta = -1 or
+ e2.(ParExpr).getExpr() = e1 and delta = 0
+ or
+ e2.(AssignExpr).getSource() = e1 and delta = 0
+ or
+ e2.(PlusExpr).getExpr() = e1 and delta = 0
+ or
+ e2.(PostIncExpr).getExpr() = e1 and delta = 0
+ or
+ e2.(PostDecExpr).getExpr() = e1 and delta = 0
+ or
+ e2.(PreIncExpr).getExpr() = e1 and delta = 1
+ or
+ e2.(PreDecExpr).getExpr() = e1 and delta = -1
+ or
exists(SsaExplicitUpdate v, FieldRead arrlen |
e2 = arrlen and
arrlen.getField() instanceof ArrayLengthField and
arrlen.getQualifier() = v.getAUse() and
v.getDefiningExpr().(VariableAssign).getSource().(ArrayCreationExpr).getDimension(0) = e1 and
delta = 0
- ) or
+ )
+ or
exists(Expr x |
- e2.(AddExpr).hasOperands(e1, x) or
+ e2.(AddExpr).hasOperands(e1, x)
+ or
exists(AssignAddExpr add | add = e2 |
- add.getDest() = e1 and add.getRhs() = x or
+ add.getDest() = e1 and add.getRhs() = x
+ or
add.getDest() = x and add.getRhs() = e1
)
- |
+ |
x.(ConstantIntegerExpr).getIntValue() = delta
- ) or
+ )
+ or
exists(Expr x |
exists(SubExpr sub |
e2 = sub and
sub.getLeftOperand() = e1 and
sub.getRightOperand() = x
- ) or
+ )
+ or
exists(AssignSubExpr sub |
e2 = sub and
sub.getDest() = e1 and
sub.getRhs() = x
)
- |
+ |
x.(ConstantIntegerExpr).getIntValue() = -delta
)
}
From c65bc5cc90555e8c31353986a120f7325eeb85c8 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Thu, 11 Oct 2018 13:44:38 +0200
Subject: [PATCH 49/98] JS: add Util::pluralize, also add tests for
Util::capitalize
---
javascript/ql/src/semmle/javascript/Util.qll | 13 +++++++++++++
.../ql/test/library-tests/Util/capitalize.expected | 1 +
javascript/ql/test/library-tests/Util/capitalize.ql | 3 +++
.../ql/test/library-tests/Util/pluralize.expected | 1 +
javascript/ql/test/library-tests/Util/pluralize.ql | 3 +++
javascript/ql/test/library-tests/Util/tst.js | 1 +
6 files changed, 22 insertions(+)
create mode 100644 javascript/ql/test/library-tests/Util/capitalize.expected
create mode 100644 javascript/ql/test/library-tests/Util/capitalize.ql
create mode 100644 javascript/ql/test/library-tests/Util/pluralize.expected
create mode 100644 javascript/ql/test/library-tests/Util/pluralize.ql
create mode 100644 javascript/ql/test/library-tests/Util/tst.js
diff --git a/javascript/ql/src/semmle/javascript/Util.qll b/javascript/ql/src/semmle/javascript/Util.qll
index caa1105191e..d669643be58 100644
--- a/javascript/ql/src/semmle/javascript/Util.qll
+++ b/javascript/ql/src/semmle/javascript/Util.qll
@@ -11,3 +11,16 @@ bindingset[s]
string capitalize(string s) {
result = s.charAt(0).toUpperCase() + s.suffix(1)
}
+
+ /**
+ * Gets the pluralization for `n` occurrences of `noun`.
+ *
+ * For example, the pluralization of `"function"` for `n = 2` is `"functions"`.
+ */
+bindingset[noun, n]
+string pluralize(string noun, int n) {
+ if n = 1 then
+ result = noun
+ else
+ result = noun + "s"
+}
\ No newline at end of file
diff --git a/javascript/ql/test/library-tests/Util/capitalize.expected b/javascript/ql/test/library-tests/Util/capitalize.expected
new file mode 100644
index 00000000000..a92936e701a
--- /dev/null
+++ b/javascript/ql/test/library-tests/Util/capitalize.expected
@@ -0,0 +1 @@
+| X | X | Xx | XX | Xx | XX |
diff --git a/javascript/ql/test/library-tests/Util/capitalize.ql b/javascript/ql/test/library-tests/Util/capitalize.ql
new file mode 100644
index 00000000000..0e90efece9b
--- /dev/null
+++ b/javascript/ql/test/library-tests/Util/capitalize.ql
@@ -0,0 +1,3 @@
+import semmle.javascript.Util
+
+select capitalize("x"), capitalize("X"), capitalize("xx"), capitalize("XX"), capitalize("Xx"), capitalize("xX")
diff --git a/javascript/ql/test/library-tests/Util/pluralize.expected b/javascript/ql/test/library-tests/Util/pluralize.expected
new file mode 100644
index 00000000000..09cfc2d49bf
--- /dev/null
+++ b/javascript/ql/test/library-tests/Util/pluralize.expected
@@ -0,0 +1 @@
+| xs | x | xs | xs |
diff --git a/javascript/ql/test/library-tests/Util/pluralize.ql b/javascript/ql/test/library-tests/Util/pluralize.ql
new file mode 100644
index 00000000000..7b7f0ed053d
--- /dev/null
+++ b/javascript/ql/test/library-tests/Util/pluralize.ql
@@ -0,0 +1,3 @@
+import semmle.javascript.Util
+
+select pluralize("x", 0), pluralize("x", 1), pluralize("x", 2), pluralize("x", -1)
diff --git a/javascript/ql/test/library-tests/Util/tst.js b/javascript/ql/test/library-tests/Util/tst.js
new file mode 100644
index 00000000000..a9eba82d534
--- /dev/null
+++ b/javascript/ql/test/library-tests/Util/tst.js
@@ -0,0 +1 @@
+// used by qltest to identify the language
From 9c2ca9a7faae533761aa5df7f5724c6c3cda8f2c Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Thu, 11 Oct 2018 09:43:24 +0200
Subject: [PATCH 50/98] JS: make js/unused-local-variable flag import
statements
---
change-notes/1.19/analysis-javascript.md | 1 +
.../ql/src/Declarations/UnusedVariable.ql | 107 ++++++++++++++----
.../UnusedVariable/UnusedVariable.expected | 8 +-
.../UnusedVariable/multi-imports.js | 4 +
4 files changed, 93 insertions(+), 27 deletions(-)
create mode 100644 javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js
diff --git a/change-notes/1.19/analysis-javascript.md b/change-notes/1.19/analysis-javascript.md
index beedd61d6da..9e2c73dd3d4 100644
--- a/change-notes/1.19/analysis-javascript.md
+++ b/change-notes/1.19/analysis-javascript.md
@@ -35,6 +35,7 @@
| Remote property injection | Fewer results | The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. |
| Missing CSRF middleware | Fewer false-positive results | This rule now recognizes additional CSRF protection middlewares. |
| Server-side URL redirect | More results | This rule now recognizes redirection calls in more cases. |
+| Unused variable, import, function or class | Fewer results | This rule now flags import statements with multiple unused imports once. |
| User-controlled bypass of security check | Fewer results | This rule no longer flags conditions that guard early returns. The precision of this rule has been revised to "medium". Results are no longer shown on LGTM by default. |
| Whitespace contradicts operator precedence | Fewer false-positive results | This rule no longer flags operators with asymmetric whitespace. |
diff --git a/javascript/ql/src/Declarations/UnusedVariable.ql b/javascript/ql/src/Declarations/UnusedVariable.ql
index 093de8b4bf7..c1d31e1b9d6 100644
--- a/javascript/ql/src/Declarations/UnusedVariable.ql
+++ b/javascript/ql/src/Declarations/UnusedVariable.ql
@@ -94,36 +94,95 @@ predicate isEnumMember(VarDecl decl) {
}
/**
- * Gets a description of the declaration `vd`, which is either of the form "function f" if
- * it is a function name, or "variable v" if it is not.
+ * Gets a description of the declaration `vd`, which is either of the form
+ * "function f", "variable v" or "class c".
*/
-string describe(VarDecl vd) {
+string describeVarDecl(VarDecl vd) {
if vd = any(Function f).getId() then
result = "function " + vd.getName()
else if vd = any(ClassDefinition c).getIdentifier() then
result = "class " + vd.getName()
- else if (vd = any(ImportSpecifier im).getLocal() or vd = any(ImportEqualsDeclaration im).getId()) then
- result = "import " + vd.getName()
else
result = "variable " + vd.getName()
}
-from VarDecl vd, UnusedLocal v
-where v = vd.getVariable() and
- // exclude variables mentioned in JSDoc comments in externs
- not mentionedInJSDocComment(v) and
- // exclude variables used to filter out unwanted properties
- not isPropertyFilter(v) and
- // exclude imports of React that are implicitly referenced by JSX
- not isReactImportForJSX(v) and
- // exclude names that are used as types
- not isUsedAsType(vd) and
- // exclude names that are used as namespaces from inside a type
- not isUsedAsNamespace(vd) and
- // exclude decorated functions and classes
- not isDecorated(vd) and
- // exclude names of enum members; they also define property names
- not isEnumMember(vd) and
- // ignore ambient declarations - too noisy
- not vd.isAmbient()
-select vd, "Unused " + describe(vd) + "."
+/**
+ * An import statement that provides variable declarations.
+ */
+class ImportVarDeclProvider extends Stmt {
+
+ ImportVarDeclProvider() {
+ this instanceof ImportDeclaration or
+ this instanceof ImportEqualsDeclaration
+ }
+
+ /**
+ * Gets a variable declaration of this import.
+ */
+ VarDecl getAVarDecl() {
+ result = this.(ImportDeclaration).getASpecifier().getLocal() or
+ result = this.(ImportEqualsDeclaration).getId()
+ }
+
+ /**
+ * Gets an unacceptable unused variable declared by this import.
+ */
+ UnusedLocal getAnUnacceptableUnusedLocal() {
+ result = getAVarDecl().getVariable() and
+ not whitelisted(result)
+ }
+
+}
+
+/**
+ * Holds if it is acceptable that `v` is unused.
+ */
+predicate whitelisted(UnusedLocal v) {
+ // exclude variables mentioned in JSDoc comments in externs
+ mentionedInJSDocComment(v) or
+ // exclude variables used to filter out unwanted properties
+ isPropertyFilter(v) or
+ // exclude imports of React that are implicitly referenced by JSX
+ isReactImportForJSX(v) or
+ // exclude names that are used as types
+ exists (VarDecl vd |
+ v = vd.getVariable() |
+ isUsedAsType(vd) or
+ // exclude names that are used as namespaces from inside a type
+ isUsedAsNamespace(vd)or
+ // exclude decorated functions and classes
+ isDecorated(vd) or
+ // exclude names of enum members; they also define property names
+ isEnumMember(vd) or
+ // ignore ambient declarations - too noisy
+ vd.isAmbient()
+ )
+}
+
+/**
+ * Holds if `vd` declares an unused variable that does not come from an import statement, as explained by `msg`.
+ */
+predicate unusedNonImports(VarDecl vd, string msg) {
+ exists (UnusedLocal v |
+ v = vd.getVariable() and
+ msg = "Unused " + describeVarDecl(vd) + "." and
+ not vd = any(ImportVarDeclProvider p).getAVarDecl() and
+ not whitelisted(v)
+ )
+}
+
+/**
+ * Holds if `provider` declares one or more unused variables, as explained by `msg`.
+ */
+predicate unusedImports(ImportVarDeclProvider provider, string msg) {
+ exists (string imports, string names |
+ imports = pluralize("import", count(provider.getAnUnacceptableUnusedLocal())) and
+ names = strictconcat(provider.getAnUnacceptableUnusedLocal().getName(), ", ") and
+ msg = "Unused " + imports + " " + names + "."
+ )
+}
+
+from ASTNode sel, string msg
+where unusedNonImports(sel, msg) or
+ unusedImports(sel, msg)
+select sel, msg
diff --git a/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected b/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected
index fc187550b36..7e1ae154cfe 100644
--- a/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected
+++ b/javascript/ql/test/query-tests/Declarations/UnusedVariable/UnusedVariable.expected
@@ -1,6 +1,8 @@
-| decorated.ts:1:9:1:21 | actionHandler | Unused import actionHandler. |
+| decorated.ts:1:1:1:126 | import ... where'; | Unused import actionHandler. |
| decorated.ts:4:10:4:12 | fun | Unused function fun. |
| externs.js:6:5:6:13 | iAmUnused | Unused variable iAmUnused. |
+| multi-imports.js:1:1:1:29 | import ... om 'x'; | Unused imports a, b, d. |
+| multi-imports.js:2:1:2:42 | import ... om 'x'; | Unused imports alphabetically, ordered. |
| typeoftype.ts:9:7:9:7 | y | Unused variable y. |
-| unusedShadowed.ts:1:8:1:8 | T | Unused import T. |
-| unusedShadowed.ts:2:8:2:13 | object | Unused import object. |
+| unusedShadowed.ts:1:1:1:26 | import ... where'; | Unused import T. |
+| unusedShadowed.ts:2:1:2:31 | import ... where'; | Unused import object. |
diff --git a/javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js b/javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js
new file mode 100644
index 00000000000..b32fa341a83
--- /dev/null
+++ b/javascript/ql/test/query-tests/Declarations/UnusedVariable/multi-imports.js
@@ -0,0 +1,4 @@
+import {a, b, c, d} from 'x';
+import {ordered, alphabetically} from 'x';
+
+c();
From bf58b6c9ab0886f35644a7267656504562a2e370 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Thu, 18 Oct 2018 15:05:04 +0200
Subject: [PATCH 51/98] Java: Remove self-ref tracking; improve
AccessPath.toString on numbers.
---
.../cpp/dataflow/internal/DataFlowPrivate.qll | 22 +++---
.../java/dataflow/internal/DataFlowImpl.qll | 71 +++----------------
.../dataflow/internal/DataFlowImplCommon.qll | 6 +-
.../dataflow/internal/DataFlowPrivate.qll | 36 ++++------
4 files changed, 37 insertions(+), 98 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll
index a3d9c966428..ce76a5b7a17 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowPrivate.qll
@@ -69,18 +69,9 @@ class Content extends TContent {
path = "" and sl = 0 and sc = 0 and el = 0 and ec = 0
}
/** Gets the type of the object containing this content. */
- abstract RefType getDeclaringType();
+ abstract RefType getContainerType();
/** Gets the type of this content. */
abstract Type getType();
- /**
- * Holds if this content may contain an object of the same type as the one
- * that contains this content, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
- predicate isSelfRef() { none() }
}
private class FieldContent extends Content, TFieldContent {
Field f;
@@ -90,17 +81,17 @@ private class FieldContent extends Content, TFieldContent {
override predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
f.getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
- override RefType getDeclaringType() { result = f.getDeclaringType() }
+ override RefType getContainerType() { result = f.getDeclaringType() }
override Type getType() { result = f.getType() }
}
private class CollectionContent extends Content, TCollectionContent {
override string toString() { result = "collection" }
- override RefType getDeclaringType() { none() }
+ override RefType getContainerType() { none() }
override Type getType() { none() }
}
private class ArrayContent extends Content, TArrayContent {
override string toString() { result = "array" }
- override RefType getDeclaringType() { none() }
+ override RefType getContainerType() { none() }
override Type getType() { none() }
}
@@ -132,6 +123,11 @@ RefType getErasedRepr(Type t) {
result instanceof VoidType // stub implementation
}
+/** Gets a string representation of a type returned by `getErasedRepr`. */
+string ppReprType(Type t) {
+ result = t.toString()
+}
+
/**
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
* a node of type `t1` to a node of type `t2`.
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
index fc1d6e5053e..02c9919723b 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll
@@ -118,7 +118,7 @@ private module ImplCommon {
node1.(ArgumentNode).argumentOf(call, i1) and
node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and
compatibleTypes(node1.getTypeBound(), f.getType()) and
- compatibleTypes(node2.getTypeBound(), f.getDeclaringType())
+ compatibleTypes(node2.getTypeBound(), f.getContainerType())
)
}
@@ -149,7 +149,7 @@ private module ImplCommon {
setterReturn(p, f) and
arg.argumentOf(node2.asExpr(), _) and
compatibleTypes(node1.getTypeBound(), f.getType()) and
- compatibleTypes(node2.getTypeBound(), f.getDeclaringType())
+ compatibleTypes(node2.getTypeBound(), f.getContainerType())
)
}
@@ -174,7 +174,7 @@ private module ImplCommon {
viableParamArg(p, arg) and
getter(p, f) and
arg.argumentOf(node2.asExpr(), _) and
- compatibleTypes(node1.getTypeBound(), f.getDeclaringType()) and
+ compatibleTypes(node1.getTypeBound(), f.getContainerType()) and
compatibleTypes(node2.getTypeBound(), f.getType())
)
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
index a7c1c8a849f..62475df4d37 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll
@@ -118,20 +118,10 @@ class Content extends TContent {
}
/** Gets the type of the object containing this content. */
- abstract RefType getDeclaringType();
+ abstract RefType getContainerType();
/** Gets the type of this content. */
abstract Type getType();
-
- /**
- * Holds if this content may contain an object of the same type as the one
- * that contains this content, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
- predicate isSelfRef() { none() }
}
private class FieldContent extends Content, TFieldContent {
@@ -147,17 +137,15 @@ private class FieldContent extends Content, TFieldContent {
f.getLocation().hasLocationInfo(path, sl, sc, el, ec)
}
- override RefType getDeclaringType() { result = f.getDeclaringType() }
+ override RefType getContainerType() { result = f.getDeclaringType() }
override Type getType() { result = getFieldTypeBound(f) }
-
- override predicate isSelfRef() { compatibleTypes(getDeclaringType(), getType()) }
}
private class CollectionContent extends Content, TCollectionContent {
override string toString() { result = "collection" }
- override RefType getDeclaringType() { none() }
+ override RefType getContainerType() { none() }
override Type getType() { none() }
}
@@ -165,7 +153,7 @@ private class CollectionContent extends Content, TCollectionContent {
private class ArrayContent extends Content, TArrayContent {
override string toString() { result = "array" }
- override RefType getDeclaringType() { none() }
+ override RefType getContainerType() { none() }
override Type getType() { none() }
}
@@ -212,6 +200,13 @@ RefType getErasedRepr(Type t) {
)
}
+/** Gets a string representation of a type returned by `getErasedRepr`. */
+string ppReprType(Type t) {
+ if t.(BoxedType).getPrimitiveType().getName() = "double"
+ then result = "Number"
+ else result = t.toString()
+}
+
private predicate canContainBool(Type t) {
t instanceof BooleanType or
any(BooleanType b).(RefType).getASourceSupertype+() = t
@@ -227,12 +222,9 @@ predicate compatibleTypes(Type t1, Type t2) {
e1 = getErasedRepr(t1) and
e2 = getErasedRepr(t2)
|
- /*
- * Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient
- * compatibility check, but `conContainBool` is kept as a dummy disjunct
- * to get the proper join-order.
- */
-
+ // Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient
+ // compatibility check, but `conContainBool` is kept as a dummy disjunct
+ // to get the proper join-order.
erasedHaveIntersection(e1, e2)
or
canContainBool(e1) and canContainBool(e2)
From 0b46ffa7d7c95d675d7ea7231b6c7eb9e6940640 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Thu, 18 Oct 2018 15:10:23 +0200
Subject: [PATCH 52/98] Java/CPP: Sync files.
---
.../cpp/dataflow/internal/DataFlowImpl.qll | 71 +++----------------
.../cpp/dataflow/internal/DataFlowImpl2.qll | 71 +++----------------
.../cpp/dataflow/internal/DataFlowImpl3.qll | 71 +++----------------
.../cpp/dataflow/internal/DataFlowImpl4.qll | 71 +++----------------
.../dataflow/internal/DataFlowImplCommon.qll | 6 +-
.../java/dataflow/internal/DataFlowImpl2.qll | 71 +++----------------
.../java/dataflow/internal/DataFlowImpl3.qll | 71 +++----------------
.../java/dataflow/internal/DataFlowImpl4.qll | 71 +++----------------
.../java/dataflow/internal/DataFlowImpl5.qll | 71 +++----------------
.../dataflow/internal/DataFlowImplDepr.qll | 71 +++----------------
10 files changed, 102 insertions(+), 543 deletions(-)
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
index fc1d6e5053e..02c9919723b 100644
--- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
+++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll
@@ -118,7 +118,7 @@ private module ImplCommon {
node1.(ArgumentNode).argumentOf(call, i1) and
node2.getPreUpdateNode().(ArgumentNode).argumentOf(call, i2) and
compatibleTypes(node1.getTypeBound(), f.getType()) and
- compatibleTypes(node2.getTypeBound(), f.getDeclaringType())
+ compatibleTypes(node2.getTypeBound(), f.getContainerType())
)
}
@@ -149,7 +149,7 @@ private module ImplCommon {
setterReturn(p, f) and
arg.argumentOf(node2.asExpr(), _) and
compatibleTypes(node1.getTypeBound(), f.getType()) and
- compatibleTypes(node2.getTypeBound(), f.getDeclaringType())
+ compatibleTypes(node2.getTypeBound(), f.getContainerType())
)
}
@@ -174,7 +174,7 @@ private module ImplCommon {
viableParamArg(p, arg) and
getter(p, f) and
arg.argumentOf(node2.asExpr(), _) and
- compatibleTypes(node1.getTypeBound(), f.getDeclaringType()) and
+ compatibleTypes(node1.getTypeBound(), f.getContainerType()) and
compatibleTypes(node2.getTypeBound(), f.getType())
)
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll
index 57eade7058e..9d28e37cc8a 100644
--- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll
+++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll
@@ -724,76 +724,27 @@ private predicate localFlowBigStep(
localFlowExit(node2, config)
}
-/**
- * Holds if `f` may contain an object of the same type, `t`, as the one
- * that contains `f`, and if this fact should be used to compress
- * access paths.
- *
- * Examples include the tail pointer in a linked list or the left and right
- * pointers in a binary tree.
- */
-private predicate selfRef(Content f, RefType t) {
- t = f.getDeclaringType() and
- f.isSelfRef()
-}
-
-private newtype TFlowContainer =
- TRegularContent(Content f) { not selfRef(f, _) } or
- TSelfRefContent(RefType t) { selfRef(_, t) }
-
-/**
- * A `Content` or a `Content` abstracted as its declaring type.
- *
- * Sequences of one or more `Content`s in the same declaring type for which
- * `isSelfRef()` holds are represented as a single `FlowContainer` in an
- * `AccessPath`.
- */
-private class FlowContainer extends TFlowContainer {
- string toString() {
- exists(Content f | this = TRegularContent(f) and result = f.toString())
- or
- exists(RefType t | this = TSelfRefContent(t) and result = t.toString())
- }
-
- predicate usesContent(Content f) {
- this = TRegularContent(f)
- or
- exists(RefType t | this = TSelfRefContent(t) and selfRef(f, t))
- }
-
- RefType getContainerType() {
- exists(Content f | this = TRegularContent(f) and result = f.getDeclaringType())
- or
- this = TSelfRefContent(result)
- }
-}
-
private newtype TAccessPathFront =
TFrontNil(Type t) or
- TFrontHead(FlowContainer f)
+ TFrontHead(Content f)
/**
* The front of an `AccessPath`. This is either a head or a nil.
*/
private class AccessPathFront extends TAccessPathFront {
string toString() {
- exists(Type t | this = TFrontNil(t) | result = t.toString())
+ exists(Type t | this = TFrontNil(t) | result = ppReprType(t))
or
- exists(FlowContainer f | this = TFrontHead(f) | result = f.toString())
+ exists(Content f | this = TFrontHead(f) | result = f.toString())
}
Type getType() {
this = TFrontNil(result)
or
- exists(FlowContainer head | this = TFrontHead(head) | result = head.getContainerType())
+ exists(Content head | this = TFrontHead(head) | result = head.getContainerType())
}
- predicate headUsesContent(Content f) {
- exists(FlowContainer fc |
- fc.usesContent(f) and
- this = TFrontHead(fc)
- )
- }
+ predicate headUsesContent(Content f) { this = TFrontHead(f) }
}
private class AccessPathFrontNil extends AccessPathFront, TFrontNil { }
@@ -1004,7 +955,7 @@ private predicate consCand(Content f, AccessPathFront apf, Configuration config)
private newtype TAccessPath =
TNil(Type t) or
- TCons(FlowContainer f, int len) { len in [1 .. 5] }
+ TCons(Content f, int len) { len in [1 .. 5] }
/**
* Conceptually a list of `Content`s followed by a `Type`, but only the first
@@ -1016,7 +967,7 @@ private newtype TAccessPath =
private class AccessPath extends TAccessPath {
abstract string toString();
- FlowContainer getHead() { this = TCons(result, _) }
+ Content getHead() { this = TCons(result, _) }
int len() {
this = TNil(_) and result = 0
@@ -1027,27 +978,27 @@ private class AccessPath extends TAccessPath {
Type getType() {
this = TNil(result)
or
- exists(FlowContainer head | this = TCons(head, _) | result = head.getContainerType())
+ exists(Content head | this = TCons(head, _) | result = head.getContainerType())
}
abstract AccessPathFront getFront();
}
private class AccessPathNil extends AccessPath, TNil {
- override string toString() { exists(Type t | this = TNil(t) | result = t.toString()) }
+ override string toString() { exists(Type t | this = TNil(t) | result = ppReprType(t)) }
override AccessPathFront getFront() { exists(Type t | this = TNil(t) | result = TFrontNil(t)) }
}
private class AccessPathCons extends AccessPath, TCons {
override string toString() {
- exists(FlowContainer f, int len | this = TCons(f, len) |
+ exists(Content f, int len | this = TCons(f, len) |
result = f.toString() + ", ... (" + len.toString() + ")"
)
}
override AccessPathFront getFront() {
- exists(FlowContainer f | this = TCons(f, _) | result = TFrontHead(f))
+ exists(Content f | this = TCons(f, _) | result = TFrontHead(f))
}
}
From ce99f469a95e122a0518b5b2f4143064a9a784af Mon Sep 17 00:00:00 2001
From: Dave Bartolomeo <42150477+dave-bartolomeo@users.noreply.github.com>
Date: Thu, 18 Oct 2018 12:02:06 -0700
Subject: [PATCH 53/98] Update cpp/ql/src/Likely Bugs/Likely
Typos/illDefinedForLoop.ql
---
cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
index 06df0141d27..8f35d6f095f 100644
--- a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
@@ -1,6 +1,6 @@
/**
* @name Ill-defined for loop
- * @description A for-loop iteration expressions goes backward with respect of the initialization statement and condition expression.
+ * @description A for-loop iteration expression goes backward with respect of the initialization statement and condition expression.
* @id cpp/ill-defined-for-loop
* @kind problem
* @problem.severity warning
From e2fcaa9e20c910c6105b66ede6befa4ad52da0d8 Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Thu, 18 Oct 2018 14:44:24 -0700
Subject: [PATCH 54/98] Fixing typos & implementing the PR feedback
---
.gitignore | 3 +
.../Likely Typos/illDefinedForLoop.qhelp | 2 +-
.../Likely Typos/illDefinedForLoop.ql | 80 +++++++++++--------
.../illDefinedForLoop/illDefinedForLoop.cpp | 68 ++++++++++++++--
.../illDefinedForLoop.expected | 6 ++
5 files changed, 116 insertions(+), 43 deletions(-)
diff --git a/.gitignore b/.gitignore
index 7e82b2f488c..3327cd9096b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,6 @@
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
+/.vs/ql_6293a/v15/Browse.VC.opendb
+/.vs/ql_6293a/v15/Browse.VC.db
+/.vs/ql_6293a/v15/.suo
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
index 8e99fc88660..8440fdcf544 100644
--- a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
@@ -9,7 +9,7 @@
-
Verify the iteration expression on the for-loop and make sure the direction of the iteration expression is correct.
+
To fix this issue, check that the loop condition is correct and change the iteration expression to match.
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
index 8f35d6f095f..f0aa2a6e4c6 100644
--- a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
@@ -10,58 +10,56 @@
*/
import cpp
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
+import semmle.code.cpp.dataflow.DataFlow
predicate illDefinedDecrForStmt( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition ) {
- v.getAnAssignedValue() = initialCondition
+ v.getAnAssignedValue() = initialCondition
and
exists(
- RelationalOperation rel, Expr e |
+ RelationalOperation rel |
rel = forstmt.getCondition() |
- e = rel.getGreaterOperand()
+ terminalCondition = rel.getGreaterOperand()
and v.getAnAccess() = rel.getLesserOperand()
- and terminalCondition = e
- )
- and
- (
- exists(
- PostfixDecrExpr pdec |
- pdec = forstmt.getUpdate().(PostfixDecrExpr)
- and pdec.getAnOperand() = v.getAnAccess()
- ) or
- exists(
- PrefixDecrExpr pdec |
- pdec = forstmt.getUpdate().(PrefixDecrExpr)
- and pdec.getAnOperand() = v.getAnAccess()
+ and
+ DataFlow::localFlowStep(DataFlow::exprNode(initialCondition), DataFlow::exprNode(rel.getLesserOperand()))
)
+ and
+ exists(
+ DecrementOperation dec |
+ dec = forstmt.getUpdate().(DecrementOperation)
+ and dec.getAnOperand() = v.getAnAccess()
)
and
- upperBound(initialCondition) < lowerBound(terminalCondition)
+ (
+ ( upperBound(initialCondition) < lowerBound(terminalCondition) )
+ or
+ ( forstmt.conditionAlwaysFalse() or forstmt.conditionAlwaysTrue() )
+ )
}
predicate illDefinedIncrForStmt( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition ) {
v.getAnAssignedValue() = initialCondition
and
exists(
- RelationalOperation rel, Expr e |
+ RelationalOperation rel |
rel = forstmt.getCondition() |
- e = rel.getLesserOperand()
+ terminalCondition = rel.getLesserOperand()
and v.getAnAccess() = rel.getGreaterOperand()
- and terminalCondition = e
+ and
+ DataFlow::localFlowStep(DataFlow::exprNode(initialCondition), DataFlow::exprNode(rel.getGreaterOperand()))
)
and
- ( exists( PostfixIncrExpr pincr |
- pincr = forstmt.getUpdate().(PostfixIncrExpr)
- and
- pincr.getAnOperand() = v.getAnAccess()
- ) or
- exists( PrefixIncrExpr pincr |
- pincr = forstmt.getUpdate().(PrefixIncrExpr)
- and
- pincr.getAnOperand() = v.getAnAccess()
- )
+ exists( IncrementOperation incr |
+ incr = forstmt.getUpdate().(IncrementOperation)
+ and
+ incr.getAnOperand() = v.getAnAccess()
)
and
- upperBound(terminalCondition) < lowerBound(initialCondition)
+ (
+ ( upperBound(terminalCondition) < lowerBound(initialCondition))
+ or
+ ( forstmt.conditionAlwaysFalse() or forstmt.conditionAlwaysTrue())
+ )
}
predicate illDefinedForStmtWrongDirection( ForStmt forstmt, Variable v, Expr initialCondition, Expr terminalCondition
@@ -91,9 +89,23 @@ private string forLoopTerminalConditionRelationship(boolean b){
Expr terminalCondition |
illDefinedForStmtWrongDirection(for, v, initialCondition, terminalCondition, isIncr)
and
- message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts "
- + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is "
- + forLoopTerminalConditionRelationship(isIncr) + " (" + terminalCondition + ")."
+ if( for.conditionAlwaysFalse() ) then
+ (
+ message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts "
+ + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is always false."
+ )
+ else if
+ (
+ for.conditionAlwaysTrue() ) then (
+ message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts "
+ + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is always true."
+ )
+ else
+ (
+ message = "Ill-defined for-loop: a loop using variable \"" + v + "\" counts "
+ + forLoopdirection(isIncr) + " from a value ("+ initialCondition +"), but the terminal condition is "
+ + forLoopTerminalConditionRelationship(isIncr) + " (" + terminalCondition + ")."
+ )
)
}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
index db6fc509055..15e5c333102 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
@@ -43,19 +43,19 @@ void Unsigned()
void DeclarationInLoop()
{
- for (signed char i = 0; i < 100; i--) //BUG
+ for (signed char i = 0; i < 100; --i) //BUG
{
}
- for (signed char i = 0; i < 100; i++)
+ for (signed char i = 0; i < 100; ++i)
{
}
- for (unsigned char i = 100; i >= 0; i++) //BUG
+ for (unsigned char i = 100; i >= 0; ++i) //BUG
{
}
- for (unsigned char i = 100; i >= 0; i--)
+ for (unsigned char i = 100; i >= 0; --i)
{
}
}
@@ -88,22 +88,56 @@ void InitializationOutsideLoop()
{
signed char i = 0;
- for (; i < 100; i--) //BUG
+ for (; i < 100; --i) //BUG
{
}
i = 0;
- for (; i < 100; i++)
+ for (; i < 100; ++i)
{
}
i = 100;
- for (; i >= 0; i++) //BUG
+ for (; i >= 0; ++i) //BUG
{
}
i = 100;
- for (; i >= 0; i--)
+ for (; i >= 0; --i)
+ {
+ }
+}
+
+
+void InvalidCondition()
+{
+ signed char i;
+ signed char min = 0;
+ signed char max = 100;
+
+ for (i = max; i < min; i--) //BUG
+ {
+ }
+
+ for (i = min; i > max; i++) //BUG
+ {
+ }
+}
+
+void InvalidConditionUnsignedCornerCase()
+{
+ unsigned char i;
+ unsigned char min = 0;
+ unsigned char max = 100;
+
+ for (i = 100; i < 0; i--) //BUG
+ {
+ }
+
+ // Limitation.
+ // Currently odasa will not detect this for-loop condition as always true
+ // The rule will still detect the mismatch iterator, but the error message may change in the future.
+ for (i = 200; i >= 0; i++) //BUG
{
}
}
@@ -126,3 +160,21 @@ void NegativeTestCaseNested()
}
}
}
+
+//////////////////////////////////////
+// Query limitation:
+//
+// The following test cases are bugs,
+// but will not be found due to the itearion expression
+// not being a prefix or postfix increment/decrement
+//
+void FalseNegativeTestCases()
+{
+ for (int i = 0; i < 10; i = i - 1) {}
+ // For comparison
+ for (int i = 0; i < 10; i-- ) {} // BUG
+
+ for (int i = 100; i > 0; i += 2) {}
+ // For comparison
+ for (int i = 100; i > 0; i ++ ) {} // BUG
+}
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
index 2079abcbe2f..5e2b2bae9a0 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
@@ -14,3 +14,9 @@
| illDefinedForLoop.cpp:77:5:79:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (max), but the terminal condition is lower (min). |
| illDefinedForLoop.cpp:91:5:93:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
| illDefinedForLoop.cpp:101:5:103:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| illDefinedForLoop.cpp:118:5:120:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (max), but the terminal condition is always false. |
+| illDefinedForLoop.cpp:122:5:124:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (min), but the terminal condition is always false. |
+| illDefinedForLoop.cpp:133:5:135:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (100), but the terminal condition is always false. |
+| illDefinedForLoop.cpp:140:5:142:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (200), but the terminal condition is lower (0). |
+| illDefinedForLoop.cpp:175:5:175:36 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (10). |
+| illDefinedForLoop.cpp:179:5:179:38 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
From 8138a3be07bf38be2c9844b073697ca8f95d6cd8 Mon Sep 17 00:00:00 2001
From: Raul Garcia <42392023+raulgarciamsft@users.noreply.github.com>
Date: Thu, 18 Oct 2018 14:45:09 -0700
Subject: [PATCH 55/98] Update .gitignore
---
.gitignore | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3327cd9096b..7e82b2f488c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,3 @@
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
-/.vs/ql_6293a/v15/Browse.VC.opendb
-/.vs/ql_6293a/v15/Browse.VC.db
-/.vs/ql_6293a/v15/.suo
From de108a843dbdce7700b78ea82aba2b186af2f0fc Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Thu, 27 Sep 2018 10:56:32 +0100
Subject: [PATCH 56/98] JavaScript: Patch CFG to improve support for non-top
level import declarations.
---
javascript/ql/src/semmle/javascript/CFG.qll | 46 ++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/javascript/ql/src/semmle/javascript/CFG.qll b/javascript/ql/src/semmle/javascript/CFG.qll
index faec8deca6d..c70b0dd1511 100644
--- a/javascript/ql/src/semmle/javascript/CFG.qll
+++ b/javascript/ql/src/semmle/javascript/CFG.qll
@@ -281,7 +281,7 @@ import javascript
*/
class ControlFlowNode extends @cfg_node, Locatable {
/** Gets a node succeeding this node in the CFG. */
- ControlFlowNode getASuccessor() {
+ cached ControlFlowNode getASuccessor() {
successor(this, result)
}
@@ -457,3 +457,47 @@ class ConcreteControlFlowNode extends ControlFlowNode {
not this instanceof SyntheticControlFlowNode
}
}
+
+/**
+ * A CFG node corresponding to a nested (that is, non-toplevel) import declaration.
+ *
+ * This is a non-standard language feature that is not currently supported very well
+ * by the extractor, in particular such imports do not appear in the control flow graph
+ * generated by the extractor. We patch them in by overriding `getASuccessor`; once an
+ * extractor fix becomes available, this class can be removed.
+ */
+private class NestedImportDeclaration extends ImportDeclaration {
+ NestedImportDeclaration() {
+ exists (ASTNode p | p = getParent() |
+ not p instanceof TopLevel
+ ) and
+ // if there are no specifiers, the default control flow graph is fine
+ exists(getASpecifier())
+ }
+
+ override ControlFlowNode getASuccessor() {
+ result = getSpecifier(0).getFirstControlFlowNode()
+ }
+}
+
+/**
+ * A CFG node corresponding to an import specifier in a nested import declaration.
+ *
+ * As for `NestedImportDeclaration` above, this is a temporary workaround that will be
+ * removed once extractor support for this non-standard language feature becomes available.
+ */
+private class NestedImportSpecifier extends ImportSpecifier {
+ NestedImportDeclaration decl;
+ int i;
+
+ NestedImportSpecifier() {
+ this = decl.getSpecifier(i)
+ }
+
+ override ControlFlowNode getASuccessor() {
+ result = decl.getSpecifier(i+1).getFirstControlFlowNode()
+ or
+ not exists(decl.getSpecifier(i+1)) and
+ successor(decl, result)
+ }
+}
From e683b516119b3a2673a3706c3f830f185b1da606 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Thu, 27 Sep 2018 10:57:45 +0100
Subject: [PATCH 57/98] JavaScript: Generalise code that assumes imports only
appear at the toplevel.
(cherry picked from commit db32dc2bdf5b7609f932169a71d3f00d09c7f861)
---
javascript/ql/src/semmle/javascript/ES2015Modules.qll | 6 +++---
.../dataflow/internal/InterModuleTypeInference.qll | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/ES2015Modules.qll b/javascript/ql/src/semmle/javascript/ES2015Modules.qll
index f5cf676f3d5..4cfa9dcb04d 100644
--- a/javascript/ql/src/semmle/javascript/ES2015Modules.qll
+++ b/javascript/ql/src/semmle/javascript/ES2015Modules.qll
@@ -27,7 +27,7 @@ class ES2015Module extends Module {
/** Gets an export declaration in this module. */
ExportDeclaration getAnExport() {
- result.getContainer() = this
+ result.getTopLevel() = this
}
override Module getAnImportedModule() {
@@ -55,7 +55,7 @@ class ES2015Module extends Module {
/** An import declaration. */
class ImportDeclaration extends Stmt, Import, @importdeclaration {
override ES2015Module getEnclosingModule() {
- result = getContainer()
+ result = getTopLevel()
}
override PathExprInModule getImportedPath() {
@@ -254,7 +254,7 @@ class BulkReExportDeclaration extends ReExportDeclaration, @exportalldeclaration
* but we ignore this subtlety.
*/
private predicate isShadowedFromBulkExport(BulkReExportDeclaration reExport, string name) {
- exists (ExportNamedDeclaration other | other.getContainer() = reExport.getEnclosingModule() |
+ exists (ExportNamedDeclaration other | other.getTopLevel() = reExport.getEnclosingModule() |
other.getAnExportedDecl().getName() = name
or
other.getASpecifier().getExportedName() = name)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
index 6efa6f9b0cf..77359efb216 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
@@ -301,7 +301,7 @@ private class AnalyzedExportAssign extends AnalyzedPropertyWrite, DataFlow::Valu
}
override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
- baseVal = TAbstractModuleObject(exportAssign.getContainer()) and
+ baseVal = TAbstractModuleObject(exportAssign.getTopLevel()) and
propName = "exports" and
source = this
}
From 5e75a62f5c39ed98d7c931dc91ce46f657b11016 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Thu, 27 Sep 2018 10:58:26 +0100
Subject: [PATCH 58/98] JavaScript: Add test case for type inference in the
presence of non-toplevel imports.
(cherry picked from commit 8b7bb8ceccf229985608f6e5274171e5ee864d39)
---
.../test/library-tests/Flow/AbstractValues.expected | 4 ++++
.../ql/test/library-tests/Flow/abseval.expected | 5 +++++
.../ql/test/library-tests/Flow/nestedImport.js | 12 ++++++++++++
javascript/ql/test/library-tests/Flow/types.expected | 3 +++
4 files changed, 24 insertions(+)
create mode 100644 javascript/ql/test/library-tests/Flow/nestedImport.js
diff --git a/javascript/ql/test/library-tests/Flow/AbstractValues.expected b/javascript/ql/test/library-tests/Flow/AbstractValues.expected
index 0f3244a73cb..77911a90215 100644
--- a/javascript/ql/test/library-tests/Flow/AbstractValues.expected
+++ b/javascript/ql/test/library-tests/Flow/AbstractValues.expected
@@ -180,6 +180,10 @@
| n.js:3:16:3:23 | object literal |
| namespace-reexport.js:1:1:4:0 | exports object of module namespace-reexport |
| namespace-reexport.js:1:1:4:0 | module object of module namespace-reexport |
+| nestedImport.js:1:1:13:0 | exports object of module nestedImport |
+| nestedImport.js:1:1:13:0 | module object of module nestedImport |
+| nestedImport.js:9:1:12:1 | function tst |
+| nestedImport.js:9:1:12:1 | instance of function tst |
| nodeJsClient.js:1:1:6:0 | exports object of module nodeJsClient |
| nodeJsClient.js:1:1:6:0 | module object of module nodeJsClient |
| nodeJsLib.js:1:1:4:0 | exports object of module nodeJsLib |
diff --git a/javascript/ql/test/library-tests/Flow/abseval.expected b/javascript/ql/test/library-tests/Flow/abseval.expected
index dbc1a06ebfe..86e8334adc7 100644
--- a/javascript/ql/test/library-tests/Flow/abseval.expected
+++ b/javascript/ql/test/library-tests/Flow/abseval.expected
@@ -54,6 +54,7 @@
| b.js:42:5:42:7 | z11 | b.js:42:11:42:18 | toString | file://:0:0:0:0 | indefinite value (import) |
| b.js:45:5:45:7 | z12 | b.js:45:11:45:12 | f2 | ts2.ts:1:1:6:0 | exports object of module ts2 |
| b.js:45:5:45:7 | z12 | b.js:45:11:45:12 | f2 | ts2.ts:1:10:1:22 | anonymous function |
+| b.js:45:5:45:7 | z12 | b.js:45:11:45:12 | f2 | ts2.ts:4:12:4:13 | object literal |
| b.js:48:5:48:7 | z13 | b.js:48:11:48:11 | w | file://:0:0:0:0 | non-empty, non-numeric string |
| b.js:51:5:51:7 | z14 | b.js:51:11:51:24 | foo_reexported | file://:0:0:0:0 | indefinite value (import) |
| b.js:54:5:54:7 | z15 | b.js:54:11:54:19 | something | file://:0:0:0:0 | indefinite value (import) |
@@ -171,6 +172,10 @@
| mixed.js:9:5:9:6 | fn | mixed.js:9:10:9:19 | __filename | file://:0:0:0:0 | numeric string |
| mixed.js:10:5:10:6 | dn | mixed.js:10:10:10:18 | __dirname | file://:0:0:0:0 | non-empty, non-numeric string |
| mixed.js:10:5:10:6 | dn | mixed.js:10:10:10:18 | __dirname | file://:0:0:0:0 | numeric string |
+| nestedImport.js:2:5:2:6 | x1 | nestedImport.js:2:10:2:12 | foo | esLib.js:3:8:3:24 | function foo |
+| nestedImport.js:6:7:6:8 | x2 | nestedImport.js:6:12:6:14 | foo | file://:0:0:0:0 | indefinite value (import) |
+| nestedImport.js:6:7:6:8 | x2 | nestedImport.js:6:12:6:14 | foo | nodeJsLib.js:3:15:3:37 | function nodeJsFoo |
+| nestedImport.js:11:7:11:8 | x3 | nestedImport.js:11:12:11:14 | foo | esLib.js:3:8:3:24 | function foo |
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | file://:0:0:0:0 | indefinite value (call) |
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | nodeJsLib.js:1:1:4:0 | exports object of module nodeJsLib |
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | nodeJsLib.js:1:18:1:43 | function nodeJsModule |
diff --git a/javascript/ql/test/library-tests/Flow/nestedImport.js b/javascript/ql/test/library-tests/Flow/nestedImport.js
new file mode 100644
index 00000000000..c52953db2cf
--- /dev/null
+++ b/javascript/ql/test/library-tests/Flow/nestedImport.js
@@ -0,0 +1,12 @@
+import { foo } from './esLib';
+let x1 = foo;
+
+if (!foo) {
+ import { foo } from './nodeJsLib';
+ let x2 = foo;
+}
+
+function tst() {
+ import { foo } from './esLib';
+ let x3 = foo;
+}
diff --git a/javascript/ql/test/library-tests/Flow/types.expected b/javascript/ql/test/library-tests/Flow/types.expected
index 786fef8fcb9..f0aef176a10 100644
--- a/javascript/ql/test/library-tests/Flow/types.expected
+++ b/javascript/ql/test/library-tests/Flow/types.expected
@@ -98,6 +98,9 @@
| mixed.js:8:5:8:7 | exp | mixed.js:8:11:8:17 | exports | object |
| mixed.js:9:5:9:6 | fn | mixed.js:9:10:9:19 | __filename | string |
| mixed.js:10:5:10:6 | dn | mixed.js:10:10:10:18 | __dirname | string |
+| nestedImport.js:2:5:2:6 | x1 | nestedImport.js:2:10:2:12 | foo | function |
+| nestedImport.js:6:7:6:8 | x2 | nestedImport.js:6:12:6:14 | foo | boolean, class, date, function, null, number, object, regular expression,string or undefined |
+| nestedImport.js:11:7:11:8 | x3 | nestedImport.js:11:12:11:14 | foo | function |
| nodeJsClient.js:1:5:1:6 | nj | nodeJsClient.js:1:10:1:31 | require ... JsLib') | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| nodeJsClient.js:2:5:2:6 | es | nodeJsClient.js:2:10:2:27 | require('./esLib') | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| nodeJsClient.js:4:5:4:6 | x1 | nodeJsClient.js:4:10:4:15 | nj.foo | boolean, class, date, function, null, number, object, regular expression,string or undefined |
From b2824447405fdccd8bede80dfd97b4f27462a276 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Mon, 8 Oct 2018 16:20:40 +0200
Subject: [PATCH 59/98] Revert "JavaScript: Patch CFG to improve support for
non-top level import declarations."
This reverts commit f05e777e640d6c890609b6863472a371b86a6df1.
---
javascript/ql/src/semmle/javascript/CFG.qll | 46 +--------------------
1 file changed, 1 insertion(+), 45 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/CFG.qll b/javascript/ql/src/semmle/javascript/CFG.qll
index c70b0dd1511..faec8deca6d 100644
--- a/javascript/ql/src/semmle/javascript/CFG.qll
+++ b/javascript/ql/src/semmle/javascript/CFG.qll
@@ -281,7 +281,7 @@ import javascript
*/
class ControlFlowNode extends @cfg_node, Locatable {
/** Gets a node succeeding this node in the CFG. */
- cached ControlFlowNode getASuccessor() {
+ ControlFlowNode getASuccessor() {
successor(this, result)
}
@@ -457,47 +457,3 @@ class ConcreteControlFlowNode extends ControlFlowNode {
not this instanceof SyntheticControlFlowNode
}
}
-
-/**
- * A CFG node corresponding to a nested (that is, non-toplevel) import declaration.
- *
- * This is a non-standard language feature that is not currently supported very well
- * by the extractor, in particular such imports do not appear in the control flow graph
- * generated by the extractor. We patch them in by overriding `getASuccessor`; once an
- * extractor fix becomes available, this class can be removed.
- */
-private class NestedImportDeclaration extends ImportDeclaration {
- NestedImportDeclaration() {
- exists (ASTNode p | p = getParent() |
- not p instanceof TopLevel
- ) and
- // if there are no specifiers, the default control flow graph is fine
- exists(getASpecifier())
- }
-
- override ControlFlowNode getASuccessor() {
- result = getSpecifier(0).getFirstControlFlowNode()
- }
-}
-
-/**
- * A CFG node corresponding to an import specifier in a nested import declaration.
- *
- * As for `NestedImportDeclaration` above, this is a temporary workaround that will be
- * removed once extractor support for this non-standard language feature becomes available.
- */
-private class NestedImportSpecifier extends ImportSpecifier {
- NestedImportDeclaration decl;
- int i;
-
- NestedImportSpecifier() {
- this = decl.getSpecifier(i)
- }
-
- override ControlFlowNode getASuccessor() {
- result = decl.getSpecifier(i+1).getFirstControlFlowNode()
- or
- not exists(decl.getSpecifier(i+1)) and
- successor(decl, result)
- }
-}
From 2b7d69aaf44eee1c10bbf33f28f4ce8731d8c98a Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 10 Oct 2018 15:04:16 +0100
Subject: [PATCH 60/98] JavaScript: Add support for Google Cloud Spanner.
(cherry picked from commit cd284b2f97f392fef4898437245c617946323a54)
---
.../src/semmle/javascript/frameworks/SQL.qll | 102 ++++++++++++++++++
.../frameworks/SQL/SqlString.expected | 20 ++++
.../library-tests/frameworks/SQL/spanner.js | 20 ++++
.../library-tests/frameworks/SQL/spanner2.js | 7 ++
4 files changed, 149 insertions(+)
create mode 100644 javascript/ql/test/library-tests/frameworks/SQL/spanner.js
create mode 100644 javascript/ql/test/library-tests/frameworks/SQL/spanner2.js
diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll
index d31df8973f1..305f8f8adcd 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll
@@ -387,3 +387,105 @@ private module Sequelize {
}
}
}
+
+/**
+ * Provides classes modelling the Google Cloud Spanner library.
+ */
+private module Spanner {
+ /**
+ * Gets a node that refers to the `Spanner` class
+ */
+ DataFlow::SourceNode spanner() {
+ // older versions
+ result = DataFlow::moduleImport("@google-cloud/spanner")
+ or
+ // newer versions
+ result = DataFlow::moduleMember("@google-cloud/spanner", "Spanner")
+ }
+
+ /**
+ * Gets a node that refers to an instance of the `Database` class.
+ */
+ DataFlow::SourceNode database() {
+ result = spanner().getAnInvocation().getAMethodCall("instance").getAMethodCall("database")
+ }
+
+ /**
+ * Gets a node that refers to an instance of the `v1.SpannerClient` class.
+ */
+ DataFlow::SourceNode v1SpannerClient() {
+ result = spanner().getAPropertyRead("v1").getAPropertyRead("SpannerClient").getAnInstantiation()
+ }
+
+ /**
+ * Gets a node that refers to a transaction object.
+ */
+ DataFlow::SourceNode transaction() {
+ result = database().getAMethodCall("runTransaction").getCallback(0).getParameter(1)
+ }
+
+ /**
+ * A call to a Spanner method that executes a SQL query.
+ */
+ abstract class SqlExecution extends DatabaseAccess, DataFlow::InvokeNode {
+ /**
+ * Gets the position of the query argument; default is zero, which can be overridden
+ * by subclasses.
+ */
+ int getQueryArgumentPosition() {
+ result = 0
+ }
+
+ override DataFlow::Node getAQueryArgument() {
+ result = getArgument(getQueryArgumentPosition()) or
+ result = getOptionArgument(getQueryArgumentPosition(), "sql")
+ }
+ }
+
+ /**
+ * A call to `Database.run` or `Database.runStream`.
+ */
+ class DatabaseRunCall extends SqlExecution {
+ DatabaseRunCall() {
+ exists (string run | run = "run" or run = "runPartitionedUpdate" or run = "runStream" |
+ this = database().getAMethodCall(run)
+ )
+ }
+ }
+
+ /**
+ * A call to `Transaction.run` or `Database.runStream`.
+ */
+ class TransactionRunCall extends SqlExecution {
+ TransactionRunCall() {
+ exists (string run | run = "run" or run = "runStream" or run = "runUpdate" |
+ this = transaction().getAMethodCall(run)
+ )
+ }
+ }
+
+ /**
+ * A call to `v1.SpannerClient.executeSql` or `v1.SpannerClient.executeStreamingSql`.
+ */
+ class ExecuteSqlCall extends SqlExecution {
+ ExecuteSqlCall() {
+ exists (string exec | exec = "executeSql" or exec = "executeStreamingSql" |
+ this = v1SpannerClient().getAMethodCall(exec)
+ )
+ }
+
+ override DataFlow::Node getAQueryArgument() {
+ // `executeSql` and `executeStreamingSql` do not accept query strings directly
+ result = getOptionArgument(0, "sql")
+ }
+ }
+
+ /**
+ * An expression that is interpreted as a SQL string.
+ */
+ class QueryString extends SQL::SqlString {
+ QueryString() {
+ this = any(SqlExecution se).getAQueryArgument().asExpr()
+ }
+ }
+}
diff --git a/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected b/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected
index d4866f842e9..ace4b4fa548 100644
--- a/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected
+++ b/javascript/ql/test/library-tests/frameworks/SQL/SqlString.expected
@@ -14,4 +14,24 @@
| postgres3.js:15:16:15:40 | 'SELECT ... s name' |
| sequelize2.js:10:17:10:118 | 'SELECT ... Y name' |
| sequelize.js:8:17:8:118 | 'SELECT ... Y name' |
+| spanner2.js:5:26:5:35 | "SQL code" |
+| spanner2.js:7:35:7:44 | "SQL code" |
+| spanner.js:6:8:6:17 | "SQL code" |
+| spanner.js:7:8:7:26 | { sql: "SQL code" } |
+| spanner.js:7:15:7:24 | "SQL code" |
+| spanner.js:8:25:8:34 | "SQL code" |
+| spanner.js:9:25:9:43 | { sql: "SQL code" } |
+| spanner.js:9:32:9:41 | "SQL code" |
+| spanner.js:10:14:10:23 | "SQL code" |
+| spanner.js:11:14:11:31 | { sql: "SQL code"} |
+| spanner.js:11:21:11:30 | "SQL code" |
+| spanner.js:14:10:14:19 | "SQL code" |
+| spanner.js:15:10:15:28 | { sql: "SQL code" } |
+| spanner.js:15:17:15:26 | "SQL code" |
+| spanner.js:16:16:16:25 | "SQL code" |
+| spanner.js:17:16:17:34 | { sql: "SQL code" } |
+| spanner.js:17:23:17:32 | "SQL code" |
+| spanner.js:18:16:18:25 | "SQL code" |
+| spanner.js:19:16:19:34 | { sql: "SQL code" } |
+| spanner.js:19:23:19:32 | "SQL code" |
| sqlite.js:7:8:7:45 | "UPDATE ... id = ?" |
diff --git a/javascript/ql/test/library-tests/frameworks/SQL/spanner.js b/javascript/ql/test/library-tests/frameworks/SQL/spanner.js
new file mode 100644
index 00000000000..d4276223006
--- /dev/null
+++ b/javascript/ql/test/library-tests/frameworks/SQL/spanner.js
@@ -0,0 +1,20 @@
+const { Spanner } = require("@google-cloud/spanner");
+const spanner = new Spanner();
+const instance = spanner.instance('inst');
+const db = instance.database('db');
+
+db.run("SQL code", (err, rows) => {});
+db.run({ sql: "SQL code" }, (err, rows) => {});
+db.runPartitionedUpdate("SQL code", (err, rows) => {});
+db.runPartitionedUpdate({ sql: "SQL code" }, (err, rows) => {});
+db.runStream("SQL code");
+db.runStream({ sql: "SQL code"});
+
+db.runTransaction((err, tx) => {
+ tx.run("SQL code");
+ tx.run({ sql: "SQL code" });
+ tx.runStream("SQL code");
+ tx.runStream({ sql: "SQL code" });
+ tx.runUpdate("SQL code");
+ tx.runUpdate({ sql: "SQL code" });
+});
\ No newline at end of file
diff --git a/javascript/ql/test/library-tests/frameworks/SQL/spanner2.js b/javascript/ql/test/library-tests/frameworks/SQL/spanner2.js
new file mode 100644
index 00000000000..9795d0f3a39
--- /dev/null
+++ b/javascript/ql/test/library-tests/frameworks/SQL/spanner2.js
@@ -0,0 +1,7 @@
+const spanner = require("@google-cloud/spanner");
+const client = new spanner.v1.SpannerClient({});
+
+client.executeSql("not SQL code", (err, rows) => {});
+client.executeSql({ sql: "SQL code" }, (err, rows) => {});
+client.executeStreamingSql("not SQL code", (err, rows) => {});
+client.executeStreamingSql({ sql: "SQL code" }, (err, rows) => {});
From 898ba94837039674926218d68fe33397b195cb96 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Mon, 15 Oct 2018 20:14:40 +0100
Subject: [PATCH 61/98] JavaScript: Address review comments.
(cherry picked from commit 6835815673bffd8e0bff57c29338a76b0be2a74c)
---
javascript/ql/src/semmle/javascript/frameworks/SQL.qll | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll
index 305f8f8adcd..f933b25b477 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/SQL.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/SQL.qll
@@ -443,7 +443,7 @@ private module Spanner {
}
/**
- * A call to `Database.run` or `Database.runStream`.
+ * A call to `Database.run`, `Database.runPartitionedUpdate` or `Database.runStream`.
*/
class DatabaseRunCall extends SqlExecution {
DatabaseRunCall() {
@@ -454,7 +454,7 @@ private module Spanner {
}
/**
- * A call to `Transaction.run` or `Database.runStream`.
+ * A call to `Transaction.run`, `Transaction.runStream` or `Transaction.runUpdate`.
*/
class TransactionRunCall extends SqlExecution {
TransactionRunCall() {
From 5167d43fbc2e169ce5835ccad88848e4758a6323 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Tue, 9 Oct 2018 12:55:38 +0100
Subject: [PATCH 62/98] JavaScript: Refactor `AnalyzedPropertyWrite::writes` to
enable correct modelling of variable exports.
(cherry picked from commit 080f974663798b42b31aeb4f7c5952198797ba20)
---
.../javascript/dataflow/AbstractProperties.qll | 5 ++---
.../internal/InterModuleTypeInference.qll | 4 ++--
.../internal/PropertyTypeInference.qll | 18 +++++++++++++++++-
.../library-tests/Flow/AbstractValues.expected | 10 ++++++----
javascript/ql/test/library-tests/Flow/a.js | 5 +++++
.../test/library-tests/Flow/abseval.expected | 3 +++
javascript/ql/test/library-tests/Flow/b.js | 3 +++
.../ql/test/library-tests/Flow/types.expected | 2 ++
8 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/AbstractProperties.qll b/javascript/ql/src/semmle/javascript/dataflow/AbstractProperties.qll
index ed362d313d2..9d5ee206532 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/AbstractProperties.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/AbstractProperties.qll
@@ -96,8 +96,7 @@ class AbstractProtoProperty extends AbstractProperty {
* which in turn introduces a materialization.
*/
private AbstractValue getAnAssignedValue(AbstractValue b, string p) {
- exists (AnalyzedPropertyWrite apw, DataFlow::AnalyzedNode afn |
- apw.writes(b, p, afn) and
- result = afn.getALocalValue()
+ exists (AnalyzedPropertyWrite apw |
+ apw.writesValue(b, p, result)
)
}
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
index 77359efb216..599ebb4ae7e 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
@@ -263,10 +263,10 @@ private class AnalyzedVariableExport extends AnalyzedPropertyWrite, DataFlow::Va
astNode = varDef.getTarget()
}
- override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
+ override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue val) {
baseVal = TAbstractExportsObject(export.getEnclosingModule()) and
propName = name and
- source = varDef.getSource().analyze()
+ val = varDef.getAnAssignedValue()
}
}
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/PropertyTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/PropertyTypeInference.qll
index 8eafaa53222..c9160e4a4a7 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/PropertyTypeInference.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/PropertyTypeInference.qll
@@ -96,8 +96,24 @@ abstract class AnalyzedPropertyWrite extends DataFlow::Node {
/**
* Holds if this property write assigns `source` to property `propName` of one of the
* concrete objects represented by `baseVal`.
+ *
+ * Note that not all property writes have an explicit `source` node; use predicate
+ * `writesValue` below to cover these cases.
*/
- abstract predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source);
+ predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
+ none()
+ }
+
+ /**
+ * Holds if this property write assigns `val` to property `propName` of one of the
+ * concrete objects represented by `baseVal`.
+ */
+ predicate writesValue(AbstractValue baseVal, string propName, AbstractValue val) {
+ exists (AnalyzedNode source |
+ writes(baseVal, propName, source) and
+ val = source.getALocalValue()
+ )
+ }
/**
* Holds if the flow information for the base node of this property write is incomplete
diff --git a/javascript/ql/test/library-tests/Flow/AbstractValues.expected b/javascript/ql/test/library-tests/Flow/AbstractValues.expected
index 77911a90215..e7379efebec 100644
--- a/javascript/ql/test/library-tests/Flow/AbstractValues.expected
+++ b/javascript/ql/test/library-tests/Flow/AbstractValues.expected
@@ -4,10 +4,12 @@
| ChatListScreen.js:3:1:5:1 | instance of function foo |
| a2.js:1:1:2:0 | exports object of module a2 |
| a2.js:1:1:2:0 | module object of module a2 |
-| a.js:1:1:13:0 | exports object of module a |
-| a.js:1:1:13:0 | module object of module a |
+| a.js:1:1:18:0 | exports object of module a |
+| a.js:1:1:18:0 | module object of module a |
| a.js:3:8:5:1 | function setX |
| a.js:3:8:5:1 | instance of function setX |
+| a.js:15:1:17:1 | function bump |
+| a.js:15:1:17:1 | instance of function bump |
| amd2.js:1:1:4:0 | exports object of module amd2 |
| amd2.js:1:1:4:0 | module object of module amd2 |
| amd2.js:1:8:3:1 | anonymous function |
@@ -36,8 +38,8 @@
| arguments.js:30:2:33:1 | anonymous function |
| arguments.js:30:2:33:1 | arguments object of anonymous function |
| arguments.js:30:2:33:1 | instance of anonymous function |
-| b.js:1:1:55:0 | exports object of module b |
-| b.js:1:1:55:0 | module object of module b |
+| b.js:1:1:58:0 | exports object of module b |
+| b.js:1:1:58:0 | module object of module b |
| backend.js:1:1:3:0 | exports object of module backend |
| backend.js:1:1:3:0 | module object of module backend |
| backend.js:1:17:1:18 | object literal |
diff --git a/javascript/ql/test/library-tests/Flow/a.js b/javascript/ql/test/library-tests/Flow/a.js
index 5319d461a5b..e733d336127 100644
--- a/javascript/ql/test/library-tests/Flow/a.js
+++ b/javascript/ql/test/library-tests/Flow/a.js
@@ -10,3 +10,8 @@ let z = someGlobal;
export let w;
w = "w";
+
+export let notAlwaysZero = 0;
+function bump() {
+ ++notAlwaysZero;
+}
diff --git a/javascript/ql/test/library-tests/Flow/abseval.expected b/javascript/ql/test/library-tests/Flow/abseval.expected
index 86e8334adc7..11deddd1ab9 100644
--- a/javascript/ql/test/library-tests/Flow/abseval.expected
+++ b/javascript/ql/test/library-tests/Flow/abseval.expected
@@ -6,6 +6,7 @@
| a.js:9:5:9:5 | z | a.js:9:9:9:18 | someGlobal | file://:0:0:0:0 | indefinite value (global) |
| a.js:9:5:9:5 | z | a.js:9:9:9:18 | someGlobal | file://:0:0:0:0 | non-zero value |
| a.js:9:5:9:5 | z | a.js:9:9:9:18 | someGlobal | file://:0:0:0:0 | true |
+| a.js:14:12:14:24 | notAlwaysZero | a.js:14:28:14:28 | 0 | file://:0:0:0:0 | 0 |
| amd.js:2:7:2:7 | m | amd.js:2:11:2:13 | mod | amd.js:1:1:7:0 | module object of module amd |
| amd.js:2:7:2:7 | m | amd.js:2:11:2:13 | mod | file://:0:0:0:0 | indefinite value (call) |
| amd.js:3:7:3:7 | e | amd.js:3:11:3:13 | exp | amd.js:1:1:7:0 | exports object of module amd |
@@ -58,6 +59,8 @@
| b.js:48:5:48:7 | z13 | b.js:48:11:48:11 | w | file://:0:0:0:0 | non-empty, non-numeric string |
| b.js:51:5:51:7 | z14 | b.js:51:11:51:24 | foo_reexported | file://:0:0:0:0 | indefinite value (import) |
| b.js:54:5:54:7 | z15 | b.js:54:11:54:19 | something | file://:0:0:0:0 | indefinite value (import) |
+| b.js:57:5:57:7 | z16 | b.js:57:11:57:23 | notAlwaysZero | file://:0:0:0:0 | 0 |
+| b.js:57:5:57:7 | z16 | b.js:57:11:57:23 | notAlwaysZero | file://:0:0:0:0 | non-zero value |
| backend.js:1:7:1:13 | Backend | backend.js:1:17:1:18 | {} | backend.js:1:17:1:18 | object literal |
| classAccessors.js:10:9:10:11 | myX | classAccessors.js:10:15:10:20 | this.x | file://:0:0:0:0 | indefinite value (call) |
| classAccessors.js:10:9:10:11 | myX | classAccessors.js:10:15:10:20 | this.x | file://:0:0:0:0 | indefinite value (heap) |
diff --git a/javascript/ql/test/library-tests/Flow/b.js b/javascript/ql/test/library-tests/Flow/b.js
index 6c8632f5348..f1d9c7c6606 100644
--- a/javascript/ql/test/library-tests/Flow/b.js
+++ b/javascript/ql/test/library-tests/Flow/b.js
@@ -52,3 +52,6 @@ let z14 = foo_reexported;
import { something } from './reexport-unknown';
let z15 = something;
+
+import { notAlwaysZero } from './a';
+let z16 = notAlwaysZero;
diff --git a/javascript/ql/test/library-tests/Flow/types.expected b/javascript/ql/test/library-tests/Flow/types.expected
index f0aef176a10..bdbff344ac5 100644
--- a/javascript/ql/test/library-tests/Flow/types.expected
+++ b/javascript/ql/test/library-tests/Flow/types.expected
@@ -2,6 +2,7 @@
| a.js:1:12:1:12 | x | a.js:1:16:1:16 | 0 | number |
| a.js:1:19:1:19 | y | a.js:1:23:1:23 | 0 | number |
| a.js:9:5:9:5 | z | a.js:9:9:9:18 | someGlobal | boolean, class, date, function, null, number, object, regular expression,string or undefined |
+| a.js:14:12:14:24 | notAlwaysZero | a.js:14:28:14:28 | 0 | number |
| amd.js:2:7:2:7 | m | amd.js:2:11:2:13 | mod | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| amd.js:3:7:3:7 | e | amd.js:3:11:3:13 | exp | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| arguments.js:2:7:2:7 | y | arguments.js:2:11:2:11 | x | number |
@@ -32,6 +33,7 @@
| b.js:48:5:48:7 | z13 | b.js:48:11:48:11 | w | string |
| b.js:51:5:51:7 | z14 | b.js:51:11:51:24 | foo_reexported | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| b.js:54:5:54:7 | z15 | b.js:54:11:54:19 | something | boolean, class, date, function, null, number, object, regular expression,string or undefined |
+| b.js:57:5:57:7 | z16 | b.js:57:11:57:23 | notAlwaysZero | number |
| backend.js:1:7:1:13 | Backend | backend.js:1:17:1:18 | {} | object |
| classAccessors.js:10:9:10:11 | myX | classAccessors.js:10:15:10:20 | this.x | boolean, class, date, function, null, number, object, regular expression,string or undefined |
| classAccessors.js:11:9:11:11 | myY | classAccessors.js:11:15:11:20 | this.y | boolean, class, date, function, null, number, object, regular expression,string or undefined |
From b0425a298cf651585c9eb9dce8f70ed1220d4306 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 10 Oct 2018 11:41:52 +0100
Subject: [PATCH 63/98] JavaScript: Eliminate slow antijoin predicate.
(cherry picked from commit 0cfd04dfa2cc521f0df56c61390ee3c2f23d562f)
---
.../internal/InterProceduralTypeInference.qll | 20 +++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll
index e71561b4139..49c60bc3eba 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterProceduralTypeInference.qll
@@ -160,18 +160,26 @@ private class IIFEWithAnalyzedReturnFlow extends CallWithAnalyzedReturnFlow {
}
+/**
+ * Gets the only access to `v`, which is the variable declared by `fn`.
+ *
+ * This predicate is not defined for global functions `fn`, or for
+ * local variables `v` that do not have exactly one access.
+ */
+private VarAccess getOnlyAccess(FunctionDeclStmt fn, LocalVariable v) {
+ v = fn.getVariable() and
+ result = v.getAnAccess() and
+ strictcount(v.getAnAccess()) = 1
+}
+
/** A function that only is used locally, making it amenable to type inference. */
class LocalFunction extends Function {
DataFlow::Impl::ExplicitInvokeNode invk;
LocalFunction() {
- this instanceof FunctionDeclStmt and
- exists (LocalVariable v, Expr callee |
- callee = invk.getCalleeNode().asExpr() and
- v = getVariable() and
- v.getAnAccess() = callee and
- forall(VarAccess o | o = v.getAnAccess() | o = callee) and
+ exists (LocalVariable v |
+ getOnlyAccess(this, v) = invk.getCalleeNode().asExpr() and
not exists(v.getAnAssignedExpr()) and
not exists(ExportDeclaration export | export.exportsAs(v, _))
) and
From 374fd597d7482899e7601deebdd4f5589325ae61 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Tue, 16 Oct 2018 07:29:41 +0100
Subject: [PATCH 64/98] JavaScript: Reinstate override.
(cherry picked from commit df5a8651c361c1406d24c4e320b1408a6a98b809)
---
.../dataflow/internal/InterModuleTypeInference.qll | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
index 599ebb4ae7e..117e6be397b 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/internal/InterModuleTypeInference.qll
@@ -263,6 +263,12 @@ private class AnalyzedVariableExport extends AnalyzedPropertyWrite, DataFlow::Va
astNode = varDef.getTarget()
}
+ override predicate writes(AbstractValue baseVal, string propName, DataFlow::AnalyzedNode source) {
+ baseVal = TAbstractExportsObject(export.getEnclosingModule()) and
+ propName = name and
+ source = varDef.getSource().analyze()
+ }
+
override predicate writesValue(AbstractValue baseVal, string propName, AbstractValue val) {
baseVal = TAbstractExportsObject(export.getEnclosingModule()) and
propName = name and
From 4d7e76262995117abd977935eed9c48f0b63dc3a Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 2 Oct 2018 15:06:52 +0100
Subject: [PATCH 65/98] TS: test case for type expansion through type parameter
bound
(cherry picked from commit 8bc92bd53439982e004fa9b79838aebde2d0cbc9)
---
.../TypeScript/ExpansiveTypes/ExpansiveTypes.expected | 1 +
.../TypeScript/ExpansiveTypes/NonExpansiveTypes.expected | 1 +
.../TypeScript/ExpansiveTypes/expansive_signature.ts | 4 ++++
3 files changed, 6 insertions(+)
diff --git a/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/ExpansiveTypes.expected b/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/ExpansiveTypes.expected
index 152299ee587..8ced7c1fb7d 100644
--- a/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/ExpansiveTypes.expected
+++ b/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/ExpansiveTypes.expected
@@ -18,4 +18,5 @@
| ExpansiveMethod in expansive_signature.ts | has no properties |
| ExpansiveParameter in expansive_signature.ts | has no properties |
| ExpansiveSignature in expansive_signature.ts | has no properties |
+| ExpansiveSignatureTypeBound in expansive_signature.ts | has no properties |
| ExpansiveX in used_from_expansion.ts | has no properties |
diff --git a/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/NonExpansiveTypes.expected b/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/NonExpansiveTypes.expected
index edf58f7f0e1..a150036fa21 100644
--- a/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/NonExpansiveTypes.expected
+++ b/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/NonExpansiveTypes.expected
@@ -25,6 +25,7 @@
| ExpansiveMethod in expansive_signature.ts | has properties |
| ExpansiveParameter in expansive_signature.ts | has properties |
| ExpansiveSignature in expansive_signature.ts | has properties |
+| ExpansiveSignatureTypeBound in expansive_signature.ts | has properties |
| ExpansiveX in used_from_expansion.ts | has properties |
| Function in global scope | has properties |
| Intl.CollatorOptions in global scope | has properties |
diff --git a/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/expansive_signature.ts b/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/expansive_signature.ts
index d0ce77d194c..6bdfd8e1f2a 100644
--- a/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/expansive_signature.ts
+++ b/javascript/ql/test/library-tests/TypeScript/ExpansiveTypes/expansive_signature.ts
@@ -19,3 +19,7 @@ interface ExpansiveMethod {
interface ExpansiveFunctionType {
x: () => ExpansiveFunctionType;
}
+
+interface ExpansiveSignatureTypeBound {
+ foo : { >(x: G): G };
+}
From cbf06ae74d41b7cac3e8725de119f8b636b72fc1 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 1 Oct 2018 14:36:19 +0100
Subject: [PATCH 66/98] TypeScript: test case for tokenization of template
literals
(cherry picked from commit 9146cc26bdb85977021ab99d8527ca9c2ba75ca0)
---
.../TypeScript/Tokens/PrettyPrintTokens.expected | 4 ++++
.../TypeScript/Tokens/PrettyPrintTokens.ql | 15 +++++++++++++++
.../TypeScript/Tokens/templateLiteralsJS.js | 4 ++++
.../TypeScript/Tokens/templateLiteralsTS.ts | 4 ++++
4 files changed, 27 insertions(+)
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsJS.js
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
new file mode 100644
index 00000000000..f82a9638acc
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
@@ -0,0 +1,4 @@
+| templateLiteralsJS.js:2 | console . log ( ` template~without~placeholders ` ) ; |
+| templateLiteralsJS.js:3 | console . log ( ` template~with~placeholder~ ${ x } . ` ) ; |
+| templateLiteralsTS.ts:2 | console . log ( `template~without~placeholders` ) ; |
+| templateLiteralsTS.ts:3 | console . log ( `template~with~placeholder~${ x }.` ) ; |
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql
new file mode 100644
index 00000000000..e3344a5075c
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql
@@ -0,0 +1,15 @@
+import javascript
+
+Token getATokenAtLine(File file, int line) {
+ result.getFile() = file and
+ result.getLocation().getStartLine() = line
+}
+
+bindingset[line]
+string getTokenStringAtLine(File file, int line) {
+ result = concat(Token tok | tok = getATokenAtLine(file, line) | tok.toString().replaceAll(" ", "~") + " " order by tok.getLocation().getStartColumn())
+}
+
+from File file, int line
+where exists(CallExpr call | call.getFile() = file and call.getLocation().getStartLine() = line)
+select file.getBaseName() + ":" + line, getTokenStringAtLine(file, line)
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsJS.js b/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsJS.js
new file mode 100644
index 00000000000..689e596474a
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsJS.js
@@ -0,0 +1,4 @@
+function f(x) {
+ console.log(`template without placeholders`);
+ console.log(`template with placeholder ${x}.`);
+}
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts b/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts
new file mode 100644
index 00000000000..689e596474a
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts
@@ -0,0 +1,4 @@
+function f(x) {
+ console.log(`template without placeholders`);
+ console.log(`template with placeholder ${x}.`);
+}
From 2abe34b2f9fcfca825e91962408a7d08e0d71865 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 1 Oct 2018 15:19:43 +0100
Subject: [PATCH 67/98] TypeScript: test case for whitespace before a rescanned
token
(cherry picked from commit a199035a05e7fc5b2fe246706a2cbfdcee00ce03)
---
.../TypeScript/Tokens/PrettyPrintTokens.expected | 5 +++++
.../ql/test/library-tests/TypeScript/Tokens/regexpTS.ts | 4 ++++
.../library-tests/TypeScript/Tokens/templateLiteralsTS.ts | 3 +++
3 files changed, 12 insertions(+)
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/regexpTS.ts
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
index f82a9638acc..a12760caf5e 100644
--- a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
@@ -1,4 +1,9 @@
+| regexpTS.ts:2 | console . log ( /foo/g ) ; |
+| regexpTS.ts:3 | console . log ( /foo/g ) ; |
| templateLiteralsJS.js:2 | console . log ( ` template~without~placeholders ` ) ; |
| templateLiteralsJS.js:3 | console . log ( ` template~with~placeholder~ ${ x } . ` ) ; |
| templateLiteralsTS.ts:2 | console . log ( `template~without~placeholders` ) ; |
| templateLiteralsTS.ts:3 | console . log ( `template~with~placeholder~${ x }.` ) ; |
+| templateLiteralsTS.ts:4 | console . log ( `template~with~placeholder~${ x }.` ) ; |
+| templateLiteralsTS.ts:5 | console . log ( `template~with~placeholder~${ x }.` ) ; |
+| templateLiteralsTS.ts:6 | console . log ( `template~with~placeholder~${ x }.` ) ; |
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/regexpTS.ts b/javascript/ql/test/library-tests/TypeScript/Tokens/regexpTS.ts
new file mode 100644
index 00000000000..ca269c3db89
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/regexpTS.ts
@@ -0,0 +1,4 @@
+function f() {
+ console.log(/foo/g);
+ console.log( /foo/g);
+}
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts b/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts
index 689e596474a..803685637b2 100644
--- a/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/templateLiteralsTS.ts
@@ -1,4 +1,7 @@
function f(x) {
console.log(`template without placeholders`);
console.log(`template with placeholder ${x}.`);
+ console.log(`template with placeholder ${x }.`);
+ console.log(`template with placeholder ${ x}.`);
+ console.log(`template with placeholder ${ x }.`);
}
From 39c788f4f1c231cef5a81fbc13e55a2c8031bebc Mon Sep 17 00:00:00 2001
From: Asger F
Date: Mon, 1 Oct 2018 15:22:02 +0100
Subject: [PATCH 68/98] TypeScript: test case for tokens starting with ">"
(cherry picked from commit d3a1df644cea84b159a21fe7606c4ea9752f4e01)
---
.../TypeScript/Tokens/PrettyPrintTokens.expected | 8 ++++++++
.../library-tests/TypeScript/Tokens/greaterThanTS.ts | 11 +++++++++++
2 files changed, 19 insertions(+)
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/greaterThanTS.ts
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
index a12760caf5e..f9de49e83b9 100644
--- a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
@@ -1,3 +1,11 @@
+| greaterThanTS.ts:2 | console . log ( x >= 1 ) ; |
+| greaterThanTS.ts:3 | console . log ( x >>= 1 ) ; |
+| greaterThanTS.ts:4 | console . log ( x >>>= 1 ) ; |
+| greaterThanTS.ts:5 | console . log ( x >> 1 ) ; |
+| greaterThanTS.ts:6 | console . log ( x >>> 1 ) ; |
+| greaterThanTS.ts:8 | console . log ( x >= 1 ) ; |
+| greaterThanTS.ts:9 | console . log ( x >= 1 ) ; |
+| greaterThanTS.ts:10 | console . log ( x >= 1 ) ; |
| regexpTS.ts:2 | console . log ( /foo/g ) ; |
| regexpTS.ts:3 | console . log ( /foo/g ) ; |
| templateLiteralsJS.js:2 | console . log ( ` template~without~placeholders ` ) ; |
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/greaterThanTS.ts b/javascript/ql/test/library-tests/TypeScript/Tokens/greaterThanTS.ts
new file mode 100644
index 00000000000..391971677f5
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/greaterThanTS.ts
@@ -0,0 +1,11 @@
+function f(x) {
+ console.log(x >= 1);
+ console.log(x >>= 1);
+ console.log(x >>>= 1);
+ console.log(x >> 1);
+ console.log(x >>> 1);
+
+ console.log(x>=1);
+ console.log(x>= 1);
+ console.log(x >=1);
+}
From f9634040b067a1e735ce67a6ab7f4842ec2200f8 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Tue, 2 Oct 2018 10:42:33 +0100
Subject: [PATCH 69/98] TypeScript: add test case with mixed rescanned tokens
(cherry picked from commit 057af7c8657c9484a759a59fd30d42c477f97b35)
---
.../TypeScript/Tokens/PrettyPrintTokens.expected | 4 ++++
javascript/ql/test/library-tests/TypeScript/Tokens/mixed.ts | 6 ++++++
2 files changed, 10 insertions(+)
create mode 100644 javascript/ql/test/library-tests/TypeScript/Tokens/mixed.ts
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
index f9de49e83b9..81e03e43592 100644
--- a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.expected
@@ -6,6 +6,10 @@
| greaterThanTS.ts:8 | console . log ( x >= 1 ) ; |
| greaterThanTS.ts:9 | console . log ( x >= 1 ) ; |
| greaterThanTS.ts:10 | console . log ( x >= 1 ) ; |
+| mixed.ts:2 | console . log ( x >= 1 ) ; |
+| mixed.ts:3 | console . log ( `${ /r/g >= 1 }` ) ; |
+| mixed.ts:4 | console . log ( /r/g ) ; |
+| mixed.ts:5 | console . log ( `${ 1 }${ 1 }` ) ; |
| regexpTS.ts:2 | console . log ( /foo/g ) ; |
| regexpTS.ts:3 | console . log ( /foo/g ) ; |
| templateLiteralsJS.js:2 | console . log ( ` template~without~placeholders ` ) ; |
diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/mixed.ts b/javascript/ql/test/library-tests/TypeScript/Tokens/mixed.ts
new file mode 100644
index 00000000000..7f01ada79ad
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/Tokens/mixed.ts
@@ -0,0 +1,6 @@
+function f(x) {
+ console.log(x >= 1);
+ console.log(`${/r/g >= 1}`);
+ console.log( /r/g);
+ console.log( `${1}${1}`);
+}
From 2e49cd117a9ba1a2d08d7293e065bbc8e5a56ff5 Mon Sep 17 00:00:00 2001
From: Esben Sparre Andreasen
Date: Fri, 12 Oct 2018 09:37:30 +0200
Subject: [PATCH 70/98] JS: avoid flagging early returns in
js/user-controlled-bypass
(cherry picked from commit ffbbb807f4dc3ea7b36f3d42fbca070e9c382673)
---
.../src/Security/CWE-807/ConditionalBypass.ql | 28 +++++++++++++++--
.../CWE-807/ConditionalBypass.expected | 2 ++
.../test/query-tests/Security/CWE-807/tst.js | 31 +++++++++++++++++++
3 files changed, 59 insertions(+), 2 deletions(-)
diff --git a/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql b/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
index ca7188ce5eb..e939bb61f8a 100644
--- a/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
+++ b/javascript/ql/src/Security/CWE-807/ConditionalBypass.ql
@@ -3,7 +3,7 @@
* @description Conditions that the user controls are not suited for making security-related decisions.
* @kind problem
* @problem.severity error
- * @precision high
+ * @precision medium
* @id js/user-controlled-bypass
* @tags security
* external/cwe/cwe-807
@@ -83,8 +83,32 @@ predicate isTaintedGuardForSensitiveAction(Sink sink, DataFlow::Node source, Sen
)
}
+/**
+ * Holds if `e` effectively guards access to `action` by returning or throwing early.
+ *
+ * Example: `if (e) return; action(x)`.
+ */
+predicate isEarlyAbortGuard(Sink e, SensitiveAction action) {
+ exists (IfStmt guard |
+ // `e` is in the condition of an if-statement ...
+ e.asExpr().getParentExpr*() = guard.getCondition() and
+ // ... where the then-branch always throws or returns
+ exists (Stmt abort |
+ abort instanceof ThrowStmt or
+ abort instanceof ReturnStmt |
+ abort.nestedIn(guard) and
+ abort.getBasicBlock().(ReachableBasicBlock).postDominates(guard.getThen().getBasicBlock() )
+ ) and
+ // ... and the else-branch does not exist
+ not exists (guard.getElse()) |
+ // ... and `action` is outside the if-statement
+ not action.asExpr().getEnclosingStmt().nestedIn(guard)
+ )
+}
+
from DataFlow::Node source, DataFlow::Node sink, SensitiveAction action
-where isTaintedGuardForSensitiveAction(sink, source, action)
+where isTaintedGuardForSensitiveAction(sink, source, action) and
+ not isEarlyAbortGuard(sink, action)
select sink, "This condition guards a sensitive $@, but $@ controls it.",
action, "action",
source, "a user-provided value"
diff --git a/javascript/ql/test/query-tests/Security/CWE-807/ConditionalBypass.expected b/javascript/ql/test/query-tests/Security/CWE-807/ConditionalBypass.expected
index 36c6f163cf5..0ee7194b924 100644
--- a/javascript/ql/test/query-tests/Security/CWE-807/ConditionalBypass.expected
+++ b/javascript/ql/test/query-tests/Security/CWE-807/ConditionalBypass.expected
@@ -10,3 +10,5 @@
| tst.js:76:9:76:10 | v1 | This condition guards a sensitive $@, but $@ controls it. | tst.js:78:9:78:22 | process.exit() | action | tst.js:75:14:75:24 | req.cookies | a user-provided value |
| tst.js:76:9:76:10 | v1 | This condition guards a sensitive $@, but $@ controls it. | tst.js:78:9:78:22 | process.exit() | action | tst.js:75:39:75:58 | req.params.requestId | a user-provided value |
| tst.js:90:9:90:41 | req.coo ... secret" | This condition guards a sensitive $@, but $@ controls it. | tst.js:92:9:92:22 | process.exit() | action | tst.js:90:9:90:19 | req.cookies | a user-provided value |
+| tst.js:111:13:111:32 | req.query.vulnerable | This condition guards a sensitive $@, but $@ controls it. | tst.js:114:9:114:16 | verify() | action | tst.js:111:13:111:32 | req.query.vulnerable | a user-provided value |
+| tst.js:118:13:118:32 | req.query.vulnerable | This condition guards a sensitive $@, but $@ controls it. | tst.js:121:13:121:20 | verify() | action | tst.js:118:13:118:32 | req.query.vulnerable | a user-provided value |
diff --git a/javascript/ql/test/query-tests/Security/CWE-807/tst.js b/javascript/ql/test/query-tests/Security/CWE-807/tst.js
index 84235fa0d3f..aa00c47f1eb 100644
--- a/javascript/ql/test/query-tests/Security/CWE-807/tst.js
+++ b/javascript/ql/test/query-tests/Security/CWE-807/tst.js
@@ -99,3 +99,34 @@ app.get('/user/:id', function(req, res) {
console.log(commit.author().toString());
}
});
+
+app.get('/user/:id', function(req, res) {
+ if (!req.body || !username || !password || riskAssessnment == null) { // OK: early return below
+ res.status(400).send({ error: '...', id: '...' });
+ return
+ }
+ customerLogin.customerLogin(username, password, riskAssessment, clientIpAddress);
+
+ while (!verified) {
+ if (req.query.vulnerable) { // NOT OK
+ break;
+ }
+ verify();
+ }
+
+ while (!verified) {
+ if (req.query.vulnerable) { // NOT OK
+ break;
+ } else {
+ verify();
+ }
+ }
+
+ while (!verified) {
+ if (req.query.vulnerable) { // OK: early return
+ return;
+ }
+ verify();
+ }
+
+});
From 826d15e6c1c27e58945bdf36eb4f94cc067e1149 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Fri, 19 Oct 2018 10:41:58 +0200
Subject: [PATCH 71/98] C#: Address review comments
---
.../extractor/Semmle.Extraction.CSharp/CompilerVersion.cs | 8 ++++----
csharp/extractor/Semmle.Extraction/TrapWriter.cs | 7 +++++--
2 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs b/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs
index cec120ca6e7..6f92b639e1a 100644
--- a/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs
+++ b/csharp/extractor/Semmle.Extraction.CSharp/CompilerVersion.cs
@@ -55,21 +55,21 @@ namespace Semmle.Extraction.CSharp
var versionInfo = FileVersionInfo.GetVersionInfo(SpecifiedCompiler);
var compilerDir = Path.GetDirectoryName(SpecifiedCompiler);
- var known_compiler_names = new Dictionary
+ var knownCompilerNames = new Dictionary
{
{ "csc.exe", "Microsoft" },
{ "csc2.exe", "Microsoft" },
{ "csc.dll", "Microsoft" },
{ "mcs.exe", "Novell" }
};
- var mscorlib_exists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll"));
+ var mscorlibExists = File.Exists(Path.Combine(compilerDir, "mscorlib.dll"));
- if (specifiedFramework == null && mscorlib_exists)
+ if (specifiedFramework == null && mscorlibExists)
{
specifiedFramework = compilerDir;
}
- if (!known_compiler_names.TryGetValue(versionInfo.OriginalFilename, out var vendor))
+ if (!knownCompilerNames.TryGetValue(versionInfo.OriginalFilename, out var vendor))
{
SkipExtractionBecause("the compiler name is not recognised");
return;
diff --git a/csharp/extractor/Semmle.Extraction/TrapWriter.cs b/csharp/extractor/Semmle.Extraction/TrapWriter.cs
index 5ead984b5d5..cdc31019d1f 100644
--- a/csharp/extractor/Semmle.Extraction/TrapWriter.cs
+++ b/csharp/extractor/Semmle.Extraction/TrapWriter.cs
@@ -211,11 +211,14 @@ namespace Semmle.Extraction
///
static string ComputeHash(string filePath)
{
- using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
+ using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var shaAlg = new SHA256Managed())
{
var sha = shaAlg.ComputeHash(fileStream);
- return Convert.ToBase64String(sha);
+ var hex = new StringBuilder(sha.Length * 2);
+ foreach (var b in sha)
+ hex.AppendFormat("{0:x2}", b);
+ return hex.ToString();
}
}
From 6f11849fef602147cb899ae1f7371bf601248064 Mon Sep 17 00:00:00 2001
From: Anders Schack-Mulligen
Date: Fri, 19 Oct 2018 15:02:52 +0200
Subject: [PATCH 72/98] Java: Add test.
---
.../query-tests/UselessComparisonTest/B.java | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 java/ql/test/query-tests/UselessComparisonTest/B.java
diff --git a/java/ql/test/query-tests/UselessComparisonTest/B.java b/java/ql/test/query-tests/UselessComparisonTest/B.java
new file mode 100644
index 00000000000..c9c2b1cd69c
--- /dev/null
+++ b/java/ql/test/query-tests/UselessComparisonTest/B.java
@@ -0,0 +1,22 @@
+import java.util.*;
+import java.util.function.*;
+
+public class B {
+ int modcount = 0;
+
+ int[] arr;
+
+ public void modify(IntUnaryOperator func) {
+ int mc = modcount;
+ for (int i = 0; i < arr.length; i++) {
+ arr[i] = func.applyAsInt(arr[i]);
+ }
+ // Always false unless there is a call path from func.applyAsInt(..) to
+ // this method, but should not be reported, as checks guarding
+ // ConcurrentModificationExceptions are expected to always be false in the
+ // absence of API misuse.
+ if (mc != modcount)
+ throw new ConcurrentModificationException();
+ modcount++;
+ }
+}
From b72e2aa6029d6998dd8a24b96c957159a66a7730 Mon Sep 17 00:00:00 2001
From: Asger F
Date: Thu, 18 Oct 2018 11:21:27 +0100
Subject: [PATCH 73/98] JS: address comments and introduce
LabeledBarrierGuardNode
---
.../javascript/dataflow/Configuration.qll | 39 ++++++++-----------
.../javascript/dataflow/TaintTracking.qll | 6 +++
.../semmle/javascript/frameworks/Express.qll | 6 +--
.../javascript/frameworks/ExpressModules.qll | 5 ++-
.../javascript/security/TaintedObject.qll | 14 +++----
.../security/dataflow/RemoteFlowSources.qll | 4 +-
6 files changed, 37 insertions(+), 37 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
index 6daa44daa6c..2ef42c450d1 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
@@ -93,7 +93,7 @@ abstract class Configuration extends string {
}
/**
- * Holds if `source` is a source of flow labelled with `lbl` that is relevant
+ * Holds if `source` is a source of flow labeled with `lbl` that is relevant
* for this configuration.
*/
predicate isSource(DataFlow::Node source, FlowLabel lbl) {
@@ -108,7 +108,7 @@ abstract class Configuration extends string {
}
/**
- * Holds if `sink` is a sink of flow labelled with `lbl` that is relevant
+ * Holds if `sink` is a sink of flow labeled with `lbl` that is relevant
* for this configuration.
*/
predicate isSink(DataFlow::Node sink, FlowLabel lbl) {
@@ -146,7 +146,7 @@ abstract class Configuration extends string {
*/
predicate isBarrier(DataFlow::Node node) {
exists (BarrierGuardNode guard |
- guard.blocksAllLabels() and
+ not guard instanceof LabeledBarrierGuardNode and
isBarrierGuard(guard) and
guard.blocks(node)
)
@@ -165,9 +165,9 @@ abstract class Configuration extends string {
/**
* Holds if flow with label `lbl` cannot flow into `node`.
*/
- predicate isBarrierForLabel(DataFlow::Node node, FlowLabel lbl) {
- exists (BarrierGuardNode guard |
- guard.blocksSpecificLabel(lbl) and
+ predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) {
+ exists (LabeledBarrierGuardNode guard |
+ lbl = guard.getALabel() and
isBarrierGuard(guard) and
guard.blocks(node)
)
@@ -309,21 +309,16 @@ abstract class BarrierGuardNode extends DataFlow::Node {
* Holds if this node blocks expression `e` provided it evaluates to `outcome`.
*/
abstract predicate blocks(boolean outcome, Expr e);
+}
+/**
+ * A guard node that only blocks specific labels.
+ */
+abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
/**
- * Holds if this barrier guard should block all labels.
- *
- * To block specific labels only, subclasses should override this with `none()` and
- * also override `blocksSpecificLabel`.
+ * Get a flow label blocked by this guard node.
*/
- predicate blocksAllLabels() { any() }
-
- /**
- * Holds if this barrier guard only blocks specific labels, and `label` is one of them.
- *
- * Subclasses that override this predicate should also override `blocksAllLabels`.
- */
- predicate blocksSpecificLabel(FlowLabel label) { none() }
+ abstract FlowLabel getALabel();
}
/**
@@ -597,7 +592,7 @@ private predicate flowThroughCall(DataFlow::Node input, DataFlow::Node invk,
calls(invk, f) and // Do not consider partial calls
reachableFromInput(f, invk, input, ret, cfg, summary) and
not cfg.isBarrier(ret, invk) and
- not cfg.isBarrierForLabel(invk, summary.getEndLabel())
+ not cfg.isLabeledBarrier(invk, summary.getEndLabel())
)
}
@@ -669,7 +664,7 @@ private predicate flowStep(DataFlow::Node pred, DataFlow::Configuration cfg,
) and
not cfg.isBarrier(succ) and
not cfg.isBarrier(pred, succ) and
- not cfg.isBarrierForLabel(succ, summary.getEndLabel())
+ not cfg.isLabeledBarrier(succ, summary.getEndLabel())
}
/**
@@ -694,7 +689,7 @@ private predicate reachableFromSource(DataFlow::Node nd, DataFlow::Configuration
exists (FlowLabel lbl |
isSource(nd, cfg, lbl) and
not cfg.isBarrier(nd) and
- not cfg.isBarrierForLabel(nd, lbl) and
+ not cfg.isLabeledBarrier(nd, lbl) and
summary = MkPathSummary(false, false, lbl, lbl)
)
or
@@ -714,7 +709,7 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg,
reachableFromSource(nd, cfg, summary) and
isSink(nd, cfg, summary.getEndLabel()) and
not cfg.isBarrier(nd) and
- not cfg.isBarrierForLabel(nd, summary.getEndLabel())
+ not cfg.isLabeledBarrier(nd, summary.getEndLabel())
or
exists (DataFlow::Node mid, PathSummary stepSummary |
reachableFromSource(nd, cfg, summary) and
diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
index bef9a766b17..53cf8767f6d 100644
--- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
+++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll
@@ -159,6 +159,12 @@ module TaintTracking {
}
+ /**
+ * A sanitizer guard node that only blocks specific flow labels.
+ */
+ abstract class LabeledSanitizerGuardNode extends SanitizerGuardNode, DataFlow::LabeledBarrierGuardNode {
+ }
+
/**
* DEPRECATED: Override `Configuration::isAdditionalTaintStep` or use
* `AdditionalTaintStep` instead.
diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
index 6a2c7af99d0..de7f12e3f3b 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll
@@ -489,18 +489,18 @@ module Express {
result = kind
}
- override predicate isDeepObject() {
+ override predicate isUserControlledObject() {
kind = "body" and
exists (ExpressLibraries::BodyParser bodyParser, RouteHandlerExpr expr |
expr.getBody() = rh and
- bodyParser.isDeepObject() and
+ bodyParser.producesUserControlledObjects() and
bodyParser.flowsToExpr(expr.getAMatchingAncestor())
)
or
// If we can't find the middlewares for the route handler,
// but all known body parsers are deep, assume req.body is a deep object.
kind = "body" and
- forall(ExpressLibraries::BodyParser bodyParser | bodyParser.isDeepObject())
+ forall(ExpressLibraries::BodyParser bodyParser | bodyParser.producesUserControlledObjects())
or
kind = "parameter" and
exists (DataFlow::Node request | request = DataFlow::valueNode(rh.getARequestExpr()) |
diff --git a/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll b/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll
index 2a7beadee5c..6f184880b16 100644
--- a/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll
+++ b/javascript/ql/src/semmle/javascript/frameworks/ExpressModules.qll
@@ -271,9 +271,10 @@ module ExpressLibraries {
}
/**
- * Holds if this parses the input as JSON or extended URL-encoding.
+ * Holds if this parses the input as JSON or extended URL-encoding, resulting
+ * in user-controlled objects (as opposed to user-controlled strings).
*/
- predicate isDeepObject() {
+ predicate producesUserControlledObjects() {
isJson() or isExtendedUrlEncoded()
}
}
diff --git a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
index 6778a65d2e6..efb5545b2c3 100644
--- a/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
+++ b/javascript/ql/src/semmle/javascript/security/TaintedObject.qll
@@ -24,7 +24,7 @@ module TaintedObject {
/**
* Gets the flow label representing a deeply tainted object.
*
- * A "tainted object" is an array or object whose properties values are all assumed to be tainted as well.
+ * A "tainted object" is an array or object whose property values are all assumed to be tainted as well.
*
* Note that the presence of the this label generally implies the presence of the `taint` label as well.
*/
@@ -70,25 +70,23 @@ module TaintedObject {
}
/**
- * A source of a user-controlled deep object object.
+ * A source of a user-controlled deep object.
*/
abstract class Source extends DataFlow::Node {}
/** Request input accesses as a JSON source. */
private class RequestInputAsSource extends Source {
RequestInputAsSource() {
- this.(HTTP::RequestInputAccess).isDeepObject()
+ this.(HTTP::RequestInputAccess).isUserControlledObject()
}
}
/**
* Sanitizer guard that blocks deep object taint.
*/
- abstract class SanitizerGuard extends TaintTracking::SanitizerGuardNode {
- override predicate blocksAllLabels() { none() }
-
- override predicate blocksSpecificLabel(FlowLabel label) {
- label = label()
+ abstract class SanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode {
+ override FlowLabel getALabel() {
+ result = label()
}
}
diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll
index c63a80cad64..964f9e988ef 100644
--- a/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll
+++ b/javascript/ql/src/semmle/javascript/security/dataflow/RemoteFlowSources.qll
@@ -12,9 +12,9 @@ abstract class RemoteFlowSource extends DataFlow::Node {
abstract string getSourceType();
/**
- * Holds if this can be a user-controlled deep object, such as a JSON object parsed from user-controlled data.
+ * Holds if this can be a user-controlled object, such as a JSON object parsed from user-controlled data.
*/
- predicate isDeepObject() { none() }
+ predicate isUserControlledObject() { none() }
}
/**
From 5864e57398fe0407157bc0a532bf9e309f2a7751 Mon Sep 17 00:00:00 2001
From: calum
Date: Tue, 2 Oct 2018 18:17:47 +0100
Subject: [PATCH 74/98] C#: Improvements to LockOrder.ql. Detect
inter-procedural locks, and tidy up the tests.
---
csharp/ql/src/Concurrency/LockOrder.qhelp | 4 +-
csharp/ql/src/Concurrency/LockOrder.ql | 38 +++++++---
.../{LockOrder.cs => LockOrderBad.cs} | 3 +
.../{LockOrderFix.cs => LockOrderGood.cs} | 3 +
.../Concurrency/LockOrder/LockOrder.cs | 70 ++++++++++++++++---
.../Concurrency/LockOrder/LockOrder.expected | 14 ++--
.../Concurrency/LockOrder/LockOrderBad.cs | 34 +++++++++
.../Concurrency/LockOrder/LockOrderGood.cs | 34 +++++++++
8 files changed, 168 insertions(+), 32 deletions(-)
rename csharp/ql/src/Concurrency/{LockOrder.cs => LockOrderBad.cs} (94%)
rename csharp/ql/src/Concurrency/{LockOrderFix.cs => LockOrderGood.cs} (94%)
create mode 100644 csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs
create mode 100644 csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs
diff --git a/csharp/ql/src/Concurrency/LockOrder.qhelp b/csharp/ql/src/Concurrency/LockOrder.qhelp
index 2d592c6b60d..8032e2f3369 100644
--- a/csharp/ql/src/Concurrency/LockOrder.qhelp
+++ b/csharp/ql/src/Concurrency/LockOrder.qhelp
@@ -17,10 +17,10 @@ variables in the same sequence.
The following example shows a program running two threads, which deadlocks because thread1 holds lock1 and
is waiting to acquire lock2, whilst thread2 holds lock2 and is waiting to acquire lock1.
-
+
This problem is resolved by reordering the lock variables as shown below.
-
+
diff --git a/csharp/ql/src/Concurrency/LockOrder.ql b/csharp/ql/src/Concurrency/LockOrder.ql
index 7d1e7211e6b..76bc5588af0 100644
--- a/csharp/ql/src/Concurrency/LockOrder.ql
+++ b/csharp/ql/src/Concurrency/LockOrder.ql
@@ -13,13 +13,31 @@
import csharp
-from LockStmt l1, LockStmt l2, LockStmt l3, LockStmt l4, Variable v1, Variable v2
-where l1.getALockedStmt()=l2
-and l3.getALockedStmt()=l4
-and l1.getLockVariable()=v1
-and l2.getLockVariable()=v2
-and l3.getLockVariable()=v2
-and l4.getLockVariable()=v1
-and v1!=v2
-select l4, "Inconsistent lock sequence. The locks " + v1 + " and " + v2 + " are locked in a different sequence $@.",
- l2, "here"
+LockStmt getAReachableLockStmt(Callable callable) {
+ result.getEnclosingCallable() = callable
+ or
+ exists(Call c | c.getEnclosingCallable() = callable |
+ result = getAReachableLockStmt(c.getARuntimeTarget())
+ )
+}
+
+predicate nestedLocks(LockStmt outer, LockStmt inner) {
+ inner = outer.getALockedStmt()
+ or
+ exists(Call call | call.getEnclosingStmt() = outer.getALockedStmt() |
+ inner = getAReachableLockStmt(call.getARuntimeTarget())
+ )
+}
+
+from LockStmt outer1, LockStmt inner1, LockStmt outer2, LockStmt inner2, Variable v1, Variable v2
+where
+ nestedLocks(outer1, inner1) and
+ nestedLocks(outer2, inner2) and
+ outer1.getLockVariable() = v1 and
+ inner1.getLockVariable() = v2 and
+ outer2.getLockVariable() = v2 and
+ inner2.getLockVariable() = v1 and
+ v1 != v2 and
+ v1.getName() <= v2.getName()
+select inner2, "Inconsistent lock sequence. The locks " + v1 + " and " + v2 + " are locked in a different sequence $@.",
+ inner1, "here"
diff --git a/csharp/ql/src/Concurrency/LockOrder.cs b/csharp/ql/src/Concurrency/LockOrderBad.cs
similarity index 94%
rename from csharp/ql/src/Concurrency/LockOrder.cs
rename to csharp/ql/src/Concurrency/LockOrderBad.cs
index a8c8025143b..a9dd05ac866 100644
--- a/csharp/ql/src/Concurrency/LockOrder.cs
+++ b/csharp/ql/src/Concurrency/LockOrderBad.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Threading;
+
class Deadlock
{
private readonly Object lock1 = new Object();
diff --git a/csharp/ql/src/Concurrency/LockOrderFix.cs b/csharp/ql/src/Concurrency/LockOrderGood.cs
similarity index 94%
rename from csharp/ql/src/Concurrency/LockOrderFix.cs
rename to csharp/ql/src/Concurrency/LockOrderGood.cs
index f1548b956eb..1aaeef6a045 100644
--- a/csharp/ql/src/Concurrency/LockOrderFix.cs
+++ b/csharp/ql/src/Concurrency/LockOrderGood.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Threading;
+
class DeadlockFixed
{
private readonly Object lock1 = new Object();
diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs
index 06382cb47e6..8129b9509a3 100644
--- a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs
+++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs
@@ -1,18 +1,68 @@
using System;
-class Foo
+class LocalTest
{
Object a, b, c;
- void f()
+ void F()
{
- lock (a) lock (b) { }
- lock (b) lock (a) { }
- lock (b) lock (a) { }
- lock (a) lock (b) { }
- lock (b) lock (c) { }
- lock (c) lock (b) { }
- lock (a) lock (a) { }
- lock (a) lock (a) { }
+ // BAD: Flagged in G().
+ lock (a) lock (b) lock(c) { }
+ }
+
+ void G()
+ {
+ // BAD: Inconsistent with F().
+ lock (a) lock (c) lock(b) { }
+ }
+
+ void H()
+ {
+ // GOOD: Consistent with F() and G().
+ lock (a) lock(c) { }
}
}
+
+class GlobalTest
+{
+ Object a, b, c;
+
+ void F()
+ {
+ lock (a) G();
+ }
+
+ void G()
+ {
+ lock (b) H();
+ lock (c) I();
+ }
+
+ void H()
+ {
+ // BAD: Inconsistent with I().
+ lock (c) { }
+ }
+
+ void I()
+ {
+ // BAD: Flagged in H().
+ lock (b) { }
+ }
+}
+
+class LambdaTest
+{
+ Object a, b;
+
+ void F()
+ {
+ Action lock_a = () => { lock(a) { } }; // BAD: Inconsistent with lock_b.
+ Action lock_b = () => { lock(b) { } }; // BAD: Flagged in lock_a.
+
+ lock(a) lock_b();
+ lock(b) lock_a();
+ }
+}
+
+// semmle-extractor-options: /r:System.Runtime.Extensions.dll /r:System.Threading.dll /r:System.Threading.Thread.dll
diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected
index 0fd430ed263..9404fd2afa7 100644
--- a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected
+++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected
@@ -1,10 +1,4 @@
-| LockOrder.cs:9:18:9:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:10:18:10:29 | lock (...) {...} | here |
-| LockOrder.cs:9:18:9:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:11:18:11:29 | lock (...) {...} | here |
-| LockOrder.cs:10:18:10:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:9:18:9:29 | lock (...) {...} | here |
-| LockOrder.cs:10:18:10:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:12:18:12:29 | lock (...) {...} | here |
-| LockOrder.cs:11:18:11:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:9:18:9:29 | lock (...) {...} | here |
-| LockOrder.cs:11:18:11:29 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:12:18:12:29 | lock (...) {...} | here |
-| LockOrder.cs:12:18:12:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:10:18:10:29 | lock (...) {...} | here |
-| LockOrder.cs:12:18:12:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and a are locked in a different sequence $@. | LockOrder.cs:11:18:11:29 | lock (...) {...} | here |
-| LockOrder.cs:13:18:13:29 | lock (...) {...} | Inconsistent lock sequence. The locks c and b are locked in a different sequence $@. | LockOrder.cs:14:18:14:29 | lock (...) {...} | here |
-| LockOrder.cs:14:18:14:29 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:13:18:13:29 | lock (...) {...} | here |
+| LockOrder.cs:16:27:16:37 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:10:27:10:37 | lock (...) {...} | here |
+| LockOrder.cs:50:9:50:20 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:44:9:44:20 | lock (...) {...} | here |
+| LockOrder.cs:60:31:60:41 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:61:31:61:41 | lock (...) {...} | here |
+| LockOrderBad.cs:29:13:31:13 | lock (...) {...} | Inconsistent lock sequence. The locks lock1 and lock2 are locked in a different sequence $@. | LockOrderBad.cs:16:13:18:13 | lock (...) {...} | here |
diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs
new file mode 100644
index 00000000000..a9dd05ac866
--- /dev/null
+++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderBad.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Threading;
+
+class Deadlock
+{
+ private readonly Object lock1 = new Object();
+ private readonly Object lock2 = new Object();
+
+ public void thread1()
+ {
+ lock (lock1)
+ {
+ Console.Out.WriteLine("Thread 1 acquired lock1");
+ Thread.Sleep(10);
+ Console.Out.WriteLine("Thread 1 waiting on lock2");
+ lock (lock2) // Deadlock here
+ {
+ }
+ }
+ }
+
+ public void thread2()
+ {
+ lock (lock2)
+ {
+ Console.Out.WriteLine("Thread 2 acquired lock2");
+ Thread.Sleep(10);
+ Console.Out.WriteLine("Thread 2 waiting on lock1");
+ lock (lock1) // Deadlock here
+ {
+ }
+ }
+ }
+}
diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs
new file mode 100644
index 00000000000..1aaeef6a045
--- /dev/null
+++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrderGood.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Threading;
+
+class DeadlockFixed
+{
+ private readonly Object lock1 = new Object();
+ private readonly Object lock2 = new Object();
+
+ public void thread1()
+ {
+ lock (lock1)
+ {
+ Console.Out.WriteLine("Thread 1 acquired lock1");
+ Thread.Sleep(10);
+ Console.Out.WriteLine("Thread 1 waiting on lock2");
+ lock (lock2)
+ {
+ }
+ }
+ }
+
+ public void thread2()
+ {
+ lock (lock1) // Fixed
+ {
+ Console.Out.WriteLine("Thread 2 acquired lock1");
+ Thread.Sleep(10);
+ Console.Out.WriteLine("Thread 2 waiting on lock2");
+ lock (lock2) // Fixed
+ {
+ }
+ }
+ }
+}
From 4200c5b57c539e2cebb44f83007e921e6f661183 Mon Sep 17 00:00:00 2001
From: calum
Date: Wed, 3 Oct 2018 12:49:31 +0100
Subject: [PATCH 75/98] C#: Analysis change notes.
---
change-notes/1.19/analysis-csharp.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/change-notes/1.19/analysis-csharp.md b/change-notes/1.19/analysis-csharp.md
index 3b85b8f367c..963bf907622 100644
--- a/change-notes/1.19/analysis-csharp.md
+++ b/change-notes/1.19/analysis-csharp.md
@@ -12,8 +12,8 @@
## Changes to existing queries
-| **Query** | **Expected impact** | **Change** |
-|----------------------------|------------------------|------------------------------------------------------------------|
+| Inconsistent lock sequence (`cs/inconsistent-lock-sequence`) | More results | This query now finds inconsistent lock sequences globally across calls. |
+
| *@name of query (Query ID)*| *Impact on results* | *How/why the query has changed* |
From aab30ade938500fe21ca41f147efaf1684846df9 Mon Sep 17 00:00:00 2001
From: calum
Date: Fri, 5 Oct 2018 13:43:30 +0100
Subject: [PATCH 76/98] C#: Restrict nested locks to static variables, and
report the variable itself as well as the lock sequences in order to handle
large numbers of results.
---
csharp/ql/src/Concurrency/LockOrder.ql | 53 ++++++++++++++++++--------
1 file changed, 37 insertions(+), 16 deletions(-)
diff --git a/csharp/ql/src/Concurrency/LockOrder.ql b/csharp/ql/src/Concurrency/LockOrder.ql
index 76bc5588af0..91f7520d23e 100644
--- a/csharp/ql/src/Concurrency/LockOrder.ql
+++ b/csharp/ql/src/Concurrency/LockOrder.ql
@@ -13,31 +13,52 @@
import csharp
+/**
+ * Find a call target conservatively only when there is
+ * a static target or one runtime target.
+ */
+Callable getCallTarget(Call c) {
+ if count(c.getARuntimeTarget()) = 1 then
+ result = c.getARuntimeTarget()
+ else
+ result = c.getTarget()
+}
+
+/** Find any lock statement reachable from a callable. */
LockStmt getAReachableLockStmt(Callable callable) {
result.getEnclosingCallable() = callable
or
- exists(Call c | c.getEnclosingCallable() = callable |
- result = getAReachableLockStmt(c.getARuntimeTarget())
+ exists(Call call | call.getEnclosingCallable() = callable |
+ result = getAReachableLockStmt(getCallTarget(call))
)
}
-predicate nestedLocks(LockStmt outer, LockStmt inner) {
- inner = outer.getALockedStmt()
- or
- exists(Call call | call.getEnclosingStmt() = outer.getALockedStmt() |
- inner = getAReachableLockStmt(call.getARuntimeTarget())
+/**
+ * Finds nested pairs of lock statements, either
+ * inter-procedurally or intra-procedurally.
+ */
+predicate nestedLocks(Variable outerVariable, Variable innerVariable, LockStmt outer, LockStmt inner) {
+ outerVariable = outer.getLockVariable() and
+ innerVariable = inner.getLockVariable() and
+ outerVariable != innerVariable and (
+ inner = outer.getALockedStmt()
+ or
+ exists(Call call | call.getEnclosingStmt() = outer.getALockedStmt() |
+ inner = getAReachableLockStmt(getCallTarget(call))
+ ) and
+ outerVariable.(Modifiable).isStatic() and
+ innerVariable.(Modifiable).isStatic()
)
}
from LockStmt outer1, LockStmt inner1, LockStmt outer2, LockStmt inner2, Variable v1, Variable v2
where
- nestedLocks(outer1, inner1) and
- nestedLocks(outer2, inner2) and
- outer1.getLockVariable() = v1 and
- inner1.getLockVariable() = v2 and
- outer2.getLockVariable() = v2 and
- inner2.getLockVariable() = v1 and
- v1 != v2 and
+ nestedLocks(v1, v2, outer1, inner1) and
+ nestedLocks(v2, v1, outer2, inner2) and
v1.getName() <= v2.getName()
-select inner2, "Inconsistent lock sequence. The locks " + v1 + " and " + v2 + " are locked in a different sequence $@.",
- inner1, "here"
+select v1, "Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found.",
+ v2, v2.getName(),
+ outer1, v1.getName(),
+ inner1, v2.getName(),
+ outer2, v2.getName(),
+ inner2, v1.getName()
From 5a4af0c2b27b9169d6a0dd34c9627b6cec7b16b5 Mon Sep 17 00:00:00 2001
From: calum
Date: Fri, 5 Oct 2018 14:32:33 +0100
Subject: [PATCH 77/98] C#: Update test case for LockOrder.
---
.../Concurrency/LockOrder/LockOrder.cs | 16 +++++++---------
.../Concurrency/LockOrder/LockOrder.expected | 8 ++++----
2 files changed, 11 insertions(+), 13 deletions(-)
diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs
index 8129b9509a3..5f72563406a 100644
--- a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs
+++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.cs
@@ -2,30 +2,29 @@ using System;
class LocalTest
{
+ // BAD: b is flagged.
Object a, b, c;
void F()
{
- // BAD: Flagged in G().
lock (a) lock (b) lock(c) { }
}
void G()
{
- // BAD: Inconsistent with F().
lock (a) lock (c) lock(b) { }
}
void H()
{
- // GOOD: Consistent with F() and G().
lock (a) lock(c) { }
}
}
class GlobalTest
{
- Object a, b, c;
+ // BAD: b is flagged.
+ static Object a, b, c;
void F()
{
@@ -40,25 +39,24 @@ class GlobalTest
void H()
{
- // BAD: Inconsistent with I().
lock (c) { }
}
void I()
{
- // BAD: Flagged in H().
lock (b) { }
}
}
class LambdaTest
{
- Object a, b;
+ // BAD: a is flagged.
+ static Object a, b;
void F()
{
- Action lock_a = () => { lock(a) { } }; // BAD: Inconsistent with lock_b.
- Action lock_b = () => { lock(b) { } }; // BAD: Flagged in lock_a.
+ Action lock_a = () => { lock(a) { } };
+ Action lock_b = () => { lock(b) { } };
lock(a) lock_b();
lock(b) lock_a();
diff --git a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected
index 9404fd2afa7..ff738999771 100644
--- a/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected
+++ b/csharp/ql/test/query-tests/Concurrency/LockOrder/LockOrder.expected
@@ -1,4 +1,4 @@
-| LockOrder.cs:16:27:16:37 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:10:27:10:37 | lock (...) {...} | here |
-| LockOrder.cs:50:9:50:20 | lock (...) {...} | Inconsistent lock sequence. The locks b and c are locked in a different sequence $@. | LockOrder.cs:44:9:44:20 | lock (...) {...} | here |
-| LockOrder.cs:60:31:60:41 | lock (...) {...} | Inconsistent lock sequence. The locks a and b are locked in a different sequence $@. | LockOrder.cs:61:31:61:41 | lock (...) {...} | here |
-| LockOrderBad.cs:29:13:31:13 | lock (...) {...} | Inconsistent lock sequence. The locks lock1 and lock2 are locked in a different sequence $@. | LockOrderBad.cs:16:13:18:13 | lock (...) {...} | here |
+| LockOrder.cs:6:15:6:15 | b | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:6:18:6:18 | c | c | LockOrder.cs:10:18:10:37 | lock (...) {...} | b | LockOrder.cs:10:27:10:37 | lock (...) {...} | c | LockOrder.cs:15:18:15:37 | lock (...) {...} | c | LockOrder.cs:15:27:15:37 | lock (...) {...} | b |
+| LockOrder.cs:27:22:27:22 | b | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:27:25:27:25 | c | c | LockOrder.cs:36:8:36:20 | lock (...) {...} | b | LockOrder.cs:42:9:42:20 | lock (...) {...} | c | LockOrder.cs:37:8:37:20 | lock (...) {...} | c | LockOrder.cs:47:9:47:20 | lock (...) {...} | b |
+| LockOrder.cs:54:19:54:19 | a | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrder.cs:54:22:54:22 | b | b | LockOrder.cs:61:9:61:25 | lock (...) {...} | a | LockOrder.cs:59:33:59:43 | lock (...) {...} | b | LockOrder.cs:62:9:62:25 | lock (...) {...} | b | LockOrder.cs:58:33:58:43 | lock (...) {...} | a |
+| LockOrderBad.cs:6:29:6:33 | lock1 | Inconsistent lock sequence with $@. Lock sequences $@, $@ and $@, $@ found. | LockOrderBad.cs:7:29:7:33 | lock2 | lock2 | LockOrderBad.cs:11:9:19:9 | lock (...) {...} | lock1 | LockOrderBad.cs:16:13:18:13 | lock (...) {...} | lock2 | LockOrderBad.cs:24:9:32:9 | lock (...) {...} | lock2 | LockOrderBad.cs:29:13:31:13 | lock (...) {...} | lock1 |
From 8c29d0eb0ab738ef75357b134098801c98dcce7d Mon Sep 17 00:00:00 2001
From: calum
Date: Fri, 19 Oct 2018 18:08:52 +0100
Subject: [PATCH 78/98] C#: Address review comments.
---
csharp/ql/src/Concurrency/LockOrder.ql | 14 ++++++--------
1 file changed, 6 insertions(+), 8 deletions(-)
diff --git a/csharp/ql/src/Concurrency/LockOrder.ql b/csharp/ql/src/Concurrency/LockOrder.ql
index 91f7520d23e..4ab41e85fda 100644
--- a/csharp/ql/src/Concurrency/LockOrder.ql
+++ b/csharp/ql/src/Concurrency/LockOrder.ql
@@ -14,17 +14,15 @@
import csharp
/**
- * Find a call target conservatively only when there is
- * a static target or one runtime target.
+ * Gets a call target conservatively only when there is
+ * one runtime target.
*/
Callable getCallTarget(Call c) {
- if count(c.getARuntimeTarget()) = 1 then
- result = c.getARuntimeTarget()
- else
- result = c.getTarget()
+ count(c.getARuntimeTarget()) = 1 and
+ result = c.getARuntimeTarget()
}
-/** Find any lock statement reachable from a callable. */
+/** Gets a lock statement reachable from a callable. */
LockStmt getAReachableLockStmt(Callable callable) {
result.getEnclosingCallable() = callable
or
@@ -34,7 +32,7 @@ LockStmt getAReachableLockStmt(Callable callable) {
}
/**
- * Finds nested pairs of lock statements, either
+ * Holds if there is nested pairs of lock statements, either
* inter-procedurally or intra-procedurally.
*/
predicate nestedLocks(Variable outerVariable, Variable innerVariable, LockStmt outer, LockStmt inner) {
From c97a5ed29210b1182e1f4486416b109fe6cfee7d Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 19 Oct 2018 22:45:48 +0100
Subject: [PATCH 79/98] CPP: Add tests of AV Rule 114.ql with non-trivial
return types.
---
.../AV Rule 114/AV Rule 114.expected | 4 ++
.../jsf/4.13 Functions/AV Rule 114/test.cpp | 42 +++++++++++++++++++
2 files changed, 46 insertions(+)
diff --git a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected
index 2bf6f9673a3..eede8bed705 100644
--- a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected
+++ b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected
@@ -3,3 +3,7 @@
| test.c:39:9:39:14 | ExprStmt | Function f6 should return a value of type int but does not return a value here |
| test.cpp:16:1:18:1 | { ... } | Function g2 should return a value of type MyValue but does not return a value here |
| test.cpp:48:2:48:26 | if (...) ... | Function g7 should return a value of type MyValue but does not return a value here |
+| test.cpp:74:1:76:1 | { ... } | Function g10 should return a value of type second but does not return a value here |
+| test.cpp:80:1:82:1 | { ... } | Function g11 should return a value of type first but does not return a value here |
+| test.cpp:86:1:88:1 | { ... } | Function g12 should return a value of type second but does not return a value here |
+| test.cpp:86:1:88:1 | { ... } | Function g12 should return a value of type second but does not return a value here |
diff --git a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp
index 4605fee5a63..eb4df1f0b4e 100644
--- a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp
@@ -50,3 +50,45 @@ MyValue g7(bool c)
DONOTHING
// BAD [the alert here is unfortunately placed]
}
+
+typedef void MYVOID;
+MYVOID g8()
+{
+ // GOOD
+}
+
+template
+class TypePair
+{
+public:
+ typedef T first;
+ typedef U second;
+};
+
+TypePair::first g9()
+{
+ // GOOD (the return type amounts to void)
+}
+
+TypePair::second g10()
+{
+ // BAD (the return type amounts to int)
+}
+
+template
+typename TypePair::first g11()
+{
+ // GOOD (the return type amounts to void) [FALSE POSITIVE]
+}
+
+template
+typename TypePair::second g12()
+{
+ // BAD (the return type amounts to T / int)
+}
+
+void instantiate()
+{
+ g11();
+ g12();
+}
From 515898461336f8882de7304b310bd1f1fe7bdda5 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 19 Oct 2018 22:19:13 +0100
Subject: [PATCH 80/98] CPP: Fix the issue.
---
cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql | 20 ++++++++++---------
.../AV Rule 114/AV Rule 114.expected | 2 --
.../jsf/4.13 Functions/AV Rule 114/test.cpp | 2 +-
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql b/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql
index 61c3413d35d..9a9760e3d5f 100644
--- a/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql
+++ b/cpp/ql/src/jsf/4.13 Functions/AV Rule 114.ql
@@ -19,7 +19,11 @@ import cpp
predicate functionsMissingReturnStmt(Function f, ControlFlowNode blame) {
f.fromSource() and
- not f.getType().getUnderlyingType().getUnspecifiedType() instanceof VoidType and
+ exists(Type returnType |
+ returnType = f.getType().getUnderlyingType().getUnspecifiedType() and
+ not returnType instanceof VoidType and
+ not returnType instanceof TemplateParameter
+ ) and
exists(ReturnStmt s | f.getAPredecessor() = s | blame = s.getAPredecessor())}
/* If a function has a value-carrying return statement, but the extractor hit a snag
@@ -32,13 +36,11 @@ predicate functionImperfectlyExtracted(Function f) {
exists(ErrorExpr ee | ee.getEnclosingFunction() = f)
}
-from Stmt stmt, string msg
+from Stmt stmt, string msg, Function f, ControlFlowNode blame
where
- exists(Function f, ControlFlowNode blame |
- functionsMissingReturnStmt(f, blame) and
- reachable(blame) and
- not functionImperfectlyExtracted(f) and
- (blame = stmt or blame.(Expr).getEnclosingStmt() = stmt) and
- msg = "Function " + f.getName() + " should return a value of type " + f.getType().getName() + " but does not return a value here"
- )
+ functionsMissingReturnStmt(f, blame) and
+ reachable(blame) and
+ not functionImperfectlyExtracted(f) and
+ (blame = stmt or blame.(Expr).getEnclosingStmt() = stmt) and
+ msg = "Function " + f.getName() + " should return a value of type " + f.getType().getName() + " but does not return a value here"
select stmt, msg
diff --git a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected
index eede8bed705..e55d3bc27ea 100644
--- a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected
+++ b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/AV Rule 114.expected
@@ -4,6 +4,4 @@
| test.cpp:16:1:18:1 | { ... } | Function g2 should return a value of type MyValue but does not return a value here |
| test.cpp:48:2:48:26 | if (...) ... | Function g7 should return a value of type MyValue but does not return a value here |
| test.cpp:74:1:76:1 | { ... } | Function g10 should return a value of type second but does not return a value here |
-| test.cpp:80:1:82:1 | { ... } | Function g11 should return a value of type first but does not return a value here |
-| test.cpp:86:1:88:1 | { ... } | Function g12 should return a value of type second but does not return a value here |
| test.cpp:86:1:88:1 | { ... } | Function g12 should return a value of type second but does not return a value here |
diff --git a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp
index eb4df1f0b4e..a2da6ed3cbf 100644
--- a/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.13 Functions/AV Rule 114/test.cpp
@@ -78,7 +78,7 @@ TypePair::second g10()
template
typename TypePair::first g11()
{
- // GOOD (the return type amounts to void) [FALSE POSITIVE]
+ // GOOD (the return type amounts to void)
}
template
From ebeda2fb9901c762b3b6d8e68bb34372fca01953 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Fri, 19 Oct 2018 22:54:44 +0100
Subject: [PATCH 81/98] CPP: Change note.
---
change-notes/1.19/analysis-cpp.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md
index aa74fddba1b..184dd12b940 100644
--- a/change-notes/1.19/analysis-cpp.md
+++ b/change-notes/1.19/analysis-cpp.md
@@ -16,6 +16,7 @@
|----------------------------|------------------------|------------------------------------------------------------------|
| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. |
| Missing return statement (`cpp/missing-return`) | Visible by default | The precision of this query has been increased from 'medium' to 'high', which makes it visible by default in LGTM. It was 'medium' in release 1.17 and 1.18 because it had false positives due to an extractor bug that was fixed in 1.18. |
+| Missing return statement | Fewer false positive results | The query is now produces correct results when a function returns a template dependent type. |
| Call to memory access function may overflow buffer | More correct results | Array indexing with a negative index is now detected by this query. |
| Suspicious add with sizeof | Fewer false positive results | Arithmetic with void pointers (where allowed) is now excluded from this query. |
| Wrong type of arguments to formatting function | Fewer false positive results | False positive results involving typedefs have been removed. Expected argument types are determined more accurately, especially for wide string and pointer types. Custom (non-standard) formatting functions are also identified more accurately. |
From 2f4da8841fc7dee399a237dd10295b669d3feac5 Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Fri, 19 Oct 2018 15:21:56 -0700
Subject: [PATCH 82/98] Changing the name (file & tags) to match the JS
version.
---
...dForLoop.c => inconsistentLoopDirection.c} | 0
....qhelp => inconsistentLoopDirection.qhelp} | 2 +-
...orLoop.ql => inconsistentLoopDirection.ql} | 10 +++++----
.../illDefinedForLoop.expected | 22 -------------------
.../illDefinedForLoop/illDefinedForLoop.qlref | 1 -
.../inconsistentLoopDirection.c} | 0
.../inconsistentLoopDirection.cpp} | 2 +-
.../inconsistentLoopDirection.expected | 22 +++++++++++++++++++
.../inconsistentLoopDirection.qlref | 1 +
9 files changed, 31 insertions(+), 29 deletions(-)
rename cpp/ql/src/Likely Bugs/Likely Typos/{illDefinedForLoop.c => inconsistentLoopDirection.c} (100%)
rename cpp/ql/src/Likely Bugs/Likely Typos/{illDefinedForLoop.qhelp => inconsistentLoopDirection.qhelp} (96%)
rename cpp/ql/src/Likely Bugs/Likely Typos/{illDefinedForLoop.ql => inconsistentLoopDirection.ql} (94%)
delete mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
delete mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref
rename cpp/ql/test/query-tests/Likely Bugs/Likely Typos/{illDefinedForLoop/illDefinedForLoop.c => inconsistentLoopDirection/inconsistentLoopDirection.c} (100%)
rename cpp/ql/test/query-tests/Likely Bugs/Likely Typos/{illDefinedForLoop/illDefinedForLoop.cpp => inconsistentLoopDirection/inconsistentLoopDirection.cpp} (98%)
create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected
create mode 100644 cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.c b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.c
similarity index 100%
rename from cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.c
rename to cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.c
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp
similarity index 96%
rename from cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
rename to cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp
index 8440fdcf544..406019602f7 100644
--- a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.qhelp
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp
@@ -14,7 +14,7 @@
In the following example, the initialization statement (i = 0) and the condition expression (i < 100) indicate that the intended iteration expression should have been incrementing, but instead a postfix decrement operator is used (i--).
-
+
To fix this issue, change the iteration expression to match the direction of the initialization statement and the condition expression: i++.
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.ql
similarity index 94%
rename from cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
rename to cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.ql
index f0aa2a6e4c6..a12cbe08b03 100644
--- a/cpp/ql/src/Likely Bugs/Likely Typos/illDefinedForLoop.ql
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.ql
@@ -1,11 +1,13 @@
/**
- * @name Ill-defined for loop
+ * @name Inconsistent direction of for loop
* @description A for-loop iteration expression goes backward with respect of the initialization statement and condition expression.
- * @id cpp/ill-defined-for-loop
* @kind problem
- * @problem.severity warning
+ * @problem.severity error
* @precision high
- * @tags external/microsoft/6293
+ * @id cpp/inconsistent-loop-direction
+ * @tags correctness
+ * external/cwe/cwe-835
+ * external/microsoft/6293
* @msrc.severity important
*/
import cpp
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
deleted file mode 100644
index 5e2b2bae9a0..00000000000
--- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.expected
+++ /dev/null
@@ -1,22 +0,0 @@
-| illDefinedForLoop.c:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.c:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.c:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.c:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.c:48:5:50:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.c:58:5:60:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.cpp:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.cpp:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.cpp:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.cpp:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.cpp:46:5:48:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.cpp:54:5:56:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.cpp:69:5:71:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (min), but the terminal condition is higher (max). |
-| illDefinedForLoop.cpp:77:5:79:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (max), but the terminal condition is lower (min). |
-| illDefinedForLoop.cpp:91:5:93:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
-| illDefinedForLoop.cpp:101:5:103:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
-| illDefinedForLoop.cpp:118:5:120:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (max), but the terminal condition is always false. |
-| illDefinedForLoop.cpp:122:5:124:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (min), but the terminal condition is always false. |
-| illDefinedForLoop.cpp:133:5:135:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (100), but the terminal condition is always false. |
-| illDefinedForLoop.cpp:140:5:142:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (200), but the terminal condition is lower (0). |
-| illDefinedForLoop.cpp:175:5:175:36 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (10). |
-| illDefinedForLoop.cpp:179:5:179:38 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref
deleted file mode 100644
index cc5ed35c3b4..00000000000
--- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.qlref
+++ /dev/null
@@ -1 +0,0 @@
-Likely Bugs/Likely Typos/illDefinedForLoop.ql
\ No newline at end of file
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.c b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.c
similarity index 100%
rename from cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.c
rename to cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.c
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.cpp
similarity index 98%
rename from cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
rename to cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.cpp
index 15e5c333102..e6e743382ed 100644
--- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/illDefinedForLoop/illDefinedForLoop.cpp
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.cpp
@@ -144,7 +144,7 @@ void InvalidConditionUnsignedCornerCase()
void NegativeTestCase()
{
- for (int i = 0; (200 - i) < 100; i--)
+ for (int i = 0; (100 - i) < 200; i--)
{
// code ...
}
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected
new file mode 100644
index 00000000000..f5ff346271f
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.expected
@@ -0,0 +1,22 @@
+| inconsistentLoopDirection.c:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.c:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.c:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.c:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.c:48:5:50:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.c:58:5:60:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.cpp:5:5:7:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.cpp:13:5:15:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.cpp:27:5:29:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.cpp:35:5:37:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.cpp:46:5:48:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.cpp:54:5:56:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.cpp:69:5:71:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (min), but the terminal condition is higher (max). |
+| inconsistentLoopDirection.cpp:77:5:79:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (max), but the terminal condition is lower (min). |
+| inconsistentLoopDirection.cpp:91:5:93:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (100). |
+| inconsistentLoopDirection.cpp:101:5:103:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.cpp:118:5:120:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (max), but the terminal condition is always false. |
+| inconsistentLoopDirection.cpp:122:5:124:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (min), but the terminal condition is always false. |
+| inconsistentLoopDirection.cpp:133:5:135:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (100), but the terminal condition is always false. |
+| inconsistentLoopDirection.cpp:140:5:142:5 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (200), but the terminal condition is lower (0). |
+| inconsistentLoopDirection.cpp:175:5:175:36 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts downward from a value (0), but the terminal condition is higher (10). |
+| inconsistentLoopDirection.cpp:179:5:179:38 | for(...;...;...) ... | Ill-defined for-loop: a loop using variable "i" counts upward from a value (100), but the terminal condition is lower (0). |
diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref
new file mode 100644
index 00000000000..af5f0a899cb
--- /dev/null
+++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/inconsistentLoopDirection/inconsistentLoopDirection.qlref
@@ -0,0 +1 @@
+Likely Bugs/Likely Typos/inconsistentLoopDirection.ql
\ No newline at end of file
From 25224cc4a056731c66a03aba86775ba1525b1745 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Fri, 19 Oct 2018 10:27:13 +0100
Subject: [PATCH 83/98] Revert "TypeScript: disable queries that rely on token
information"
This reverts commit 003b600e2478fb0aaf4db19b61c782d30cae5156.
---
javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql | 1 -
.../ql/src/LanguageFeatures/SemicolonInsertion.ql | 3 +--
.../MisleadingIndentationAfterControlStmt.ql | 3 +--
.../SemicolonInsertion/template_literal.ts | 12 ------------
4 files changed, 2 insertions(+), 17 deletions(-)
delete mode 100644 javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts
diff --git a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql
index 70c89715bf8..48ae4811244 100644
--- a/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql
+++ b/javascript/ql/src/LanguageFeatures/EmptyArrayInit.ql
@@ -45,5 +45,4 @@ class OmittedArrayElement extends ArrayExpr {
}
from OmittedArrayElement ae
-where not ae.getFile().getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer
select ae, "Avoid omitted array elements."
\ No newline at end of file
diff --git a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql
index 2eef68fe5f4..fc52539be8d 100644
--- a/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql
+++ b/javascript/ql/src/LanguageFeatures/SemicolonInsertion.ql
@@ -36,8 +36,7 @@ where s.hasSemicolonInserted() and
asi = strictcount(Stmt ss | asi(sc, ss, true)) and
nstmt = strictcount(Stmt ss | asi(sc, ss, _)) and
perc = ((1-asi/nstmt)*100).floor() and
- perc >= 90 and
- not s.getFile().getFileType().isTypeScript() // ignore some quirks in the TypeScript tokenizer
+ perc >= 90
select (LastLineOf)s, "Avoid automated semicolon insertion " +
"(" + perc + "% of all statements in $@ have an explicit semicolon).",
sc, "the enclosing " + sctype
\ No newline at end of file
diff --git a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql
index 84e11138f7b..0629224ed50 100644
--- a/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql
+++ b/javascript/ql/src/Statements/MisleadingIndentationAfterControlStmt.ql
@@ -39,7 +39,6 @@ where misleadingIndentationCandidate(ctrl, s1, s2) and
f.hasIndentation(ctrlStartLine, indent, _) and
f.hasIndentation(startLine1, indent, _) and
f.hasIndentation(startLine2, indent, _) and
- not s2 instanceof EmptyStmt and
- not f.getFileType().isTypeScript() // ignore quirks in TypeScript tokenizer
+ not s2 instanceof EmptyStmt
select (FirstLineOf)s2, "The indentation of this statement suggests that it is controlled by $@, while in fact it is not.",
(FirstLineOf)ctrl, "this statement"
\ No newline at end of file
diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts b/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts
deleted file mode 100644
index 01ce984f8e3..00000000000
--- a/javascript/ql/test/query-tests/LanguageFeatures/SemicolonInsertion/template_literal.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-function foo(arg) {
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(arg);
- console.log(`Unknown option '${arg}'.`);
-}
From dbae5c2d6203e1703ae507f8090f76fb7721fa78 Mon Sep 17 00:00:00 2001
From: Dave Bartolomeo <42150477+dave-bartolomeo@users.noreply.github.com>
Date: Mon, 22 Oct 2018 11:50:18 +0100
Subject: [PATCH 84/98] Update change-notes/1.19/analysis-cpp.md
Co-Authored-By: geoffw0
---
change-notes/1.19/analysis-cpp.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md
index 184dd12b940..91977a868be 100644
--- a/change-notes/1.19/analysis-cpp.md
+++ b/change-notes/1.19/analysis-cpp.md
@@ -16,7 +16,7 @@
|----------------------------|------------------------|------------------------------------------------------------------|
| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. |
| Missing return statement (`cpp/missing-return`) | Visible by default | The precision of this query has been increased from 'medium' to 'high', which makes it visible by default in LGTM. It was 'medium' in release 1.17 and 1.18 because it had false positives due to an extractor bug that was fixed in 1.18. |
-| Missing return statement | Fewer false positive results | The query is now produces correct results when a function returns a template dependent type. |
+| Missing return statement | Fewer false positive results | The query is now produces correct results when a function returns a template-dependent type. |
| Call to memory access function may overflow buffer | More correct results | Array indexing with a negative index is now detected by this query. |
| Suspicious add with sizeof | Fewer false positive results | Arithmetic with void pointers (where allowed) is now excluded from this query. |
| Wrong type of arguments to formatting function | Fewer false positive results | False positive results involving typedefs have been removed. Expected argument types are determined more accurately, especially for wide string and pointer types. Custom (non-standard) formatting functions are also identified more accurately. |
From 7bcc4379fca507df38a3c10a749d1d68df1ceed7 Mon Sep 17 00:00:00 2001
From: Robert Marsh
Date: Mon, 22 Oct 2018 09:59:49 -0700
Subject: [PATCH 85/98] C++: accept loops with arbitrary labels or cases
---
cpp/ql/src/Critical/DeadCodeGoto.ql | 5 +--
.../Critical/DeadCodeGoto/test.cpp | 31 +++++++++++++++++++
2 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/Critical/DeadCodeGoto.ql b/cpp/ql/src/Critical/DeadCodeGoto.ql
index d6f619f80e8..831b7e1cfc3 100644
--- a/cpp/ql/src/Critical/DeadCodeGoto.ql
+++ b/cpp/ql/src/Critical/DeadCodeGoto.ql
@@ -27,6 +27,7 @@ where b.getStmt(i) = js
and not s instanceof SwitchCase
// the next statement isn't breaking out of a switch
and not s.(BreakStmt).getBreakable() instanceof SwitchStmt
- // the jump isn't a goto into the body of the next statement
- and not exists (LabelStmt ls | s.(Loop).getStmt().getAChild*() = ls | ls.getName() = js.(GotoStmt).getName())
+ // the next statement isn't a loop that can be jumped into
+ and not exists (LabelStmt ls | s.(Loop).getStmt().getAChild*() = ls)
+ and not exists (SwitchCase sc | s.(Loop).getStmt().getAChild*() = sc)
select js, "This statement makes $@ unreachable.", s, s.toString()
diff --git a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp
index 11e7c18a294..47ac10b7751 100644
--- a/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp
+++ b/cpp/ql/test/query-tests/Critical/DeadCodeGoto/test.cpp
@@ -50,3 +50,34 @@ int test5(int x, int y) {
return x;
}
+void test6(int x, int cond) {
+ if (cond) {
+ x++;
+ } else goto end; // GOOD
+ x++;
+ end:
+}
+
+void test7(int x, int cond) {
+ if (cond)
+ {
+ goto target;
+ }
+ goto somewhere_else; // GOOD
+ while (x < 10) // not dead code
+ {
+ target:
+ x++;
+ }
+ somewhere_else:
+ switch (1)
+ {
+ goto end;
+ while (x < 10) // not dead code
+ {
+ case 1:
+ x++;
+ } break;
+ }
+ end:
+}
From de1556042aff6f96e6538be0bd26281b104eefc1 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Thu, 11 Oct 2018 16:42:36 +0100
Subject: [PATCH 86/98] CPP: Fix hasXMacro performance.
---
cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql
index b4eabe1f175..1f1ddef6a9c 100644
--- a/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql
+++ b/cpp/ql/src/jsf/4.07 Header Files/AV Rule 35.ql
@@ -107,15 +107,17 @@ predicate defUndef(File f, string macroName) {
}
/**
- * Header file `hf` looks like it contains an x-macro called
- * `macroName`. That is, a macro that is used to interpret the
+ * Header file `hf` looks like it contains an x-macro.
+ * That is, a macro that is used to interpret the
* data in `hf`, usually defined just before including that file
* and undefined immediately afterwards.
*/
-predicate hasXMacro(HeaderFile hf, string macroName) {
- usesMacro(hf, macroName) and
- forex(Include i | i.getIncludedFile() = hf |
- defUndef(i.getFile(), macroName)
+predicate hasXMacro(HeaderFile hf) {
+ exists(string macroName |
+ usesMacro(hf, macroName) and
+ forex(File f | f.getAnIncludedFile() = hf |
+ defUndef(f, macroName)
+ )
)
}
@@ -135,7 +137,7 @@ where not hf instanceof IncludeGuardedHeader
exists(UsingEntry ue | ue.getFile() = hf)
)
// Exclude files which look like they contain 'x-macros'
- and not hasXMacro(hf, _)
+ and not hasXMacro(hf)
// Exclude files which are always #imported.
and not forex(Include i | i.getIncludedFile() = hf | i instanceof Import)
// Exclude files which are only included once.
From a3fafd9ad11323ef8097ea59c34fdaf87c4d7022 Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Tue, 23 Oct 2018 11:51:27 +0200
Subject: [PATCH 87/98] C#: Remove `global.json`
---
csharp/extractor/global.json | 5 -----
1 file changed, 5 deletions(-)
delete mode 100644 csharp/extractor/global.json
diff --git a/csharp/extractor/global.json b/csharp/extractor/global.json
deleted file mode 100644
index 664de46cc7d..00000000000
--- a/csharp/extractor/global.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "sdk": {
- "version": "2.1.103"
- }
-}
\ No newline at end of file
From f20af4906b234fd61c1cdabe3d04306a42d23607 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 10 Oct 2018 09:39:50 +0100
Subject: [PATCH 88/98] CPP: Add a test of a Shutdown / Clear method.
---
.../AV Rule 79/AV Rule 79.expected | 2 ++
.../jsf/4.10 Classes/AV Rule 79/Wrapped.cpp | 31 +++++++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
index eed2f14a99f..048d63477d6 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
@@ -17,3 +17,5 @@
| Variants.cpp:65:3:65:17 | ... = ... | Resource a is acquired by class MyClass6 but not released anywhere in this class. |
| Variants.cpp:66:3:66:36 | ... = ... | Resource b is acquired by class MyClass6 but not released anywhere in this class. |
| Variants.cpp:67:3:67:41 | ... = ... | Resource c is acquired by class MyClass6 but not released anywhere in this class. |
+| Wrapped.cpp:46:3:46:22 | ... = ... | Resource ptr2 is acquired by class Wrapped2 but not released anywhere in this class. |
+| Wrapped.cpp:59:3:59:22 | ... = ... | Resource ptr4 is acquired by class Wrapped2 but not released anywhere in this class. |
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp
index e682cf2da07..6b0eb79f41c 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/Wrapped.cpp
@@ -37,3 +37,34 @@ public:
private:
char *ptr1, *ptr2, *ptr3;
};
+
+class Wrapped2
+{
+public:
+ Wrapped2(int len) {
+ ptr1 = new char[len]; // GOOD
+ ptr2 = new char[len]; // BAD: not released in destructor
+
+ Init(len);
+ }
+
+ ~Wrapped2()
+ {
+ Shutdown();
+ }
+
+ void Init(int len)
+ {
+ ptr3 = new char[len]; // GOOD
+ ptr4 = new char[len]; // BAD: not released in destructor
+ }
+
+ void Shutdown()
+ {
+ delete [] ptr1;
+ delete [] ptr3;
+ }
+
+private:
+ char *ptr1, *ptr2, *ptr3, *ptr4;
+};
From 5931a978dc5e64f201881f58bdfc9d0ec9437ee6 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 10 Oct 2018 15:29:04 +0100
Subject: [PATCH 89/98] CPP: Add a test of a template instantiation where the
destructor is never called.
---
.../AV Rule 79/AV Rule 79.expected | 1 +
.../4.10 Classes/AV Rule 79/NoDestructor.cpp | 22 +++++++++++++++++++
2 files changed, 23 insertions(+)
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
index 048d63477d6..2e98819507e 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
@@ -11,6 +11,7 @@
| ExternalOwners.cpp:49:3:49:20 | ... = ... | Resource a is acquired by class MyScreen but not released anywhere in this class. |
| ListDelete.cpp:21:3:21:21 | ... = ... | Resource first is acquired by class MyThingColection but not released anywhere in this class. |
| NoDestructor.cpp:23:3:23:20 | ... = ... | Resource n is acquired by class MyClass5 but not released anywhere in this class. |
+| NoDestructor.cpp:75:3:75:21 | ... = ... | Resource ptr is acquired by class TemplateWithDestructor but not released anywhere in this class. |
| PlacementNew.cpp:36:3:36:36 | ... = ... | Resource p1 is acquired by class MyTestForPlacementNew but not released anywhere in this class. |
| SelfRegistering.cpp:25:3:25:24 | ... = ... | Resource side is acquired by class MyOwner but not released anywhere in this class. |
| Variants.cpp:25:3:25:13 | ... = ... | Resource f is acquired by class MyClass4 but not released anywhere in this class. |
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
index c732e18d261..4d2e06677ac 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
@@ -66,3 +66,25 @@ public:
n = new MyNumber(200); // GOOD: deleted in base class
}
};
+
+template
+class TemplateWithDestructor
+{
+public:
+ TemplateWithDestructor(int len) {
+ ptr = new char[len]; // GOOD [FALSE POSITIVE]
+ }
+
+ ~TemplateWithDestructor()
+ {
+ delete [] ptr;
+ }
+
+private:
+ char *ptr;
+};
+
+void test() {
+ TemplateWithDestructor *t_ptr = new TemplateWithDestructor(10);
+ //delete t_ptr; --- destructor never used
+}
From b861df088761ba52e9cd339d8463ed60f60f8506 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 10 Oct 2018 16:05:26 +0100
Subject: [PATCH 90/98] CPP: Fix issue when destructor body is missing.
---
cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql | 8 ++++++++
.../jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected | 1 -
.../jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp | 2 +-
3 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
index 853285050a4..cf51a3ba44e 100644
--- a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
+++ b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
@@ -159,6 +159,14 @@ predicate unreleasedResource(Resource r, Expr acquire, File f, int acquireLine)
)
and f = acquire.getFile()
and acquireLine = acquire.getLocation().getStartLine()
+
+ // check that any destructor for this class has a block; if it doesn't,
+ // we must be missing information.
+ and forall(Destructor d |
+ d = r.getDeclaringType().getAMember() and
+ not d.isCompilerGenerated() |
+ exists(d.getBlock())
+ )
}
predicate freedInSameMethod(Resource r, Expr acquire) {
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
index 2e98819507e..048d63477d6 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
@@ -11,7 +11,6 @@
| ExternalOwners.cpp:49:3:49:20 | ... = ... | Resource a is acquired by class MyScreen but not released anywhere in this class. |
| ListDelete.cpp:21:3:21:21 | ... = ... | Resource first is acquired by class MyThingColection but not released anywhere in this class. |
| NoDestructor.cpp:23:3:23:20 | ... = ... | Resource n is acquired by class MyClass5 but not released anywhere in this class. |
-| NoDestructor.cpp:75:3:75:21 | ... = ... | Resource ptr is acquired by class TemplateWithDestructor but not released anywhere in this class. |
| PlacementNew.cpp:36:3:36:36 | ... = ... | Resource p1 is acquired by class MyTestForPlacementNew but not released anywhere in this class. |
| SelfRegistering.cpp:25:3:25:24 | ... = ... | Resource side is acquired by class MyOwner but not released anywhere in this class. |
| Variants.cpp:25:3:25:13 | ... = ... | Resource f is acquired by class MyClass4 but not released anywhere in this class. |
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
index 4d2e06677ac..f5d2b02efaa 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
@@ -72,7 +72,7 @@ class TemplateWithDestructor
{
public:
TemplateWithDestructor(int len) {
- ptr = new char[len]; // GOOD [FALSE POSITIVE]
+ ptr = new char[len]; // GOOD
}
~TemplateWithDestructor()
From 905336a62590137c364f3975da41e256903a6ccf Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 10 Oct 2018 16:54:52 +0100
Subject: [PATCH 91/98] CPP: Refine fix.
---
cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
index cf51a3ba44e..cec6d574033 100644
--- a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
+++ b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
@@ -164,7 +164,9 @@ predicate unreleasedResource(Resource r, Expr acquire, File f, int acquireLine)
// we must be missing information.
and forall(Destructor d |
d = r.getDeclaringType().getAMember() and
- not d.isCompilerGenerated() |
+ not d.isCompilerGenerated() and
+ not d.isDefaulted() and
+ not d.isDeleted() |
exists(d.getBlock())
)
}
From 982fd522f1e3ed7451ea2fa29475dcd595a536b7 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Wed, 10 Oct 2018 17:21:24 +0100
Subject: [PATCH 92/98] CPP: Change note.
---
change-notes/1.19/analysis-cpp.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/change-notes/1.19/analysis-cpp.md b/change-notes/1.19/analysis-cpp.md
index aa74fddba1b..8447391e421 100644
--- a/change-notes/1.19/analysis-cpp.md
+++ b/change-notes/1.19/analysis-cpp.md
@@ -14,7 +14,7 @@
| **Query** | **Expected impact** | **Change** |
|----------------------------|------------------------|------------------------------------------------------------------|
-| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. |
+| Resource not released in destructor | Fewer false positive results | Placement new is now excluded from the query. Also fixed an issue where false positives could occur if the destructor body was not in the snapshot. |
| Missing return statement (`cpp/missing-return`) | Visible by default | The precision of this query has been increased from 'medium' to 'high', which makes it visible by default in LGTM. It was 'medium' in release 1.17 and 1.18 because it had false positives due to an extractor bug that was fixed in 1.18. |
| Call to memory access function may overflow buffer | More correct results | Array indexing with a negative index is now detected by this query. |
| Suspicious add with sizeof | Fewer false positive results | Arithmetic with void pointers (where allowed) is now excluded from this query. |
From 76a5072c8bf39b9e0df11e1e7a9bd4c0b4de5343 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 23 Oct 2018 12:37:39 +0100
Subject: [PATCH 93/98] CPP: Change in results presumed to result from
discover_walk extractor changes.
---
.../query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected | 1 +
.../query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
index 048d63477d6..2e98819507e 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
@@ -11,6 +11,7 @@
| ExternalOwners.cpp:49:3:49:20 | ... = ... | Resource a is acquired by class MyScreen but not released anywhere in this class. |
| ListDelete.cpp:21:3:21:21 | ... = ... | Resource first is acquired by class MyThingColection but not released anywhere in this class. |
| NoDestructor.cpp:23:3:23:20 | ... = ... | Resource n is acquired by class MyClass5 but not released anywhere in this class. |
+| NoDestructor.cpp:75:3:75:21 | ... = ... | Resource ptr is acquired by class TemplateWithDestructor but not released anywhere in this class. |
| PlacementNew.cpp:36:3:36:36 | ... = ... | Resource p1 is acquired by class MyTestForPlacementNew but not released anywhere in this class. |
| SelfRegistering.cpp:25:3:25:24 | ... = ... | Resource side is acquired by class MyOwner but not released anywhere in this class. |
| Variants.cpp:25:3:25:13 | ... = ... | Resource f is acquired by class MyClass4 but not released anywhere in this class. |
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
index f5d2b02efaa..4d2e06677ac 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
@@ -72,7 +72,7 @@ class TemplateWithDestructor
{
public:
TemplateWithDestructor(int len) {
- ptr = new char[len]; // GOOD
+ ptr = new char[len]; // GOOD [FALSE POSITIVE]
}
~TemplateWithDestructor()
From dda7069890c467d7571b8f6828f26f86b2273ad7 Mon Sep 17 00:00:00 2001
From: Geoffrey White <40627776+geoffw0@users.noreply.github.com>
Date: Tue, 23 Oct 2018 12:45:37 +0100
Subject: [PATCH 94/98] CPP: Look for destructors in the template.
---
cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql | 5 +++--
.../jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected | 1 -
.../query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
index cec6d574033..6ad499a8337 100644
--- a/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
+++ b/cpp/ql/src/jsf/4.10 Classes/AV Rule 79.ql
@@ -162,8 +162,9 @@ predicate unreleasedResource(Resource r, Expr acquire, File f, int acquireLine)
// check that any destructor for this class has a block; if it doesn't,
// we must be missing information.
- and forall(Destructor d |
- d = r.getDeclaringType().getAMember() and
+ and forall(Class c, Destructor d |
+ r.getDeclaringType().isConstructedFrom*(c) and
+ d = c.getAMember() and
not d.isCompilerGenerated() and
not d.isDefaulted() and
not d.isDeleted() |
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
index 2e98819507e..048d63477d6 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/AV Rule 79.expected
@@ -11,7 +11,6 @@
| ExternalOwners.cpp:49:3:49:20 | ... = ... | Resource a is acquired by class MyScreen but not released anywhere in this class. |
| ListDelete.cpp:21:3:21:21 | ... = ... | Resource first is acquired by class MyThingColection but not released anywhere in this class. |
| NoDestructor.cpp:23:3:23:20 | ... = ... | Resource n is acquired by class MyClass5 but not released anywhere in this class. |
-| NoDestructor.cpp:75:3:75:21 | ... = ... | Resource ptr is acquired by class TemplateWithDestructor but not released anywhere in this class. |
| PlacementNew.cpp:36:3:36:36 | ... = ... | Resource p1 is acquired by class MyTestForPlacementNew but not released anywhere in this class. |
| SelfRegistering.cpp:25:3:25:24 | ... = ... | Resource side is acquired by class MyOwner but not released anywhere in this class. |
| Variants.cpp:25:3:25:13 | ... = ... | Resource f is acquired by class MyClass4 but not released anywhere in this class. |
diff --git a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
index 4d2e06677ac..f5d2b02efaa 100644
--- a/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
+++ b/cpp/ql/test/query-tests/jsf/4.10 Classes/AV Rule 79/NoDestructor.cpp
@@ -72,7 +72,7 @@ class TemplateWithDestructor
{
public:
TemplateWithDestructor(int len) {
- ptr = new char[len]; // GOOD [FALSE POSITIVE]
+ ptr = new char[len]; // GOOD
}
~TemplateWithDestructor()
From f103b1a371f79a80d6ac63435053acb972b98c8e Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 24 Oct 2018 08:39:22 +0100
Subject: [PATCH 95/98] JavaScript: Copy over a test left in internal repo.
This test seems to have been accidentally committed into the old location in the internal repo.
---
.../TypeAnnotations/ImportTypeExpr.expected | 7 +++++++
.../TypeScript/TypeAnnotations/ImportTypeExpr.ql | 13 +++++++++++++
2 files changed, 20 insertions(+)
create mode 100644 javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected
create mode 100644 javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected
new file mode 100644
index 00000000000..dd96b0bcfb7
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.expected
@@ -0,0 +1,7 @@
+| tst.ts:122:19:122:32 | import("type") | type | type |
+| tst.ts:123:26:123:39 | import("type") | type | type |
+| tst.ts:124:28:124:46 | import("namespace") | namespace | namespace |
+| tst.ts:125:35:125:53 | import("namespace") | namespace | namespace |
+| tst.ts:126:28:126:42 | import("value") | value | value |
+| tst.ts:127:37:127:51 | import("value") | value | value |
+| tst.ts:128:38:130:3 | import( ... ce'\\n ) | awkard-namespace | namespace |
diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql
new file mode 100644
index 00000000000..9d61276462d
--- /dev/null
+++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/ImportTypeExpr.ql
@@ -0,0 +1,13 @@
+import javascript
+
+string getKind(ImportTypeExpr imprt) {
+ if imprt.isTypeAccess() then
+ result = "type"
+ else if imprt.isNamespaceAccess() then
+ result = "namespace"
+ else
+ result = "value"
+}
+
+from ImportTypeExpr imprt
+select imprt, imprt.getPath(), getKind(imprt)
From 97904eb2026b9c200049335640aa8bb6bb320bce Mon Sep 17 00:00:00 2001
From: Tom Hvitved
Date: Mon, 8 Oct 2018 16:20:40 +0200
Subject: [PATCH 96/98] Revert "JavaScript: Patch CFG to improve support for
non-top level import declarations."
This reverts commit f05e777e640d6c890609b6863472a371b86a6df1.
---
javascript/ql/src/semmle/javascript/CFG.qll | 46 +--------------------
1 file changed, 1 insertion(+), 45 deletions(-)
diff --git a/javascript/ql/src/semmle/javascript/CFG.qll b/javascript/ql/src/semmle/javascript/CFG.qll
index c70b0dd1511..faec8deca6d 100644
--- a/javascript/ql/src/semmle/javascript/CFG.qll
+++ b/javascript/ql/src/semmle/javascript/CFG.qll
@@ -281,7 +281,7 @@ import javascript
*/
class ControlFlowNode extends @cfg_node, Locatable {
/** Gets a node succeeding this node in the CFG. */
- cached ControlFlowNode getASuccessor() {
+ ControlFlowNode getASuccessor() {
successor(this, result)
}
@@ -457,47 +457,3 @@ class ConcreteControlFlowNode extends ControlFlowNode {
not this instanceof SyntheticControlFlowNode
}
}
-
-/**
- * A CFG node corresponding to a nested (that is, non-toplevel) import declaration.
- *
- * This is a non-standard language feature that is not currently supported very well
- * by the extractor, in particular such imports do not appear in the control flow graph
- * generated by the extractor. We patch them in by overriding `getASuccessor`; once an
- * extractor fix becomes available, this class can be removed.
- */
-private class NestedImportDeclaration extends ImportDeclaration {
- NestedImportDeclaration() {
- exists (ASTNode p | p = getParent() |
- not p instanceof TopLevel
- ) and
- // if there are no specifiers, the default control flow graph is fine
- exists(getASpecifier())
- }
-
- override ControlFlowNode getASuccessor() {
- result = getSpecifier(0).getFirstControlFlowNode()
- }
-}
-
-/**
- * A CFG node corresponding to an import specifier in a nested import declaration.
- *
- * As for `NestedImportDeclaration` above, this is a temporary workaround that will be
- * removed once extractor support for this non-standard language feature becomes available.
- */
-private class NestedImportSpecifier extends ImportSpecifier {
- NestedImportDeclaration decl;
- int i;
-
- NestedImportSpecifier() {
- this = decl.getSpecifier(i)
- }
-
- override ControlFlowNode getASuccessor() {
- result = decl.getSpecifier(i+1).getFirstControlFlowNode()
- or
- not exists(decl.getSpecifier(i+1)) and
- successor(decl, result)
- }
-}
From a04eb53189cb7af8f8c000f711251ddfe57edc16 Mon Sep 17 00:00:00 2001
From: Raul Garcia
Date: Wed, 24 Oct 2018 15:22:53 -0700
Subject: [PATCH 97/98] Documentation bug fix. Encoding the "<" character
---
.gitignore | 3 +++
.../Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 7e82b2f488c..3327cd9096b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,6 @@
/.vs/ql/v15/Browse.VC.db
/.vs/ProjectSettings.json
+/.vs/ql_6293a/v15/Browse.VC.opendb
+/.vs/ql_6293a/v15/Browse.VC.db
+/.vs/ql_6293a/v15/.suo
diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp
index 406019602f7..fd28b37d878 100644
--- a/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp
+++ b/cpp/ql/src/Likely Bugs/Likely Typos/inconsistentLoopDirection.qhelp
@@ -13,7 +13,7 @@
-
In the following example, the initialization statement (i = 0) and the condition expression (i < 100) indicate that the intended iteration expression should have been incrementing, but instead a postfix decrement operator is used (i--).
+
In the following example, the initialization statement (i = 0) and the condition expression (i < 100) indicate that the intended iteration expression should have been incrementing, but instead a postfix decrement operator is used (i--).
To fix this issue, change the iteration expression to match the direction of the initialization statement and the condition expression: i++.